Next.jsSeptember 29, 2026 · 7 min

Automatic Open Graph images in Next.js: better click-through without a designer

When a link to your site gets shared on LinkedIn, X, or Slack, the preview image is doing marketing whether you designed it or not. A missing or generic card gets scrolled past; a sharp, on-brand one earns the click. Doing this by hand for every page is impossible — so you generate them. Next.js makes it a build artifact. Here's how I wire it site-wide.

The ImageResponse API: OG cards as code

Next.js can render an image from JSX at the edge using the ImageResponse API. You write a component describing the card — title, eyebrow, brand mark, accent color — and Next.js turns it into a PNG for that route. It's the same mental model as a page, but the output is an image, so every page can have a tailored preview generated from its own data.

// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const size = { width: 1200, height: 630 };
export default async function OG({ params }) {
  const post = postBySlug((await params).slug);
  return new ImageResponse(
    (<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
        width: '100%', height: '100%', padding: 64, background: '#0B0B0F', color: '#fff' }}>
      <div style={{ fontSize: 28, color: post.accent }}>{post.tags[0]}</div>
      <div style={{ fontSize: 64, fontWeight: 700, lineHeight: 1.1 }}>{post.title}</div>
      <div style={{ fontSize: 28, opacity: 0.7 }}>aliasghar.me</div>
    </div>), { ...size });
}

Per-route, from the page's own data

Because the image reads the same data the page does (title, tag, accent), it stays correct automatically — rename a post and its card updates on the next build. Pair the file with metadata that points at it, and every page ships a unique, accurate preview with zero manual design work per page.

One template + per-page data is the same principle as programmatic SEO: you design the system once, and every page gets a correct, distinct artifact for free. This site generates every OG card this way.

The gotchas worth knowing

  • The renderer supports a subset of CSS — use flexbox layout; every element with children needs an explicit display:flex.
  • Fonts must be loaded explicitly (fetch the font file and pass it in) — you can't rely on system fonts for consistency.
  • Keep the design legible at small sizes; the card is usually seen as a thumbnail, so big type and high contrast win.
  • Cache the output — these are static per route, so they generate at build and serve as plain images.

Key takeaways

  • Generate OG images from JSX with the ImageResponse API — one template, per-route data.
  • Cards read the page's own data, so they stay accurate automatically.
  • Mind the constraints: flexbox-only layout, explicit fonts, legible-at-thumbnail design.
  • A blank or generic preview wastes every share; automated branded cards recover that click-through.

FAQ

How do I add dynamic social share images in Next.js?

Add an opengraph-image file to the route and render your card from JSX with the ImageResponse API from next/og. It reads the page's own data (title, tag, accent) so each URL gets a unique, accurate PNG generated at build time, with no per-page design work.

Why does my Next.js OG image render blank or wrong?

Usually a CSS or font issue: the renderer supports only a subset of CSS and needs every element that has children to set display:flex, and it can't use system fonts — you must fetch and pass the font explicitly. Stick to flexbox layout and load fonts, and it renders reliably.

AA
Ali Asghar

Senior software engineer & technical lead — 6+ years shipping production multi-tenant SaaS, payments and AI integration in Next.js, Node & TypeScript.

Keep reading
Fixing Core Web Vitals in Next.js: a performance playbook that moves conversions →Programmatic SEO in Next.js: generating hundreds of landing pages the right way →The Next.js technical SEO checklist for SaaS that actually ranks →