nextjs-og-image
npx skills add https://github.com/oscarangulo/skills --skill nextjs-og-image
Agent 安装分布
Skill 文档
Next.js OG Image
Generates production-quality dynamic OG images using next/og (Satori under the hood) and wires up all necessary metadata. Covers multi-domain support, image rendering gotchas, and SEO best practices.
Workflow
1. Audit existing setup
Read:
app/layout.tsxâ check formetadatavsgenerateMetadata(),metadataBase, icon configapp/opengraph-image.tsxâ if it exists, look for Satori gotchas (seereferences/satori-gotchas.md)public/â find the logo file and note its exact pixel dimensions (usepython3 -c "import struct; d=open('public/images/logo.png','rb').read(); w,h=struct.unpack('>II',d[16:24]); print(f'{w}x{h}')")
2. Create app/opengraph-image.tsx
Use the template in assets/og-image-template.tsx as the starting point.
Key rules (see references/satori-gotchas.md for full list):
- Set
export const runtime = "nodejs"to allowfsreads for the logo - All image dimensions must be explicit integers â never
"auto","100%", ormaxWidth - Multi-color text: use separate
<span>elements in a flex column, never inline spans inside<h1>text nodes - Every element needs
display: "flex"â Satori ignoresblock,inline, etc.
3. Configure generateMetadata() for multi-domain
When the project serves multiple domains (e.g. example.com + example.cl):
// app/layout.tsx
import { headers } from 'next/headers'
function getSiteUrl(headersList) {
const forwardedHost = headersList.get('x-forwarded-host') // Vercel proxy sets this
const host = forwardedHost ?? headersList.get('host') ?? null
if (host) {
const protocol = host.startsWith('localhost') ? 'http' : 'https'
return `${protocol}://${host}`
}
return process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com'
}
export async function generateMetadata(): Promise<Metadata> {
const headersList = await headers()
const siteUrl = getSiteUrl(headersList)
return {
metadataBase: new URL(siteUrl),
openGraph: {
images: [{ url: `${siteUrl}/opengraph-image`, width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
images: [`${siteUrl}/opengraph-image`],
},
}
}
Why: Hardcoding
metadataBaseto one domain breaks OG image URLs when the same deployment serves multiple domains.x-forwarded-hostis set by Vercel and reflects the original domain the user hit.
4. Title and description length
| Field | Optimal | Hard limit |
|---|---|---|
<title> |
50â60 chars | 70 |
og:description / meta description |
110â160 chars | 200 |
twitter:description |
60â100 chars | 200 |
Measure with: "Your title here".length
5. Favicon / icons config
icons: {
icon: [
{ url: '/ico/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/ico/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
{ url: '/ico/favicon.ico', sizes: 'any' },
],
apple: '/ico/apple-touch-icon.png',
},
manifest: '/ico/site.webmanifest',
Update site.webmanifest with real name, short_name, brand theme_color, and start_url: "/".
6. Verify build
./node_modules/.bin/next build
Check that /opengraph-image appears as â (Static) in the route table. If it appears as Æ (Dynamic), remove any headers() or cookies() calls from the image file.
References
references/satori-gotchas.mdâ Full list of Satori CSS limitations and fixes. Read this when the OG image renders incorrectly (missing elements, broken text, invisible images).assets/og-image-template.tsxâ Boilerplateopengraph-image.tsxto copy and customize.