In the Next.js App Router, a layout.tsx exports a metadata object with a title and an openGraph block, and the page.tsx nested below it exports metadata with only a title. What ends up in the rendered head?
answer
- deepest segment wins per key
- merge is one level deep
- nested block replaced, not filled in
- the missing preview image
- template applies to children, not self
basics
~20 sNext merges metadata down the segment tree shallowly. The page's title wins, and the layout's openGraph block survives untouched only because the page did not define one — had the page exported its own openGraph, it would replace the parent's object entirely rather than merge field by field.
solid answer
~50 sMetadata is evaluated from the root layout down to the matched page and merged **shallowly**, so the deepest segment that defines a key wins for that key. Here the page defines `title`, so its title is rendered; it defines no `openGraph`, so the layout's `openGraph` block is kept as-is. The trap is what happens one step further: shallow merge means whole top-level objects are replaced, not deep-merged. If the page exported `openGraph: { title: 'Page' }`, the layout's `openGraph.images`, `siteName` and everything else would vanish, because the child's object replaces the parent's rather than filling in around it. The practical fixes are to define shared fields with `title.default` and `title.template` in the layout, and to repeat or factor out the shared `openGraph` fields wherever a child overrides that block. Use `generateMetadata` instead of the static object when the values depend on fetched data.
code
typescript · 5 lines// app/blog/layout.tsx
export const metadata = {
title: { default: 'Blog', template: '%s | Acme Blog' },
openGraph: { siteName: 'Acme', images: ['/og-default.png'] },
};go deeper
Know that layouts and pages export metadata, that Next builds the head from those exports, and that a page's value overrides the layout's for the same field.
Explain that the merge is shallow: the deepest segment wins per top-level key, and defining a nested block such as openGraph replaces the parent's block instead of filling in around it. Know when to reach for generateMetadata.
Diagnose the real symptom — link previews losing their image on deep pages — and design against it with title.template and shared metadata factored into one place. Know that these exports only work in Server Components.
Set the pattern for the whole site: where the canonical defaults live, how sections override them without losing shared fields, and how metadata correctness is checked before it reaches production rather than after someone shares a link.
## How Next assembles the head Every layout and page in the App Router may export metadata in one of two forms: a static `metadata` object, or an async `generateMetadata` function for values that depend on data. Next walks the matched route from the root layout down to the page, collects those exports, and produces one head. Nothing you write in the head by hand participates in this — the exports are the mechanism. ```typescript // app/blog/layout.tsx export const metadata = { title: 'Blog', openGraph: { siteName: 'Acme', images: ['/og-default.png'] }, }; // app/blog/[slug]/page.tsx export const metadata = { title: 'Hello world', }; ``` The result: `title` is `Hello world` — the deeper segment wins — and the `openGraph` block from the layout is emitted unchanged, because the page never mentioned it. ## Shallow, not deep The merge operates on top-level keys only. A key defined lower in the tree **replaces** the value above it; it does not recursively fill in around it. That is invisible for a scalar like `description`, and expensive for an object like `openGraph`, `twitter` or `robots`. ```typescript // app/blog/[slug]/page.tsx — the trap export const metadata = { openGraph: { title: 'Hello world' }, }; // Rendered openGraph now has ONLY title. // siteName and images from the layout are gone. ``` The symptom in production is a link preview that loses its image on article pages while the homepage keeps it. The team added an article-specific Open Graph title and silently dropped the inherited fields. Whenever a child overrides a nested block, it must restate every field of that block it still wants — or you factor the shared fields into a helper the child spreads in. ## title is its own small system `title` accepts a string, or an object with `default`, `template` and `absolute`: - `title.default` — used by **child** segments that define no title of their own. - `title.template` — a pattern such as `'%s | Acme'` applied to titles from child segments. It does not apply to the segment that declares it; that segment needs `default` (or a plain string) for its own page. - `title.absolute` — a child opting out of the parent's template entirely. This is the intended way to get consistent titles across a section without repeating the suffix in every page. ## Static object or generateMetadata When the values depend on data — an article's real title, a product's image — export `generateMetadata` instead. It receives the same `params` the segment gets and returns the metadata object, and it may be async. Two rules matter: - **You cannot export both `metadata` and `generateMetadata` from the same route segment.** Pick one per segment. Different segments in the same tree may use different forms. - **Both are supported only in Server Components.** A page or layout marked `'use client'` cannot contribute metadata; move the export to a Server Component parent, or keep the segment on the server and push the interactive part into a child. ## Debugging it When the head is wrong, work top-down rather than guessing. List every segment on the matched route, note which of them export metadata, and remember that the last one to define a given top-level key wins and that defining a nested object at all discards the parent's version of it. Most "metadata is not inherited" bug reports are shallow-merge behaviour working exactly as documented. ## What an interviewer is checking Anyone can say "child overrides parent". The signal is knowing that the override is per top-level key and destroys the rest of a nested block, and being able to name the two consequences that follow: restate shared `openGraph` fields when you override, and use `title.template` rather than repeating a suffix. Mentioning that these exports are Server-Component-only shows you have hit the failure where a `'use client'` page's metadata quietly does nothing.
- Can a single route segment export both a metadata object and a generateMetadata function?No — exporting both from the same segment is an error. Choose one per segment: the static object when the values are known at build time, `generateMetadata` when they depend on fetched data or on `params`. Different segments along the same route may use different forms, so a layout can hold a static object while the page below it generates its title from the record it loaded.
- What does title.template in a layout apply to?To titles coming from child segments, not to the layout's own page. A layout exporting `title: { default: 'Acme', template: '%s | Acme' }` renders 'Acme' for itself and turns a child's `title: 'Pricing'` into 'Pricing | Acme'. A child that wants to escape the pattern exports `title: { absolute: 'Something else' }`.
- A page marked 'use client' exports metadata and nothing appears in the head. Why?Metadata exports are supported only in Server Components. In a client entry file the export is simply not picked up. Move the metadata to a Server Component — usually the layout above, or a server page that renders the interactive part as a child component marked `'use client'`.
saying these in an interview costs you the question
- Metadata is deep-merged, so parent fields always survive
- A page's metadata replaces everything the layout set
- title.template also formats the layout's own title
- Just add a <head> block when inheritance misbehaves
- Export both metadata and generateMetadata for flexibility