How Next.js handles metadata
Next.js generates the <head> of each page at the framework level. You define metadata — including Open Graph tags — in your route files, and Next.js renders them into the final HTML. The Metadata API lets you set static values for pages that don't change, and async functions for pages where the tags need to reflect dynamic data (like a blog post title or a product name pulled from a database).
When it works correctly, the rendered HTML contains og:title, og:description, og:image, and og:url as static text in the <head> — readable by any crawler without executing JavaScript.
Run the free Open Graph Checker to see which tags are missing, invalid, or too small.
The dynamic-metadata pitfalls
Relative and build-time image URLs
The most common cause of broken previews in Next.js is og:image resolving to a relative URL.
If you set og:image to /og.png, the tag in the rendered HTML will contain /og.png — not https://yourdomain.com/og.png. Facebook, LinkedIn, and other platform crawlers fetch pages from their own servers. They cannot resolve a relative path to your domain, so the image fails to load silently.
The fix is to ensure og:image always contains a fully absolute URL. Next.js provides metadataBase specifically for this: set it to your production origin once, and the framework will prepend it to any relative image paths in your metadata exports. Without it, relative paths stay relative in the output.
Per-route metadata on dynamic routes
A Next.js app with dynamic routes — product pages, blog posts, user profiles — needs per-route metadata. A single set of static tags at the layout level will give every product page the same title, the same description, and often the same image.
For dynamic routes, the metadata function receives the route parameters and can use them to fetch the right data and return the correct tags for that specific page. Pages where this is missing will share whatever the parent layout defines, which is rarely specific enough.
Verifying per route
The common mistake is checking the source of the page in a browser. The browser source (view-source:) shows you the HTML your server returned — but social crawlers and link-preview tools make direct HTTP requests for that HTML. If your server returns different content for crawlers (server-side rendering) versus browsers (client-side hydration), the two views can diverge.
Paste the URL into the Open Graph Checker to see what a crawler actually receives. It makes the same kind of direct HTTP request that Facebook, LinkedIn, and Slack make, and it shows you the tags as they appear in the response — not as the browser has processed them.
Check both the static pages and a sample of your dynamic routes. Problems in dynamic routes often go unnoticed because they show up on individual pages rather than site-wide.
Common mistakes
- No
metadataBaseset — relative image paths stay relative in the output. SetmetadataBaseto your production origin. - Client-side
og:imageinjection — if your JavaScript adds or updates theog:imagetag after the page loads, crawlers will miss it. Open Graph tags must be in the server-rendered HTML. - Layout-level tags not overridden at the route level — confirm each dynamic route exports its own metadata, not just the layout's fallback.
- Dynamic
og:imagepointing to a build-time path — if your image URL is constructed from environment variables that resolve differently at build time versus runtime, confirm the final URL is correct in the live rendered HTML.
See og:image is missing for a diagnostic checklist if your image is not appearing in previews.