Plugins
OG Images
/plugin-og-images generates Open Graph (social card) images for every content node at build time. Each page gets a 1200×630 PNG written to dist/assets/og/{slug}.png. A sidecar manifest at dist/data/og-images.json maps slugs to their public paths so your theme can inject <meta og:image> tags.
Images that already exist on disk are skipped on rebuild, so incremental builds are fast.
Installation
pnpm add /plugin-og-images
Basic setup
// stedefast.config.ts
import { defineConfig } from "@stedefast/core";
import { OgImagesPlugin } from "/plugin-og-images";
export default defineConfig({
// ...
plugins: [
OgImagesPlugin({
fonts: [
{ name: "Inter", path: "./theme/fonts/Inter-Bold.ttf", weight: 700 },
],
}),
],
});
This uses the built-in DefaultOgTemplate — a dark slate card with the page title in white and the site name at the bottom.
Writing a custom template
Templates are React components that receive page data as props:
// theme/og-template.tsx
import type { OgTemplateProps } from "/plugin-og-images";
export default function OgTemplate({
title,
description,
siteTitle,
date,
tags,
}: OgTemplateProps) {
return (
<div
style={{
display: "flex",
flexDirection: "column",
width: 1200,
height: 630,
background: "#0f172a",
padding: 80,
}}
>
<div style={{ fontSize: 64, fontWeight: 700, color: "white" }}>
{title}
</div>
{description && (
<div style={{ fontSize: 32, color: "#94a3b8", marginTop: 24 }}>
{description}
</div>
)}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-end",
marginTop: "auto",
}}
>
<div style={{ fontSize: 24, color: "#64748b" }}>{siteTitle}</div>
{date && (
<div style={{ fontSize: 24, color: "#64748b" }}>{date}</div>
)}
</div>
</div>
);
}
Register it in your config:
import OgTemplate from "./theme/og-template.tsx";
OgImagesPlugin({
template: OgTemplate,
fonts: [
{ name: "Inter", path: "./theme/fonts/Inter-Regular.ttf", weight: 400 },
{ name: "Inter", path: "./theme/fonts/Inter-Bold.ttf", weight: 700 },
],
})
Satori CSS limitations
Satori renders React elements via its own layout engine. It supports a subset of CSS — specifically the same constraints as React Native's flexbox layout. Key limitations:
- Inline styles only — no CSS classes, no
className, no external stylesheets - No
background-image— use a solidbackgroundcolour or gradient - Limited
position—relativeandabsolutework;fixedandstickydo not - No
overflow: scroll— useoverflow: hidden - Flexbox only — no CSS Grid
- No
calc()— use numeric pixel values - No pseudo-elements —
:hover,::before, etc. are ignored display: flexis required on container elements — block layout is not supported
For the full list, see the Satori documentation.
Template props reference
| Prop | Type | Description |
|---|---|---|
title |
string |
Page title from front matter |
description |
string | undefined |
Page description from front matter |
date |
string | undefined |
Publication date as "YYYY-MM-DD" |
tags |
string[] | undefined |
Tag array from front matter |
siteTitle |
string |
The site title from SiteConfig.siteTitle |
type |
string |
Content type, e.g. "post", "page" |
Using the default template
The built-in DefaultOgTemplate renders a dark slate card (#0f172a) with:
- The page
titlein large white text (64px, bold) - The
siteTitlein muted slate text at the bottom (28px,#94a3b8)
It works without any custom fonts — Satori's built-in fallback font is used if none are provided. For best results, supply a web font.
// Minimal setup — no template, no fonts
OgImagesPlugin()
Using generated images in your layout
The plugin writes a manifest to dist/data/og-images.json:
{
"hello-world": "/assets/og/hello-world.png",
"about": "/assets/og/about.png"
}
Read this file in your layout or template to inject the correct og:image meta tag. In a React template:
// theme/layouts/default.tsx
import ogImages from "../../dist/data/og-images.json";
export default function DefaultLayout({ page, site }) {
const ogImagePath = ogImages[page.slug];
const ogImageUrl = ogImagePath
? `${site.baseUrl}${ogImagePath}`
: undefined;
return (
<html>
<head>
<title>{page.frontMatter.title}</title>
{ogImageUrl && (
<>
<meta property="og:image" content={ogImageUrl} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImageUrl} />
</>
)}
</head>
<body>{/* ... */}</body>
</html>
);
}
Alternatively, use the static path pattern directly without reading the manifest:
const ogImageUrl = `${site.baseUrl}/assets/og/${page.slug}.png`;
This is simpler but will produce broken image links for pages where generation was skipped (e.g. if the plugin is not configured).
Font configuration
Satori requires font data to render text. Provide TTF or OTF files:
OgImagesPlugin({
fonts: [
// Regular weight for body text
{ name: "Inter", path: "./theme/fonts/Inter-Regular.ttf", weight: 400 },
// Bold weight for titles
{ name: "Inter", path: "./theme/fonts/Inter-Bold.ttf", weight: 700 },
// Italic variant
{
name: "Inter",
path: "./theme/fonts/Inter-Italic.ttf",
weight: 400,
style: "italic",
},
],
})
name— the font family name as used infontFamilystyle valuespath— path to the font file, relative to yourstedefast.config.tsweight— numeric weight (e.g.400,700)style—"normal"(default) or"italic"
You can download variable fonts from Google Fonts and use them as static font files.
Caching behaviour
The plugin skips generating an image if dist/assets/og/{slug}.png already exists on disk. This means:
- First build — all images are generated
- Subsequent builds — only new pages (or pages whose image was deleted) are generated
- To force regeneration — delete
dist/assets/og/or runstedefast build --clean
The build log shows the counts:
og-images 42 generated 8 cached (3.2s)
Config reference
| Option | Type | Default | Description |
|---|---|---|---|
template |
React.ComponentType<OgTemplateProps> |
DefaultOgTemplate |
React component to render as the OG image |
width |
number |
1200 |
Output image width in pixels |
height |
number |
630 |
Output image height in pixels |
fonts |
OgFontConfig[] |
[] |
Font files to load for Satori |
OgFontConfig
| Field | Type | Default | Description |
|---|---|---|---|
name |
string |
— | Font family name |
path |
string |
— | Path to font file, relative to config |
weight |
number |
— | Font weight (e.g. 400, 700) |
style |
"normal" | "italic" |
"normal" |
Font style |
How it works
OgImagesPlugin uses the buildHook extension point in the StedefastPlugin interface. This hook runs after Stage 2 (content graph) in the build pipeline, before the asset pipeline and page rendering. It receives the full ContentGraph, the resolved SiteConfig, and the output directory path.
For each content node, the plugin:
- Renders your template component to SVG using Satori
- Converts the SVG to PNG using @resvg/resvg-js
- Writes the PNG to
dist/assets/og/{slug}.png - Records the path in the
dist/data/og-images.jsonmanifest
Up to 4 images are generated concurrently to keep build times reasonable on large sites.