Static metadata is an object exported from a layout or page, typed with Metadata.
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
title: { default: "Bookshelf", template: "%s | Bookshelf" },
alternates: { canonical: "/" },
openGraph: { type: "website", siteName: "Bookshelf", locale: "en_US", url: "/" },
twitter: { card: "summary_large_image", site: "@example" },
robots: { index: true, follow: true, googleBot: { "max-image-preview": "large" } },
};The home page adds only its own title and description. Requesting /, script tags removed:
<meta charSet="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Every book we have read</title> <meta name="googlebot" content="max-image-preview:large"/> <link rel="canonical" href="https://example.com"/> <meta property="og:site_name" content="Bookshelf"/>
Charset and viewport arrive unasked. The layout's openGraph survived only because the page did not declare it: merging is one key deep, so a page setting openGraph: { title: "Blog" } discards the layout's siteName and locale instead of patching them — keep shared fragments in a module and spread them. Titles are the exception: title.template applies to descendants only, never to the segment declaring it (hence title.default), and a child's title.absolute opts out of an inherited template.
metadataBase lets URL fields below it be written as paths, so canonical: "/" came back absolute; without it a relative URL is a build error. Set it from an environment variable so previews do not advertise production canonicals. Both are Server Component exports: "use client" on a page exporting metadata fails the build.