Next.js SEO
How to Configure the Next.js Metadata API: Titles, Canonicals, and Open Graph
Use the Next.js 16 Metadata API for title templates, canonical URLs, dynamic metadata, Open Graph cards, and data-backed App Router pages.
Metadata becomes reliable when it comes from the same route data and URL rules as the visible page. Duplicating that logic in a second client-side system creates drift.
Use metadata as route data
A page title, description, canonical URL, and social image describe a particular resource. Generate them beside the route that owns that resource. The root layout should supply stable site defaults; nested layouts can define section conventions; pages should own entity-specific values.
This hierarchy reduces repetition and makes the final title predictable. It also prevents a client component from becoming responsible for document metadata after the first response. The metadata exports work in Server Components, which is the correct boundary for private data access and canonical URL decisions.
Create strong root defaults
Set metadataBase to the production origin, then use relative paths for canonical URLs and images. Define a title template once so child pages supply only the meaningful part. Defaults should be accurate for routes that do not override them, not filler copied to every page.
Keep the site name stable across titles, social cards, and structured data. If staging and production need different public origins, resolve the allowed origin from deployment configuration and fail clearly when it is missing rather than quietly emitting localhost URLs.
export const metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL),
title: {
default: "Northstar",
template: "%s | Northstar",
},
openGraph: {
siteName: "Northstar",
type: "website",
},
};Generate metadata for dynamic routes
generateMetadata receives asynchronous route parameters in current App Router projects. Fetch the entity, return an explicit not-found title when it does not exist, and build the canonical from its durable slug. Do not use a display name directly in a URL unless the same slugging rules created the route.
The page and metadata often need the same record. Wrap the data function with React cache to deduplicate it during one render pass, or rely on a suitable persistent cache when the content lifetime calls for one. The goal is one source of truth, not two independent API calls that can disagree.
import { cache } from "react";
const getPost = cache(async (slug: string) => {
return db.post.findUnique({ where: { slug, published: true } });
});
export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) return { title: "Article not found" };
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: "/blog/" + post.slug },
};
}Write canonical rules before code
Decide whether the site uses www, how trailing slashes behave, which locale owns a page, and whether filter parameters create distinct resources. Then make redirects, internal links, sitemap URLs, and metadata agree. A canonical assembled ad hoc in every page will eventually diverge.
For content whose slug changes, redirect the old URL to the new canonical and preserve a durable identifier in the database. For deleted content, decide whether a related replacement exists; otherwise return a real 404 or 410 rather than a soft-404 page with a successful status.
- —One preferred origin and protocol.
- —One trailing-slash policy.
- —Explicit locale alternates where translated pages truly exist.
- —A redirect plan for changed slugs and retired content.
Handle robots and missing entities on the server
A draft, private record, or internal search page should not become indexable because the browser later hides it. Return the correct metadata and status from the server. If an entity is not public, do not leak its title or description in metadata before authorization is checked.
Robots metadata is not access control. Private routes still require authentication and authorization. For public preview links, use unguessable identifiers, noindex metadata, and clear lifecycle rules, then ensure previews are absent from the sitemap and internal navigation.
Test the rendered document
Inspect the browser document head, but also fetch the raw response and view page source. Confirm that the final title, description, canonical, robots directive, Open Graph values, and JSON-LD are correct for a direct request. Test a known entity, a missing entity, a draft, and a changed slug.
Automate the stable parts. A route smoke test can assert the status, canonical URL, title fragment, and absence of preview origins. Keep content-quality review human; a test can prove a description exists but cannot prove it is useful.
Verify metadata before domain promotion
Deploy the candidate to an Adios-generated HTTPS route and inspect its head output using the production build. Keep the public site origin in reviewable configuration and sensitive provider values behind secret references. Build and runtime logs remain attached to the candidate if metadata generation fails.
Once the release responds successfully and its metadata is correct, connect or retain the canonical custom domain. Managed TLS and release promotion keep the public hostname attached to the version that passed the checks instead of making DNS changes part of every content release.
env:
NEXT_PUBLIC_SITE_URL: https://www.example.com
runtime:
name: node@24
port: 3000
health_path: /api/health