og-image
Dynamic OG image generation and the full meta tag stack — one route, all pages covered. Your links stop looking broken when someone shares them.
Phase 1: Detect the Stack
Check the codebase:
- Framework: Next.js App Router / Pages Router / Astro / Remix / static HTML?
- Existing meta tags: Search for
og:image,twitter:cardin layout files - Dynamic pages: Blog posts, product pages, skill pages — anything that needs per-page OG?
If meta tags already exist, audit them before changing anything.
Phase 2: Framework-Specific Setup
Next.js App Router (recommended path)
Create app/api/og/route.tsx:
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || '[Project Name]';
const description = searchParams.get('description') || '[One-line description]';
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#09090b',
color: '#fafafa',
fontFamily: 'system-ui, sans-serif',
padding: '60px',
}}
>
<div
style={{
fontSize: 64,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
lineHeight: 1.1,
}}
>
{title}
</div>
<div
style={{
fontSize: 28,
opacity: 0.55,
textAlign: 'center',
maxWidth: '80%',
lineHeight: 1.4,
}}
>
{description}
</div>
</div>
),
{ width: 1200, height: 630 }
);
}
Add to app/layout.tsx — read the actual project name and description from package.json, README, or landing page copy:
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL || 'https://yourdomain.com'),
openGraph: {
title: '[Project Name]',
description: '[One-line description]',
images: [{
url: '/api/og?title=[Project Name]&description=[Description]',
width: 1200,
height: 630,
}],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: '[Project Name]',
description: '[One-line description]',
images: ['/api/og?title=[Project Name]&description=[Description]'],
},
};
For dynamic pages (blog posts, skill pages, product pages), override per page:
// app/[slug]/page.tsx
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const item = getItem(slug);
if (!item) return {};
const ogUrl = `/api/og?title=${encodeURIComponent(item.title)}&description=${encodeURIComponent(item.description)}`;
return {
title: `${item.title} — [Site Name]`,
description: item.description,
openGraph: {
images: [{ url: ogUrl, width: 1200, height: 630 }],
},
twitter: {
images: [ogUrl],
},
};
}
Next.js Pages Router
Create pages/api/og.tsx with the same ImageResponse logic.
Add meta tags via next/head in _app.tsx or per-page with <Head>.
Astro
Install: npm install satori @resvg/resvg-js
Create src/pages/og/[...slug].png.ts as an endpoint that uses satori to generate a PNG buffer and returns it with Content-Type: image/png.
Add meta tags in src/layouts/Layout.astro in the <head> section.
Static HTML
Generate a static og-image.png (1200×630) once, and reference it:
<meta property="og:image" content="https://yourdomain.com/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
For static sites with many pages, consider generating per-page OG images at build time.
Phase 3: Required Meta Tags
Every page needs these. Inject them in the base layout:
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="[Canonical page URL]" />
<meta property="og:title" content="[Page title]" />
<meta property="og:description" content="[Page description — 1-2 sentences]" />
<meta property="og:image" content="[OG image URL — absolute, not relative]" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<!-- Twitter / X -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="[Page title]" />
<meta name="twitter:description" content="[Page description]" />
<meta name="twitter:image" content="[OG image URL — must match og:image]" />
Common mistakes that break previews:
- Using relative URLs for
og:image— must be absolute (https://...) - Setting
og:imagebut nottwitter:image— Twitter ignoresog:image - Missing
og:image:widthand:height— causes slow rendering and sometimes no preview - Not setting
metadataBasein Next.js — all relative URLs become broken
Phase 4: Design Rules
The image renders at 1200×630 on desktop and gets thumbnail-cropped on mobile. Design for both:
- Text must be readable at 300px wide — that's how it looks in a Slack/Twitter feed
- Keep all content within center 80% — platforms crop the edges unpredictably
- Dark background preferred — stands out in light-mode feeds
- Title: 48-64px bold — readable at thumbnail size
- Description/subtitle: 24-32px, lower opacity — supporting context
- Minimum 4.5:1 contrast ratio — both light and dark mode platforms
Phase 5: Verify
Paste your URL into these tools after deploying. Don't skip this — meta tags look correct in code but break in practice more often than you'd expect:
- Twitter Card Validator: cards-dev.twitter.com/validator
- LinkedIn Post Inspector: linkedin.com/post-inspector/
- Facebook Debugger: developers.facebook.com/tools/debug/ (also works for WhatsApp)
- Quick check: paste URL in any Slack channel
[ ] OG image route returns valid image at /api/og (or equivalent)
[ ] og:title, og:description, og:image all set in base layout
[ ] og:image:width and og:image:height set
[ ] twitter:card set to summary_large_image
[ ] twitter:image set (separate from og:image — both required)
[ ] og:image URL is absolute, not relative
[ ] Dynamic pages have per-page og:image with correct title param
[ ] Image readable at 300px thumbnail width
[ ] Verified in Twitter Card Validator or LinkedIn Inspector
More from tushaarmehtaa/tushar-skills
ship-credits
Scaffold a complete credits/token metering system for any app — database schema, backend middleware, payment webhooks, frontend state, and UI components. Goes from zero to "users can buy and spend credits" in one session.
17ship-email
Scaffold transactional and campaign email infrastructure end-to-end — provider setup, templates, user segmentation, and admin send UI. Use when the user wants to add email to their app — welcome emails, notifications, re-engagement, or bulk campaigns. Triggers on requests like "add email", "set up Resend", "email campaigns", "transactional email", "send emails to users", "welcome email", "notification emails", or any mention of email sending in an app context.
6make-skill
Turn any workflow into a properly structured Claude Code skill — YAML frontmatter, phase-based instructions, real code blocks, and a verify checklist. Use when the user wants to package a repeated workflow, create a new skill, turn a process into a slash command, or publish to the skills directory. Triggers on requests like "make a skill", "create a skill", "turn this into a skill", "new skill for...", "package this as a skill", "build a skill", "I want to publish a skill", "help me write a skill", or any request to create a reusable Claude Code skill.
6mvp-spec
Turn a rough product idea into a structured MVP spec — problem statement, personas, core loop, feature split, data model, API routes, page list, and tech stack recommendation. Write this before touching any code. Triggers on requests like "spec this out", "MVP spec", "plan this product", "what should I build first", "scope this idea", "PRD", "product spec", "write a spec for...", "help me plan this", "what do I build in v1", "product requirements", or any request to structure a product idea before writing code.
6segment-users
Read your database schema, generate behavioral user segments with exact queries, and recommend targeted actions per segment. Use when the user wants to understand their user base, find power users, identify churn risk, build email cohorts, or understand usage patterns. Triggers on requests like "segment users", "who are my power users", "find churned users", "user cohorts", "churn analysis", "inactive users", "behavioral segmentation", "who's about to leave", or any mention of grouping users by activity, usage, or lifecycle.
6aeo-ready
Full Answer Engine Optimization audit — make your project discoverable by AI search engines (ChatGPT, Perplexity, Claude, Google AI Overviews), not just Google. Use when the user wants to improve their site's visibility in AI-powered search, add structured data, create llms.txt, optimize for AI citations, or audit AEO readiness. Triggers on requests like "AEO audit", "AI search optimization", "make my site visible to AI", "structured data", "schema markup", "llms.txt", "answer engine", "AI discoverability", or any mention of being found by AI search engines or chatbots.
6