Open Graph metadata in Next.js controls how a link to your site looks when it is pasted into a chat app, a social feed, or a preview card. You declare it through the openGraph field of the App Router Metadata API, and the twitter field covers the cases where one platform needs different values. Next.js renders the tags for you.
Everything here was verified against Next.js 16.3 with the App Router, reading the tags from a production build rather than the development server.
Declare the shared values once
Site-wide values belong in the root layout, because every route inherits fields it does not define. The site name, the type, and the default image rarely change per page.
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://acme.com'),
description: 'Tools for small workshops.',
openGraph: {
siteName: 'Acme Store',
type: 'website',
images: '/og-default.png',
},
}The home page now renders the site name, the type, and an image URL expanded to the absolute form. Add the locale field next to them when your audience is not English-speaking by default. The relative path works because the base URL is set in the same object, which the article on canonical URLs and metadataBase explains in more depth.
The fallback rule is worth knowing. As long as an Open Graph object exists somewhere in the segment chain, a route that sets no Open Graph title inherits the resolved document title, template included, so a post page renders an Open Graph title such as "Hello World | Acme Store". A route with no Open Graph object at all renders no og tags.
Let Next.js fill in the Twitter tags
Declaring the twitter field is optional. With only Open Graph metadata present, Next.js still renders Twitter tags derived from it, and picks a card type based on whether an image resolved.
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Launch day | Acme Store" />
<meta name="twitter:description" content="Tools for small workshops." />
<meta name="twitter:image" content="https://acme.com/og-news.png" />That output came from a page that declared no twitter field at all. The card is the large image variant because an image resolved, and it would have been the plain summary card if none had.
Add the field when you need a platform-specific value, such as an account handle or a shorter title.
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL('https://acme.com'),
description: 'Tools for small workshops.',
openGraph: { siteName: 'Acme Store', type: 'website', images: '/og-default.png' },
twitter: { card: 'summary_large_image', creator: '@acme' },
}The twitter field sits beside openGraph in the same export, so the creator handle now appears on every route. Anything you leave out of this object keeps coming from the Open Graph values. Setting a title here overrides only the Twitter title, leaving the Open Graph one alone.
Per-route values and the merge trap
Route-specific Open Graph metadata is where most link previews break. Merging between segments is shallow, so a nested object defined in a child replaces the parent object completely.
// app/about/page.tsx
export const metadata = {
title: 'About',
openGraph: { title: 'About Acme' },
}This page renders an Open Graph title, and it no longer renders the site name or the default image, because the parent object was replaced rather than extended. The description survives only because it falls back to the top-level description field.
Adding one Open Graph field to a page can remove every inherited Open Graph field. Read the rendered tags for that route after the change instead of assuming a deep merge.
Two fixes exist. Put the shared fields in a module and spread them into each page's object, or build the metadata inside generateMetadata and extend the parent value through its second argument.
Images, dimensions, and alt text
An image entry can be a string or an object. The object form is worth the extra lines because the dimensions and alt text end up in the tags, which is what the platforms use for layout and accessibility.
// app/news/page.tsx
export const metadata = {
openGraph: {
images: [
{ url: '/og-news.png', width: 1200, height: 630, alt: 'Launch day' },
],
},
}The rendered head now contains the image URL, width, height, and alt text, and the same image is mirrored into the Twitter tags. Crawlers use the first usable image in the list, so order matters when you supply more than one.
There is one interaction with the file conventions to remember. An image file placed in a segment supplies these tags automatically, and it wins over an inherited parent image, but an explicit images value in that same segment wins over the file. The details of that convention are covered in the opengraph-image file convention.
Article metadata for content pages
Setting the Open Graph type to article unlocks fields that describe publication, which some platforms display and which is generally useful structured signal.
// app/news/[slug]/page.tsx
import { getPost } from '@/app/lib/posts'
export async function generateMetadata({ params }: PageProps<'/news/[slug]'>) {
const { slug } = await params
const post = await getPost(slug)
return {
openGraph: {
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author],
},
}
}Next.js renders these as article-namespaced tags, so the published time becomes article:published_time and each author becomes an article:author tag, one per entry. The values come from the record rather than from a literal, which is why this belongs in the metadata function on a route with a dynamic segment.
Keep the type honest. A marketing page that claims to be an article gains nothing, and a content page left as the default website type loses the publication signal.
The modified time field is the one worth adding next, because it changes what a platform shows for updated posts. Set it only when you actually track edits, since a modified time that equals the published time on every record is noise.
Verify with the rendered tags
Preview cards fail in ways that are invisible locally, usually because an image URL is not absolute or an inherited value was dropped. Reading the response settles both questions in one command.
npx next build && npx next start
curl -s http://localhost:3000/news | grep -o '<meta property="og:[^>]*>'Look for three things: an absolute image URL on your production domain, the site name still present, and the type you intended. After that, run the URL through the platform's own card debugger, since each one caches previews and applies its own rules.
The SEO checklist for the App Router covers where this fits among the other metadata files worth shipping before a launch.
One route is rarely enough to check. Test the home page, one content page with its own image, and one page that overrides part of the Open Graph object, because those three exercise inheritance, replacement, and the automatic Twitter fallbacks.
Rune AI
Key Insights
- The openGraph field renders the og tags, including image dimensions and alt text.
- Twitter tags are filled from Open Graph values unless you set the twitter field explicitly.
- The card type defaults to the large image card when an image resolves, and to summary when none does.
- Defining openGraph in a child segment replaces the parent object, dropping the site name and default image.
- An image file convention in a segment wins unless that same segment sets openGraph.images itself.
Frequently Asked Questions
Do I have to write the twitter field separately?
Why did my site name disappear on one page?
Do Open Graph image URLs have to be absolute?
What image size should I use?
Conclusion
Open Graph metadata is the source of truth for link previews in Next.js, and the Twitter fields exist only for the cases where a platform needs a different value. Declare the shared parts once in the root layout, override deliberately per route, and remember that a nested object replaces its parent rather than merging with it.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Next.js SEO Guide for the App Router: Complete Checklist
A practical Next.js SEO checklist for the App Router: metadata baseline, canonical URLs, sitemap and robots files, social images, indexing control, and how to verify the rendered HTML.