Skip to content

Metadata · Next.js (App Router)

How to add hreflang for multiple languages in Next.js (App Router)

Without it, search engines guess which version belongs to which audience, and they usually resolve it by picking one and treating the rest as duplicates. The result is your German page ranking in English results, or one language version quietly not ranking at all.

The fix for Next.js (App Router)

13x detects your framework from the response and hands you this version rather than the generic one — below 50% confidence it hedges and gives you the generic one instead.

Use the alternates.languages field of the Metadata API — it emits the link tags and keeps them beside the rest of the route's metadata:

ts code
// app/[locale]/pricing/page.tsx
import type { Metadata } from "next";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string }>;
}): Promise<Metadata> {
  const { locale } = await params;

  return {
    alternates: {
      canonical: `/${locale}/pricing`,
      languages: {
        en: "/en/pricing",
        de: "/de/preise",
        "x-default": "/en/pricing",
      },
    },
  };
}

Set metadataBase once in the root layout so these resolve to absolute URLs — without it Next emits them relative, and a relative hreflang is ignored:

ts code
// app/layout.tsx
export const metadata: Metadata = {
  metadataBase: new URL("https://example.com"),
};

params is a Promise in this version of Next; awaiting it is required.

On a different stack? The general version of this fix explains what 13x checks and why it matters, without assuming a framework.

Check your Next.js (App Router) site

113 deterministic checks against your live URL, in about 30 seconds. Framework detected from the response, so every fix comes back in the form your stack actually uses.

No signup. Results in 30 seconds.