CanonicalTag.com logoCanonicalTag.comThe canonical tag & URL canonicalization
FrameworkJavaScript Frameworks

Canonical Tags in Next.js

In Next.js, every canonical tag is opt-in — and getting metadataBase wrong is the most common way to ship the wrong one.

Next.js does not add canonical tags automatically

Next.js, in both the App Router and the Pages Router, does not emit a rel=canonical tag for you. There's no default behavior to discover in the rendered HTML — if you haven't explicitly set one, there isn't one. This puts Next.js in the same category as a bare React app rather than a platform like Yoast SEO or Wix that self-references by default: every canonical in a Next.js site is something a developer opted into.

That opt-in design isn't a flaw — it's consistent with how Next.js handles SEO metadata generally, favoring an explicit API over inferred intent. But it means a Next.js site can go live with zero canonical tags anywhere, and nothing in the framework will warn you. A migration from a platform where canonicals are automatic — WordPress with Yoast, for example — is a common moment for this gap to appear: the team assumes canonicals "just work" the way they did on the old platform, and nobody notices they're missing until an audit or a duplicate-content report surfaces the problem.

Because the canonical is entirely explicit, it also has to be maintained explicitly. There's no single global switch — the App Router's Metadata API, the metadataBase setting, and (on the Pages Router) next/head or next-seo all have to be applied deliberately, page by page or via a layout, rather than assumed to be handled once and forgotten.

Setting canonicals with the App Router Metadata API

The App Router's Metadata API is the supported way to set a canonical, and it only works from Server Components. For a static canonical that doesn't change per request, export a metadata object:

export const metadata = {
  alternates: {
    canonical: 'https://nextjs.org'
  }
}

Next.js turns that into:

<link rel="canonical" href="https://nextjs.org" />

For a canonical that depends on route params, fetched data, or other per-request logic, use the async generateMetadata function instead, returning the same alternates.canonical shape:

export async function generateMetadata({ params }) {
  return {
    alternates: {
      canonical: `https://example.com/products/${params.slug}`
    }
  }
}

Both approaches render the canonical into the page's server-generated head, which is what makes them reliable for crawlers that don't execute JavaScript.

The metadataBase gotcha

metadataBase is a single setting, defined once in the root app/layout.js (for example, a metadata export containing metadataBase: new URL('https://acme.com')), that lets every alternates.canonical value elsewhere in the app use a relative path instead of a full URL. Next.js composes the relative path onto metadataBase to produce the absolute canonical that actually gets rendered.

The gotcha has two sides. First, an absolute canonical URL ignores metadataBase entirely — write out the full https://example.com/page and metadataBase plays no role. Second, and more dangerous, a relative canonical used without metadataBase set is a build error in some cases, and where it isn't caught, Next.js falls back to a default origin — localhost in development, or a deployment's own preview URL in some hosting setups. That fallback is a common, easy-to-miss misconfiguration: the site builds and deploys fine, the canonical tag is present in the HTML, and it points at a preview or localhost URL that means nothing to Google.

The practical guidance: set metadataBase once at the root, and either rely on it consistently with relative canonicals, or use fully absolute URLs everywhere without depending on metadataBase at all. Mixing the two approaches inconsistently across a codebase is where teams get bitten.

Prerenderable pages put the canonical directly in the initial HTML head. With streaming metadata in newer Next.js versions, dynamically resolved metadata may stream into the body for JS-executing bots — Googlebot handles this, but the safer path is keeping pages prerenderable so the canonical lands in the head from the start.

Pages Router and next-seo

If a project is still on the Pages Router rather than the App Router, the Metadata API isn't available — canonicals are set with next/head or a helper library:

import Head from 'next/head'

export default function Page() {
  return (
    <Head>
      <link rel="canonical" href="https://example.com/page" />
    </Head>
  )
}

The other common approach on the Pages Router is next-seo, which wraps the same idea in a component:

import { NextSeo } from 'next-seo'

export default function AboutPage() {
  return <NextSeo canonical="https://example.com/about" />
}

next-seo also supports a site-wide default canonical via a DefaultSeo component placed in _app, which individual pages can then override. Both next/head and next-seo render server-side in the Pages Router, so — like the App Router's Metadata API — the resulting canonical tag lands in the initial server-rendered HTML rather than being injected after the fact in the browser.

Which of these two approaches to use is really a question of which router the project is on rather than a matter of preference. Projects that have already migrated to the App Router should use the Metadata API exclusively and treat next/head and next-seo as legacy patterns; projects still on the Pages Router don't have the Metadata API available to them at all, so next/head or next-seo is the only route. Mixing patterns within one codebase — some pages using next/head, others hand-placing a link tag in a custom document — is how duplicate or conflicting canonicals creep in.

Common Next.js canonical mistakes

  • Using a relative canonical without setting metadataBase. Ambiguous or unresolved relative paths are a common source of build errors or wrong output — use absolute URLs if you're not certain metadataBase is set correctly everywhere.
  • Leaving metadataBase unset in production. Without it, canonical resolution can fall back to localhost:3000 or a deployment preview URL — a canonical no search engine should ever see, shipped straight to production.
  • Canonical tag not present in the initial HTML. If the canonical only appears after client-side JavaScript runs, it's not reliable — see the client-only-injection risk covered on the React canonical page.
  • Calling the Metadata API from a Client Component. metadata and generateMetadata only work in Server Components; used in a Client Component, they're a no-op and no canonical is emitted.
  • Duplicate or conflicting canonicals. Setting alternates.canonical at both the layout and page level with different values, or having a hand-placed HTML canonical disagree with the Metadata API's output, leaves search engines with two competing signals instead of one.

Frequently Asked Questions

Does Next.js output a canonical tag by default?

No. Neither the App Router nor the Pages Router emits a rel=canonical automatically. Every canonical in a Next.js site is explicitly set by a developer, using the Metadata API, next/head, or next-seo.

What's the difference between the static metadata export and generateMetadata?

The static metadata object is for a canonical value that doesn't depend on request data — it's exported once. generateMetadata is an async function used when the canonical needs to be built dynamically, for example from a route's slug parameter.

What happens if I use a relative canonical without setting metadataBase?

It's an unreliable path. A relative canonical needs metadataBase set at the root layout to resolve into an absolute URL; without it, resolution can fail outright or fall back to a default origin such as localhost or a deployment preview URL, which is not what you want shipped to production.

Can I set a canonical from a Client Component?

No. Next.js's Metadata API — both the static metadata export and generateMetadata — only works in Server Components. Used in a Client Component, it's a no-op and no canonical tag is produced.

Is next-seo still relevant if I'm using the App Router?

next-seo and next/head are Pages Router tools. On the App Router, the Metadata API is the supported approach — alternates.canonical in a static metadata export or generateMetadata.