Back to News & Insights
Web Development September 25, 2026 · 4 min read

Dynamic Open Graph Images in Next.js With ImageResponse (next/og)

Dynamic Open Graph Images in Next.js With ImageResponse (next/og) Every page on a site...

Dynamic Open Graph Images in Next.js With ImageResponse (next/og)

Every page on a site technically has an Open Graph image slot — most sites just leave it blank, or ship one generic banner image for the entire domain. Next.js's ImageResponse API (next/og) makes it cheap enough to generate a real, on-brand OG image per route instead, using JSX and inline styles, rendered to a PNG at request time (or at build time for static routes). No design tool, no manually exported PNGs sitting in /public.

Next.js's App Router treats opengraph-image.tsx as a special file, the same way it treats layout.tsx or page.tsx. Drop one next to a route's page.tsx and Next.js automatically wires up the right tags — you don't touch the metadata export for it at all.

A few things worth noting about what's actually happening here: It's just JSX, but not full CSS. ImageResponse uses Satori under the hood, which supports a meaningful-but-limited subset of CSS — flexbox layout works well, grid does not, and most modern layout primitives you'd reach for in a browser aren't available. Stick to flex containers and you'll avoid most of the friction. runtime = 'edge' matters. Rendering happens at request time by default, so you want this on the edge runtime for low latency rather than a cold Node serverless function. The exported size and alt aren't decorative — Next.js reads those exact exports to populate og:image:width, og:image:height, and og:image:alt automatically.

The example above is static — same image for every request, since nothing in it depends on route params. The actual payoff of this API shows up when the image reflects the specific page it's attached to: a blog post's title rendered onto the card, a product's name and price, a location page's city.

You do that the same way you'd make any other route segment dynamic — accept params:

Next.js generates a distinct image per slug automatically — no extra routing config needed, because opengraph-image.tsx inherits the dynamic segment from its parent route the same way page.tsx does. If a post's title is long, that's genuinely a layout problem, not just an aesthetic one — you'll want a font-size clamp or truncation, since Satori doesn't give you graceful text overflow handling for free the way a browser's text-overflow: ellipsis does.

For a route with a bounded, known set of params (a fixed list of locations, a small product catalog), you can pair this with generateStaticParams so the images get generated once at build time rather than on every request — worth doing if the underlying data doesn't change often, since edge-rendered images on every social-share request add up in both latency and function invocations for high-traffic pages.

Social platforms use whatever og:image a page declares (or nothing, if it's absent) when a link gets shared — Slack, Twitter/X, LinkedIn, iMessage previews all read it. A generic site-wide image is fine as a fallback, but a blog post that shows its own title in the preview card gets noticeably more clicks than one showing the same banner as every other page on the domain. It's a small thing that's easy to skip, and cheap enough with next/og that there's not much reason to.

Using this pattern on DevFixel's marketing site for the site-wide default image, with per-route dynamic versions being the natural next step for the blog and location pages.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation