OG images are one of those things that developers know matter but rarely get right. The meta tags feel simple enough, but platform-specific quirks, caching behavior, and framework conventions turn it into a surprisingly tricky problem.
This guide covers OG image implementation best practices for Next.js, Vercel, Astro, and vanilla HTML — with code examples you can use directly.
Next.js (App Router)
Next.js 13+ with the App Router has first-class support for Open Graph images through the Metadata API.
Static Metadata
Export a metadata object from your page.tsx or layout.tsx:
// app/blog/[slug]/page.tsx
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Your Article Title",
description: "A concise description for social sharing.",
openGraph: {
title: "Your Article Title",
description: "A concise description for social sharing.",
type: "article",
publishedTime: "2026-03-24",
images: [
{
url: "https://yoursite.com/og/article-slug.png",
width: 1200,
height: 630,
alt: "Description of the image",
},
],
},
twitter: {
card: "summary_large_image",
title: "Your Article Title",
description: "Description for Twitter/X.",
images: ["https://yoursite.com/og/article-slug.png"],
},
}Dynamic Metadata
For pages with dynamic content, use generateMetadata:
// app/blog/[slug]/page.tsx
import type { Metadata } from "next"
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: "article",
publishedTime: post.publishedAt,
images: [{ url: post.ogImage, width: 1200, height: 630 }],
},
}
}The opengraph-image Convention
Next.js supports a special file convention for OG images. Place a file named opengraph-image.png (or .jpg) in any route folder, and Next.js automatically generates the og:image meta tag:
app/
blog/
[slug]/
page.tsx
opengraph-image.png ← auto-detected
opengraph-image.png ← fallback for all pagesFor dynamic OG images, create opengraph-image.tsx and export a function that generates the image using @vercel/og or next/og:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og"
export const size = { width: 1200, height: 630 }
export const contentType = "image/png"
export default async function OGImage({
params,
}: {
params: { slug: string }
}) {
const post = await getPost(params.slug)
return new ImageResponse(
(
<div style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
padding: 60,
width: "100%",
height: "100%",
background: "linear-gradient(135deg, #0f0f0f, #1a1a2e)",
color: "#fff",
}}>
<h1 style={{ fontSize: 48, lineHeight: 1.2 }}>{post.title}</h1>
<p style={{ fontSize: 24, color: "#888" }}>{post.excerpt}</p>
</div>
),
{ ...size }
)
}Vercel OG (@vercel/og)
Vercel's @vercel/og library generates images at the edge using Satori (JSX-to-SVG) and Resvg (SVG-to-PNG). It is the standard approach for dynamic OG images on Vercel.
Key Constraints
- Uses a subset of CSS (flexbox only, no grid, no
position: absolutein some contexts) - Custom fonts must be loaded explicitly (no system fonts)
- The
twprop enables Tailwind-like utility classes - Images must be passed as ArrayBuffers or base64, not URLs (in Edge Runtime)
Performance Tips
- Cache the font loading outside the handler so it is not re-fetched on every request
- Keep the JSX simple — complex layouts increase render time
- Use
Cache-Controlheaders to cache generated images at the CDN edge
Astro
Astro does not have a built-in OG image convention, but you can set meta tags in your layout's <head>:
---
// src/layouts/BlogPost.astro
const { title, description, ogImage } = Astro.props
const canonicalUrl = new URL(Astro.url.pathname, Astro.site)
---
<html>
<head>
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={ogImage} />
</head>
<body>
<slot />
</body>
</html>For dynamic OG image generation in Astro, you can create an API route that uses @vercel/og or satori directly if deploying to Vercel, or use a build-time script for static sites.
Vanilla HTML / Static Sites
For static sites, add the meta tags directly in your <head>. See the Open Graph Meta Tags Cheat Sheet for a complete copy-paste template.
The most common mistake with static sites is using relative image paths. OG images must be absolute URLs:
<!-- Wrong -->
<meta property="og:image" content="/images/og.png" />
<!-- Correct -->
<meta property="og:image" content="https://yoursite.com/images/og.png" />Image Optimization Best Practices
Dimensions
- Standard:1200 × 630 pixels (1.91:1 ratio)
- Minimum:600 × 315 pixels (platforms may not render large cards below this)
- Maximum:Keep under 4096 × 4096 for Twitter/X
For platform-specific dimensions, see the OG Image Size Guide.
File Format and Size
- PNG for images with text, logos, or sharp edges
- JPEG for photographs or complex images (smaller file size)
- Under 1 MB is ideal; platforms may time out on larger files during scraping
- Under 5 MB is the hard limit for Twitter/X
Safe Zones
Keep important content (text, logos) away from the edges. Different platforms crop differently, and some add rounded corners or overlays. A safe zone of at least 60 pixels from each edge ensures nothing gets cut off.
Platform Caching and Cache Busting
Social platforms cache OG data aggressively. When you update your OG image, the old one may persist for hours or days. Each platform has a tool to force a refresh:
- Facebook:Sharing Debugger — paste your URL and click “Scrape Again”
- Twitter/X: Card Validator
- LinkedIn: Post Inspector
For programmatic cache busting, append a version query parameter to your image URL: og-image.png?v=2. Platforms treat this as a new URL and re-fetch the image.
See How to Test Your Open Graph Images for a detailed walkthrough of each platform's debugging tool.
Common Developer Mistakes
- Not specifying og:image dimensions. Without
og:image:widthandog:image:height, platforms must download the image first. This adds latency and can cause layout shifts in preview cards. - Using localhost or staging URLs. OG images are fetched by platform servers, not browsers.
localhost,127.0.0.1, and private staging URLs will not resolve. - Missing canonical URL. Without
og:url, platforms may split share counts across different URL variants of the same page. - Not testing in production. OG images work differently in development vs production because platform scrapers cannot access your dev server. Always test with the production URL.
- Forgetting twitter:card. Without this tag, Twitter/X falls back to the small
summarycard instead of the large image card, even ifog:imageis set.
Skip the Boilerplate — Generate with AI
If you are spending more time designing OG image templates than writing code, try generating them with AI instead. Pixola's OG Image Generatorcreates unique 1200 × 630 images from text input — no design tools needed.