npm i next-intl-alternates
Canonical and alternate URLs for a next-intl site. Four lines of configuration produce a set of links that is easy to get subtly wrong, and wrong here fails quietly: the annotation is dropped, and nothing tells you.
what Next puts in the head
<link rel="canonical" href="https://example.com/about" />
<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/about" />
<link rel="alternate" hreflang="de" href="https://example.com/de/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />
It takes next-intl's routing object as it is — locales, default locale, prefixes, localized pathnames — plus the origin, which next-intl has no reason to know.
// src/libs/alternates.ts
import { createAlternates } from "next-intl-alternates";
import { routing } from "@/i18n/routing";
export const getAlternates = createAlternates({
baseUrl: "https://example.com",
...routing,
});// src/app/[locale]/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const { locale, slug } = await params;
const article = await getArticle(locale, slug);
return {
alternates: getAlternates({
locale,
pathname: "/blog/[slug]",
params: { slug },
// this article was only ever written in Japanese
availableLocales: article.locales,
}),
};
}The result drops straight into Metadata["alternates"]. Nothing else about your metadata changes.
With as-needed prefixes the default locale has no prefix, and its home page is the origin. Join a locale prefix to a path of "/" and you get "/ja/", which redirects to "/ja" with a 308. A canonical URL that redirects points at a page that is not there.
An article written in one language only has one version. Listing a language the page does not have makes search engines drop the annotation — all of it, not just that line.
x-default is for a reader whose language you do not publish in. It belongs on the default locale, except on a page the default locale does not have, where it belongs on the version that exists.
With localized pathnames, /about is /kaisha in Japanese. Every line has to point at that locale's own pathname, or the annotation names URLs that 404.