Next.js OG Image Generator
Generates brand-consistent Open Graph images for Next.js projects by inferring design identity from existing project files, producing HTML layouts at 1200x630, converting them to PNG via the html2png.dev API, and wiring up the resulting metadata in the appropriate router format.
Supports:
- Next.js App Router
- Next.js Pages Router
When to Use
Activate this skill when the user asks to:
- Generate or batch-generate OG images or social preview images for a Next.js project
- Add
og:imageortwitter:imagemetadata to one or more pages - Create branded Open Graph assets aligned with an existing design system
- Audit which pages are missing social preview images
Do not use this skill for:
- Non-Next.js projects
- Dynamic OG images rendered at request time (e.g.,
@vercel/og/ImageResponse) - Animated or video-based social previews
Phase 1 — Infer Brand Identity
Before generating any image, inspect the following project files in order:
tailwind.config.*— color palette, font families, custom theme tokensapp/globals.cssorstyles/globals.css— CSS custom properties, base typographystyles/directory — any additional stylesheetscomponents/directory — sample components for visual density cuesdocs/design-system.md— explicit design system documentation, if presentpublic/og-images/— any existing OG images for visual reference
From these sources, infer the following properties:
| Property | Examples |
|---|---|
| Background color | #0a0a0a, #ffffff, hsl(220 14% 96%) |
| Accent / foreground colors | primary, secondary, border tokens |
| Font families | Inter, Geist, custom web fonts |
| Visual density | minimal / bold / editorial / product / technical |
| Brand tone | playful / serious / dark / light / corporate / artistic |
Fallback defaults when no design system is found:
- Background:
#ffffff - Text:
#0a0a0a - Font: Inter via Google Fonts CDN
- Layout: centered, minimal, high-contrast
Phase 2 — Discover Routes
Identify all static routes that need OG images.
App Router:
find app -type f \( -name "page.tsx" -o -name "page.ts" -o -name "page.jsx" -o -name "page.js" \)
Pages Router:
find pages -type f \( -name "*.tsx" -o -name "*.jsx" -o -name "*.ts" -o -name "*.js" \)
Skip the following:
api/routes- Dynamic segments:
[slug],[id],[...params], etc. - Special files:
_app,_document,404,500,error,loading,not-found
Derive the slug from the file path:
| File | Slug |
|---|---|
app/page.tsx | home |
app/about/page.tsx | about |
app/blog/page.tsx | blog |
Phase 3 — Identify Missing OG Images
For each route slug, check whether a PNG already exists at:
public/og-images/<slug>.png
List all slugs that are missing an OG image and confirm with the user before proceeding with generation.
Phase 4 — Generate HTML Template (1200x630)
For each missing slug, create a self-contained HTML file that renders the OG image layout.
Constraints:
- Fixed viewport:
1200px x 630px— set on the root element, not via<meta viewport> - Prefer Tailwind CDN for layout and utility classes; supplement with a
<style>block for brand-specific values - Fonts must be loaded via a
<link>tag pointing to a CDN (see CDN Resources below) - Typography sizing: headline 60–80px, subheadline 24–32px — both must remain legible at thumbnail size
- Avoid: CSS
filter,backdrop-filter, glow effects, glitch animations, excessive radial gradients - Respect the inferred color palette, font family, visual density, and brand tone
CDN Resources:
Tailwind (utility classes, no build step):
<script src="https://cdn.tailwindcss.com"></script>
Fonts (swap for the project's inferred typeface):
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
Icons (Lucide — optional, for decorative elements):
<script src="https://unpkg.com/lucide@latest"></script>
Save each file to:
keep/og-images/<slug>.html
Phase 5 — Convert HTML to PNG
Send the HTML file to the html2png.dev API:
curl -s -X POST \
"https://html2png.dev/api/convert?width=1200&height=630&format=png&deviceScaleFactor=2&delay=1000" \
-H "Content-Type: text/html" \
--data-binary @keep/og-images/<slug>.html
The API returns a JSON response. Extract the url field — this is the temporary PNG URL used in the next phase.
Note: the html2png.dev free tier is limited to 50 requests per hour per IP. When generating images for many routes in a single session, batch them or space requests accordingly.
Phase 6 — Download and Save PNG
mkdir -p public/og-images
curl -L -o public/og-images/<slug>.png "<url-from-phase-5>"
Phase 7 — Wire Up Metadata in Next.js
App Router — add to app/<route>/page.tsx or the nearest layout.tsx:
export const metadata = {
openGraph: {
images: [{ url: "/og-images/<slug>.png", width: 1200, height: 630 }],
},
twitter: {
card: "summary_large_image",
images: ["/og-images/<slug>.png"],
},
};
Pages Router — add inside the component's <Head>:
import Head from "next/head";
// Inside your component:
<Head>
<meta property="og:image" content="/og-images/<slug>.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="/og-images/<slug>.png" />
</Head>
Output File Structure
project/
├── keep/
│ └── og-images/
│ └── <slug>.html # HTML source — commit for reproducibility
├── public/
│ └── og-images/
│ └── <slug>.png # Generated PNG — served as a static asset
Completion Report
After each image is generated, report the following:
| Field | Value |
|---|---|
| Slug | e.g. about |
| HTML source | keep/og-images/about.html |
| PNG output | public/og-images/about.png |
| Background color | inferred value |
| Accent color | inferred value |
| Font family | inferred value |
| Visual tone | inferred value |
Remind the user to verify that the metadata export (App Router) or <Head> tags (Pages Router) are present in every relevant page file.
Common Failure Modes
html2png.dev returns an error
- Confirm the HTML is valid and self-contained
- Ensure font
@importURLs are reachable from a public server - Remove any
localhostor relative asset references
PNG renders blank or misaligned
- Verify the root element has
width: 1200px; height: 630pxset explicitly - Check that no ancestor element uses
overflow: hiddenin a way that clips content
Fonts not rendering correctly
- Confirm
delay=1000is present in the API URL — this gives the browser time to load web fonts before capture - Add
font-display: swapto any@font-facedeclarations - Include a system font stack as a fallback (
-apple-system, BlinkMacSystemFont, sans-serif)
OG image not appearing on social platforms
- Confirm the image URL is publicly accessible without authentication
- In production, use an absolute URL (
https://yourdomain.com/og-images/<slug>.png) forog:image - Use the Open Graph Debugger or Twitter Card Validator to flush platform caches