Next.js Metadata Deep Dive: Titles, Canonicals, Robots and JSON-LD

How to handle SEO metadata in the Next.js App Router: metadataBase and title templates, generateMetadata, robots directives, file conventions and safe JSON-LD, with code for each.

The Next.js App Router handles SEO metadata through the Metadata API: a static metadata object or a generateMetadata function in each layout and page, plus file conventions for robots.txt, sitemaps, icons and Open Graph images. Getting it right means setting metadataBase once so URLs are absolute, using a title template, setting canonicals and alternates per page, controlling robots directives, and rendering JSON-LD safely in server components. This guide covers each piece with code.

1. Root layout: base URL and title template

// app/layout.js
export const metadata = {
  metadataBase: new URL("https://www.example.com"),
  title: { default: "Example Company", template: "%s | Example Company" },
  description: "What the company does, for whom, in one sentence.",
  openGraph: { siteName: "Example Company", locale: "en_IN", type: "website" },
};

metadataBase lets every page use relative paths for canonicals and images while the output contains absolute URLs. The template adds the brand to every page title set lower down.

2. Dynamic pages: generateMetadata

// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  if (!post) return { title: "Not found", robots: { index: false } };
  return {
    title: post.seoTitle ?? post.title,
    description: post.summary,
    alternates: { canonical: `/blog/${slug}/` },
    openGraph: {
      type: "article",
      publishedTime: post.publishedAt,
      modifiedTime: post.contentUpdatedAt,
      images: [{ url: post.image, width: 1200, height: 630 }],
    },
  };
}

Requests for the same data in generateMetadata and the page are deduplicated when using fetch, so the page does not fetch twice.

3. Robots directives per page

export const metadata = {
  robots: {
    index: true, follow: true,
    googleBot: { "max-image-preview": "large", "max-snippet": -1 },
  },
};

Use index: false for thin utility pages such as internal search results. Allowing large image previews supports Google Discover; see Google Discover.

4. File conventions

File Produces
app/robots.js robots.txt
app/sitemap.js sitemap.xml, with generateSitemaps for large sites
app/icon.png, app/apple-icon.png Favicons
app/opengraph-image.js Generated Open Graph images per route

See sitemaps that scale.

5. JSON-LD

The Next.js documentation recommends rendering JSON-LD as a script tag in the page component. Escape the less-than character so content cannot close the script tag:

export default async function Page({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.title,
    datePublished: post.publishedAt,
    dateModified: post.contentUpdatedAt,
    author: { "@id": `https://www.example.com/about/#${post.authorSlug}` },
  };
  return (
    <>
      <script type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />
      <article>{/* ... */}</article>
    </>
  );
}

The same safety issue in Laravel is covered in JSON-LD in Laravel Blade.

6. Check the output

  1. View source on each template: one title, one description, one canonical, correct robots tag, valid JSON-LD.
  2. Confirm metadata is present in the initial HTML, which is what AI crawlers read.
  3. Run the SEO smoke test in your pipeline.

Frequently asked questions

Can metadata be set in client components?

No. The Metadata API works in server components, layouts and pages. Keep metadata on the server.

Why is my canonical relative?

Set metadataBase in the root layout; Next.js then outputs absolute URLs.

Does Next.js stream metadata?

Recent versions can stream metadata for faster rendering while still serving it in the initial HTML to known crawlers. Check the output with a crawler user agent if in doubt.

Sources

  1. Next.js documentation, Metadata API, generateMetadata, file conventions and JSON-LD guidance
  2. Google Search Central, “Robots meta tag specifications”