# How Open Graph Images Are Dynamically Generated Using Satori for Roadmap Previews

> Learn how the developer-roadmap repository dynamically generates Open Graph images using Satori. Convert HTML to SVG and PNGs for previews without a headless browser.

- Repository: [Kamran Ahmed/developer-roadmap](https://github.com/kamranahmedse/developer-roadmap)
- Tags: how-to-guide
- Published: 2026-02-24

---

**The developer-roadmap repository uses Satori to convert Tailwind-styled HTML templates into SVGs, then rasterizes them to PNGs for dynamic Open Graph previews without requiring a headless browser.**

The `kamranahmedse/developer-roadmap` project generates unique social sharing previews for every roadmap, best-practice guide, and article. Instead of maintaining thousands of static images, the codebase dynamically generates Open Graph images using Satori, a library that renders HTML-like components directly to SVG. This approach produces deterministic, high-quality previews during the build process while keeping the generation pipeline lightweight and fully server-side.

## The Satori-Based OG Image Pipeline

The generation logic lives in `scripts/generate-og-images.mjs`, which orchestrates a four-step transformation from markdown metadata to final PNG assets.

### Step 1: Extracting Metadata with gray-matter

The script first collects content metadata using the `getAllRoadmaps` function. It reads every markdown file in the repository, parsing front-matter fields—such as `title`, `description`, and optional `image`—with the **gray-matter** library.

```javascript
// From scripts/generate-og-images.mjs
async function getAllRoadmaps() {
  const roadmaps = await fs.readdir('./src/data/roadmaps');
  return Promise.all(roadmaps.map(async (slug) => {
    const content = await fs.readFile(`./src/data/roadmaps/${slug}/${slug}.md`, 'utf8');
    const { data } = matter(content); // gray-matter parsing
    return { slug, ...data };
  }));
}

```

### Step 2: Building Tailwind-Styled Templates

Rather than writing raw SVG markup, the pipeline uses **satori-html** to build type-safe HTML templates styled with Tailwind CSS utility classes. The script provides three template variants:

- `getRoadmapDefaultTemplate`: Text-only layout for roadmaps without custom cover images
- `getRoadmapImageTemplate`: Composite layout that embeds an existing cover image alongside text
- `getGuideTemplate`: Specialized layout for best-practice guides and articles

```javascript
// Template construction example
function getRoadmapDefaultTemplate({ title, description }) {
  return html`
    <div tw="bg-white flex flex-col h-full w-full p-[60px]">
      <div tw="text-[70px] font-bold text-gray-900 leading-tight">
        ${title}
      </div>
      <div tw="mt-[16px] text-[30px] text-gray-600 leading-relaxed">
        ${description}
      </div>
    </div>
  `;
}

```

### Step 3: Rendering SVG with Satori

The core transformation happens in the `generateOpenGraph` function. It passes the HTML template to **Satori** along with dimensions (1200×630px) and a custom font configuration. Satori returns a pure SVG string representing the visual layout.

```javascript
import satori from 'satori';

async function generateOpenGraph(htmlString, type, fileName) {
  const svg = await satori(htmlString, {
    width: 1200,
    height: 630,
    fonts: [
      {
        name: 'balsamiq',
        data: await fs.readFile('public/fonts/BalsamiqSans-Regular.ttf'),
        weight: 400,
        style: 'normal',
      },
    ],
  });
  
  // SVG is now ready for rasterization
  return svg;
}

```

### Step 4: Rasterizing to PNG with sharp or Resvg

The SVG string is converted to a PNG file using one of two renderers:

- **sharp** (default): High-performance Node.js image processing library
- **Resvg** (fallback): Used when content contains special characters that cause rendering artifacts in sharp

The final assets are written to `public/og-images/<type>/<id>.png`, making them available at predictable URLs like `https://roadmap.sh/og/roadmap/full-stack.png`.

```javascript
import sharp from 'sharp';
import { Resvg } from '@resvg/resvg-js';

async function rasterize(svg, type, fileName, renderer = 'sharp') {
  const outputPath = `public/og-images/${type}/${fileName}`;
  
  if (renderer === 'resvg') {
    const resvg = new Resvg(svg, { fitTo: { mode: 'width', value: 2500 } });
    await fs.writeFile(outputPath, resvg.render().asPng());
  } else {
    await sharp(Buffer.from(svg), { density: 150 })
      .png()
      .toFile(outputPath);
  }
}

```

## Handling Edge Cases and Special Characters

The pipeline includes defensive logic for HTML entities that break SVG generation. The `hasSpecialCharacters` function detects characters like `&`, `<`, and `>`, then unescapes the HTML string before passing it to Satori. This prevents malformed SVG output when roadmap titles or descriptions contain reserved HTML characters.

Additionally, if a markdown file specifies a custom cover image in its front-matter, the `getRoadmapImageTemplate` function embeds that image directly into the OG template using an `<img>` tag with a `tw` attribute for sizing, creating a composite preview rather than a text-only card.

## Runtime API Access and URL Generation

While the build script pre-generates images for static hosting, the repository also exposes runtime utilities in [`src/lib/open-graph.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/open-graph.ts). The `getOpenGraphImageUrl` helper constructs canonical OG image URLs based on resource type and ID, ensuring that `<meta property="og:image">` tags always point to valid assets.

For on-demand generation, the `/v1-open-graph` API endpoint leverages the same `getResourceOpenGraph` function from the library, allowing the server to generate fresh previews for newly created or updated content without requiring a full site rebuild.

## Summary

- **Satori** converts Tailwind-styled HTML templates into SVG at build time, eliminating the need for headless browsers.
- The pipeline in `scripts/generate-og-images.mjs` extracts front-matter with **gray-matter**, builds templates with **satori-html**, and rasterizes with **sharp** or **Resvg**.
- Custom fonts (Balsamiq Sans) and defensive logic for special characters ensure consistent, high-quality renders.
- Generated PNGs are stored in `public/og-images/` and served via predictable URLs constructed by [`src/lib/open-graph.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/open-graph.ts).

## Frequently Asked Questions

### What is Satori and why is it used for Open Graph images?

Satori is an open-source library developed by Vercel that converts HTML and CSS into SVG. It is used in the developer-roadmap repository because it renders vector graphics without requiring a headless browser like Puppeteer or Playwright. This makes the build process faster, more memory-efficient, and fully deterministic, which is ideal for generating thousands of static OG images during deployment.

### How does the pipeline handle markdown front-matter for image generation?

The script uses the **gray-matter** library to parse YAML front-matter from markdown files located in `src/data/roadmaps/`. It extracts fields like `title`, `description`, and optional `image` URLs. This metadata is then passed to template functions such as `getRoadmapDefaultTemplate` or `getRoadmapImageTemplate`, which construct the HTML layout that Satori will eventually render into an SVG.

### What happens when a roadmap title contains special characters like ampersands?

The `hasSpecialCharacters` function in `scripts/generate-og-images.mjs` detects HTML entities such as `&`, `<`, and `>`. When these characters are present, the pipeline unescapes the HTML string before passing it to Satori. If rendering issues persist with the default **sharp** rasterizer, the system falls back to **Resvg**, which handles complex Unicode and special-character edge cases more robustly when converting the SVG to PNG.

### Where are the generated Open Graph images stored and how are they served?

After rasterization, PNG files are written to `public/og-images/<type>/<id>.png` within the repository. The [`src/lib/open-graph.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/open-graph.ts) utility provides the `getOpenGraphImageUrl` function, which constructs canonical URLs like `https://roadmap.sh/og/roadmap/full-stack.png`. These URLs are embedded in `<meta property="og:image">` tags, allowing social platforms to fetch the pre-generated previews directly from the static file system or via the `/v1-open-graph` API endpoint for on-demand generation.