Next.js
SEO in Next.js: Metadata, Canonicals, and Indexing Basics
How to set titles, descriptions, and canonicals in the App Router so search engines index the right thing—from setup to dynamic and multi-locale.
Next.js App Router gives you a central place for metadata and canonicals. Use it well and you avoid duplicate content, missing titles, and wrong indexing that hurt SEO. This guide covers the basics and then dynamic routes, Open Graph, and robots—so you can ship pages that search engines and social previews handle correctly.
Why metadata and canonicals matter
Search engines and social platforms rely on title, description, and canonical URL to understand and display your pages. Wrong or missing metadata leads to duplicate-content issues, poor snippets in search results, and broken previews when shared. Next.js turns a single metadata (or generateMetadata) export into the right <title>, <meta>, and <link> tags, so you don't have to wire them by hand.
The metadata API
Export a metadata object (or generateMetadata for dynamic routes) from layout.tsx or page.tsx. Next.js turns it into <title>, <meta name="description">, Open Graph tags, and more. Root layout metadata applies to the whole app; segment layouts and pages override or extend it. Prefer one source of truth per route so you don't have conflicting tags.
Static example:
// app/about/page.tsx
export const metadata = {
title: "About Us",
description: "Learn about our team and mission.",
};
export default function AboutPage() {
return <div>...</div>;
}Dynamic example (e.g. blog post from CMS):
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
if (!post) return { title: "Not found" };
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: post.ogImage ? [post.ogImage] : [],
},
};
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return <Article data={post} />;
}Use generateMetadata when the title and description depend on data (CMS, API). Keep metadata in the same file as the page or layout that owns that URL so it's easy to maintain.
Beginner tip: Always set at least title and description. Empty or generic titles (e.g. "Home") hurt both SEO and click-through rate.
Canonical URLs
Set the canonical URL so search engines know which URL is the main one for that content. That matters when the same content is reachable via multiple URLs (query params, trailing slash, or alternate paths). In the App Router, add alternates.canonical to metadata. Use the absolute URL (including your production domain).
export async function generateMetadata({ params }) {
const base = process.env.NEXT_PUBLIC_SITE_URL ?? "https://example.com";
return {
title: "Blog Post",
description: "...",
alternates: {
canonical: `${base}/blog/${params.slug}`,
},
};
}If you have alternate languages or AMP, you can add alternates.languages or other alternate links in the same object. One canonical per piece of content avoids splitting "link equity" and duplicate-content penalties.
Expert tip: Use a single env var (e.g. NEXT_PUBLIC_SITE_URL) for the site origin so canonicals are correct in preview and production.
Indexing and robots
Control indexing with the robots field in metadata (e.g. noindex for thank-you pages or drafts) or with a robots.txt (and sitemaps) at the site root. For most content pages, don't set noindex; let search engines index the canonical URL. Use noindex only for utility pages, duplicates, or private content.
// e.g. thank-you page
export const metadata = {
title: "Thanks",
robots: { index: false, follow: true },
};Combine that with a sitemap (e.g. app/sitemap.ts) and a robots.txt that points to it so crawlers discover your URLs efficiently.
Summary
- Use the metadata API (and generateMetadata for dynamic routes) for title, description, and Open Graph.
- Set
alternates.canonicalto the absolute URL of the main version of the page. - Use robots and robots.txt to control indexing and point to your sitemap.
With these basics in place, your Next.js app is in good shape for SEO fundamentals.