OG image generation
How Open Graph images are generated at build time using satori and sharp, composited onto a watercolor background.
Every published post has an Open Graph image, a 1200×627 PNG that appears when the URL is shared on LinkedIn, Slack, or in a browser preview. They’re generated at build time, not dynamically.
The pipeline
scripts/generate-og-images.cjs runs after a build. It processes four collections: articles, field-notes, seeds, and jottings.
For each post:
- Parse frontmatter to extract
title,description, anddraftflag - Skip draft posts
- Build a text overlay as a React-like element tree using satori (text → SVG)
- Composite the text SVG on top of a pre-rendered background PNG using sharp
- Save to
public/images/og/<collection>/<slug>.png
The background
scripts/og-bg.png is a pre-rendered 1200×627 PNG, a sage green wash with a watercolor leaf in the upper right. It’s generated once and reused for every image.
Text layout
// Inside buildTextOverlay()
{
type: 'div',
props: {
style: {
padding: '60px 80px',
display: 'flex',
flexDirection: 'column',
gap: '20px',
},
children: [
// Title: dark, large, Lora-style serif
// Description: muted, smaller
// Site label: "maaike.ai" bottom-left
]
}
}
Satori converts this element tree to an SVG. Sharp composites it over the background at full opacity.
Output path
public/images/og/articles/my-post-slug.png
public/images/og/field-notes/my-field-note.png
In BaseLayout.astro, the OG image meta tag uses:
const ogImage = `/images/og/${collection}/${slug}.png`;
const socialImage = ogImage
? new URL(ogImage, 'https://www.maaike.ai').href
: 'https://www.maaike.ai/images/og-default.png';
If no image exists (collections not in the generation script, or unpublished drafts), it falls back to og-default.png.
CopyCardImage component
CopyCardImage.astro (in the post footer share section) lets readers copy the OG image to their clipboard as a PNG, for pasting directly into LinkedIn without uploading. It reads the pre-generated file at the imagePath prop and uses the Clipboard API with a Canvas fallback.
Article images: split layout
When an article contains an image in its body, the OG card uses a split layout instead of the default watercolor background:
- Left panel (55%, 660px): sage green wash, wordmark top-left, title and description bottom-left
- Right panel (45%, 540px): white background, article image scaled to fit (no cropping)
How it works
The generator scans the article body for the first Markdown image (). If one is found, it calls generateSplitImage() instead of the default compositor:
- Render text panel (660×627) with sage green background via satori
- Resize article image to fit within 540×627 using
sharpfit: contain, white background - Composite both panels side by side onto a 1200×627 canvas
No frontmatter field needed. The image in the body drives everything.
Adding an image to an article
Articles created with /new-post include two Typora keys in their frontmatter:
typora-root-url: ../../../public
typora-copy-images-to: ../../../public/images/articles
When you drop or paste an image into Typora, it automatically copies the file to public/images/articles/ and inserts the reference as /images/articles/filename.jpg. On the next /publish run, the split OG card is generated automatically.
For existing articles: drop an image inline in the body using standard Markdown. The generator picks it up on the next publish.
When to regenerate
OG images need to be regenerated when:
- A post title or description changes
- A new post is published
The /publish skill handles this automatically as part of the publishing workflow.