npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vdaluz/astro-og-cards

v1.1.1

Published

Build-time OG/social-card generation (satori + sharp) and meta-tag emission for Astro - proven in production on vdaluz.com and imperfectsystems.com.

Readme

@vdaluz/astro-og-cards

CI

Social shares need an OG image, and generating one at build time from real post data (not a hand-designed static fallback) usually means wiring up a headless browser or a from-scratch Satori setup yourself. @vdaluz/astro-og-cards packages both halves: a meta-tag emission component for the og:*/twitter:* tags, and a build-time card generation harness (satori + sharp) that renders styled HTML to a real PNG. Ships raw .astro/.ts - the consuming app's Astro/Vite compiles them (no prebuild step), same as @vdaluz/astro-blog. Proven in production on vdaluz.com and imperfectsystems.com.

Install

npm install @vdaluz/astro-og-cards

Alternatively, a pinned https tarball from a tag works too, with no registry involved:

// package.json
"dependencies": {
  "@vdaluz/astro-og-cards": "https://github.com/vdaluz/astro-og-cards/archive/refs/tags/v1.1.0.tar.gz"
}

Why a tarball, not github:vdaluz/astro-og-cards#v1.0.0? npm canonicalizes GitHub shorthand (and even an explicit git+https:// URL) to git+ssh:// in the lockfile. CI runners (e.g. Cloudflare Pages/Workers) have no SSH key, so npm ci would fail to clone it. The /archive/refs/tags/<tag>.tar.gz URL is anonymous https with an integrity hash in the lockfile - it just works in CI. Bump the tag in the URL to upgrade.

Peer dependency: astro >= 6.

Installing this pulls in native sharp. satori, satori-html, and sharp are regular dependencies, not optional ones, so every consumer installs all three even if it only uses OgMeta (pure meta tags, no image generation). That's an intentional tradeoff, not an oversight: card generation is this package's actual point, and splitting OgMeta into its own zero-dependency package for the rare meta-tags-only consumer isn't worth the maintenance overhead of a second package for one component. If that changes, revisit an optional peerDependency split.

Meta tags

---
import OgMeta from '@vdaluz/astro-og-cards/OgMeta.astro';
---

<OgMeta
  title="Page title"
  description="Page description"
  image="https://example.com/og/page.png"
  url="https://example.com/page"
  siteName="Example Site"
/>

Emits the full og:*/twitter:* tag set (title, description, image, url, type, site name, twitter card, including twitter:image:alt). imageWidth/imageHeight default to 1200/630 (matching generateCard's defaults) but are optional props if your image is a different size.

For a blog post, pass type="article" plus publishedTime/modifiedTime (ISO 8601) to emit article:published_time/article:modified_time:

<OgMeta
  title={post.title}
  description={post.description}
  image={ogImageUrl}
  url={postUrl}
  siteName="Example Site"
  type="article"
  publishedTime={post.pubDate.toISOString()}
  modifiedTime={post.updatedDate?.toISOString()}
/>

Card generation

import { generateCard } from '@vdaluz/astro-og-cards';

const png = await generateCard(inlineStyledHtml); // Buffer, 1200x630 PNG

markup must use inline styles, not <style> blocks or CSS classes - satori-html (the HTML-to-Satori adapter) only reads inline styles. A hand-designed card with <style> + classes needs manual translation to inline styles first; see src/assets/fixtures/imperfectSystemsCard.ts for a worked example (imperfectsystems.com's real default card, translated).

Example

Real output from generateCard(imperfectSystemsCard), the fixture above, unmodified:

Example OG card generated by astro-og-cards

Bundles a default static (non-variable) font, Space Mono (OFL-licensed). Pass fonts in the options to override.

Known gotchas

  • Use sharp for SVG->PNG, not @resvg/resvg-js. resvg-js 2.6.2 (latest stable as of writing) native-panics (uncatchable Rust abort, not a JS exception) on Satori's feDropShadow/feGaussianBlur filter output - i.e. any box-shadow or text-shadow in the source markup. Confirmed via binary search against a real shadow-bearing design; independent of shadow color format (hex vs rgba()).
  • Variable fonts fail to parse. Satori's bundled font parser (@shuding/opentype.js) can't read variable-font files (e.g. macOS's system SF Mono, which has an fvar table). Always pass a static font weight, never a system font reference.
  • The bundled default font is embedded as base64 in src/lib/spaceMonoData.ts, not read from a sibling .ttf file at runtime. Confirmed via a real astro build: Vite/Rollup bundles this package's source into a new chunk file at a different physical location in the consumer's build output, so any import.meta.url-relative disk read breaks there (regardless of whether it's written as new URL(...) or a plain path.join - both are equally broken, since the problem is the module's code being relocated, not a specific path-construction pattern Vite's static analyzer happens to intercept). The raw .ttf files still live in src/assets/fonts/ for provenance/license visibility and to regenerate the base64 if the font is ever updated; they aren't imported by any code path.
  • satori-html is stale (last published Dec 2022) but works correctly against the current Satori API as of this writing - the smoke test (npm test) is the tripwire for a future break.
  • satori-html unconditionally trims every text node's value, so a literal space in a text node right before an inline-styled <span> is silently dropped (and a non-breaking space doesn't survive either - JS trim() treats it as whitespace too). Give the following span an explicit margin-left (or the preceding element a margin-right) instead of relying on a whitespace character between elements.

Contributing

Issues welcome. PRs by discussion - open an issue first for anything beyond a typo or docs fix.

License

MIT