How to set an OG image in Next.js

Last updated October 31, 2026 · Reviewed by Sumera Yaqoob

Next.js can emit excellent Open Graph metadata—but only if it appears in the first HTML response. Client-only useEffect meta tags fail the same way any SPA fails. This guide covers the App Router Metadata API (title, description, openGraph, twitter), the opengraph-image file convention, dynamic ImageResponse images, and how to verify the result. For a deeper dynamic-image walkthrough, see How to generate dynamic OG images.

If you are still on a pure client-side React app without Next, start with OG images for React SPAs.

What crawlers need from a Next.js page

Social crawlers fetch your URL and parse <meta property="og:…"> from the document. They do not wait for your React tree to hydrate. Confirm with View Source, then the Link Preview Checker.

Design the artwork in the OG Image Generator or export code-oriented patterns from Export OG image as code. Dimensions: OG image size.

Option 1: Metadata API (openGraph.images)

In a layout or page (App Router):

import type { Metadata } from "next";

export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),
  title: "Launch notes",
  description: "What shipped this week",
  openGraph: {
    title: "Launch notes",
    description: "What shipped this week",
    url: "https://example.com/blog/launch",
    type: "article",
    images: [
      {
        url: "/og/launch.png", // resolved against metadataBase → absolute
        width: 1200,
        height: 630,
        alt: "Launch notes cover",
      },
    ],
  },
  twitter: {
    card: "summary_large_image",
    images: ["/og/launch.png"],
  },
};

Official reference: generateMetadata / openGraph. For per-request titles, export generateMetadata instead of a static metadata object.

Critical: set metadataBase so relative image paths become absolute https:// URLs in the rendered tags.

Option 2: File convention opengraph-image

Place opengraph-image.png (or .jpg) in the route segment (for example app/blog/[slug]/opengraph-image.png). Next.js serves the file and emits the matching og:image tags automatically.

Docs: opengraph-image and twitter-image and Metadata and OG images.

Optional: opengraph-image.alt.txt for alt text. Pair with twitter-image when you want an X-specific crop.

Option 3: Dynamic ImageResponse

For per-post titles, add opengraph-image.tsx:

import { ImageResponse } from "next/og";

export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default async function Image({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const title = slug.replace(/-/g, " ");

  return new ImageResponse(
    (
      <div
        style={{
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          fontSize: 64,
          background: "#0f172a",
          color: "white",
          padding: 48,
        }}
      >
        {title}
      </div>
    ),
  );
}

Keep CSS within what ImageResponse supports (flexbox-oriented). Huge remote fonts or unsupported layout features fail at build or request time.

Pages Router note

On the Pages Router, use next/head in the page’s server-rendered output, or a document-level setup—not a client-only effect. Prefer migrating social metadata to the App Router Metadata API when you can.

Verify before you share

  1. Deploy or run production build locally.
  2. View Source → one absolute og:image.
  3. Open the image URL logged out.
  4. Run the Link Preview Checker.
  5. For Facebook/LinkedIn cache issues, use their debuggers after HTML is correct (Facebook, LinkedIn).
  6. For X-specific cards, see Twitter card not showing image.

Cross-check tags with the Meta Tag Generator and the OG tags cheat sheet.

Common mistakes

Mistake Fix
No metadataBase Set it on the root layout
Tags only in client components Move to metadata / file conventions
Relative CDN paths without host Absolute https:// URLs
200×200 favicon reused as OG Use ~1200×630
Assuming Facebook success proves X Set twitter:card correctly

Checklist

  • metadataBase configured
  • Absolute og:image in View Source
  • 1200×630 PNG/JPG (or generated equivalent)
  • Optional twitter:card=summary_large_image
  • Checker green before major campaigns

Astro teams can follow the same “HTML first” principle in How to set an OG image in Astro. Universal failures: og:image not showing.

Choosing among the three Next.js approaches

Pick the Metadata API when the image is a static file you already host and the title is known at build or request time through generateMetadata. Pick the opengraph-image file convention when each route segment should own a literal asset without hand-writing meta tags. Pick ImageResponse when the card must include a dynamic title or price that changes per slug.

Do not combine all three on the same route without checking View Source. Duplicate og:image tags are a frequent self-inflicted injury after a refactor. Prefer one clear ownership model per segment.

For marketing sites that only need a handful of share images, static PNGs in /public/og plus metadataBase are enough. For large blogs, dynamic image generation reduces designer bottlenecks but adds runtime or build complexity—budget for font loading and layout constraints inside ImageResponse.

When something fails in production only, compare next build output headers and the live HTML. Preview deployments sometimes use different absolute hosts; scrapers then store the wrong image URL. Align metadataBase with the hostname customers share.

Field notes from real share failures

Most broken previews are boring: a relative URL, a duplicate plugin tag, a cache that nobody re-scraped, or a file that is fine in Chrome but too heavy for a messenger. Resist the urge to rewrite the entire brand system until those basics are proven in View Source and a private window.

Write down the exact URL string you tested, including trailing slash and query parameters. Support threads go sideways when one person scrapes example.com/page and another shares example.com/page/. Treat the share string as part of the specification.

When you finally change artwork, change the path. Overwriting og.png in place is the most common reason a “fixed” card never appears. Pair the new file with a short note in your changelog so the next person knows which version is live.

Verification habits worth repeating

After every deploy that touches templates or SEO plugins, spot-check three URLs: the homepage, one article or product, and one utility page. Look for a single absolute og:image and open that file logged out. This ten-minute habit catches more regressions than redesigning share cards.

Document the owner of social tags for your stack—theme, plugin, or layout component—in the repository README. When something breaks, people should know which lever to pull first. Ambiguous ownership is how duplicate tags accumulate.

Finally, separate “works in our browser” from “works for crawlers.” If you only validate inside an authenticated preview session, you will ship failures to Facebook, LinkedIn, Slack, Discord, WhatsApp, and X without noticing until a campaign is live.

Frequently asked questions

Short answers from this guide.

How do I set an OG image in the Next.js App Router?

Use the Metadata API (openGraph.images), place an opengraph-image.png in the route segment, or generate a dynamic opengraph-image.tsx with ImageResponse. Image URLs must be absolute.

Why is my Next.js og:image relative and broken on Facebook?

Social crawlers need a full https URL. Set metadataBase on your root layout and pass absolute paths, or use the file-based opengraph-image convention which Next.js wires for you.

Can I generate OG images dynamically in Next.js?

Yes. Create opengraph-image.tsx that returns ImageResponse from next/og. Keep layouts simple (flexbox) and ship fonts the ImageResponse runtime supports.

Do I still need twitter:card tags?

For large cards on X, set twitter.card to summary_large_image (or rely on twitter-image conventions). Open Graph alone is not always enough for X.

Does client-side next/head fix social previews?

Not reliably. Crawlers read the initial HTML. Prefer Metadata API or static head output, not tags added only after hydration.

Related pages