My homepage had a share image. Fourteen other pages on my site didn't, and nothing told me.
I only found out when I ran my own pages through a metadata checker I'd just built. Lighthouse hadn't flagged it: pages with no og:image still scored 100 for SEO.
The cause
Here's the setup almost every Next.js site starts with. The layout holds the shared tags, and each page adds its own title.
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
openGraph: {
type: "website",
siteName: "Example",
images: ["/og-image.jpg"],
},
};
// app/pricing/page.tsx
export const metadata: Metadata = {
title: "Pricing",
openGraph: { title: "Pricing", url: "/pricing" },
};
It reads as if the page adds to the layout's Open Graph tags. It doesn't. Next.js merges metadata shallowly, so nested fields like openGraph are "overwritten by the last segment to define them". The page's object replaces the layout's whole object.
That pricing page ships with no og:image, no og:type and no site name. The homepage usually looks fine because it never sets its own openGraph, which is why this is so easy to miss.
Why it stays hidden
Nothing fails. An openGraph object without images is valid, so my build gave no error or warning. The page renders and can rank as normal.
The only symptom is in a shared link, which appears without an image in chat apps and social feeds. And you probably test sharing with your homepage, the one page that works.
The fix
Keep the shared fields in one object and spread it into every openGraph. The Next.js docs suggest the same pattern.
// lib/metadata.ts
export const OG_DEFAULTS = {
type: "website" as const,
locale: "en_US",
siteName: "Example",
images: [{ url: "/og-image.jpg", width: 1200, height: 630, alt: "Example" }],
};
// app/pricing/page.tsx
export const metadata: Metadata = {
title: "Pricing",
openGraph: { ...OG_DEFAULTS, title: "Pricing", url: "/pricing" },
};
Put page-specific values after the spread, such as type: "article" on a blog post or a different image on a case study.
If one page deserves its own image, add an opengraph-image.jpg file to that page's folder. Next.js generates the tags for you, and file-based metadata takes priority over the metadata object.
X cards have a related trap
My layout also set twitter.title and twitter.description. Pages that didn't set their own twitter object inherited those, so every X card carried the homepage's title.
The fix was to keep only the card type in the layout. On Next.js 16, each page's X tags then took their title, description and image from that page's openGraph.
// app/layout.tsx
twitter: { card: "summary_large_image" },
Check your own site
Open a page, view its source and search for og:image. Don't stop at the homepage. Check one page from each route, because those are where the tag goes missing.
For a quicker check, paste the HTML into this free Next.js metadata checker. It lists what's missing and gives the Next.js fix for each tag.
The Open Graph protocol lists og:title, og:type, og:image and og:url as required. For the image, Meta recommends at least 1200 x 630 pixels. It also caches images by URL, so links shared before your fix may keep their old preview until they're refreshed.
Top comments (0)