Back to Blog
SEO

Multilingual SEO in Next.js: What next-intl Solves and What You Still Have to Build

Translating a site isn't the same as making it multilingual. Notes from running three locales on one domain with next-intl — and the four places Google is genuinely strict.

Mehran HatamiMehran Hatami3 min readNext.jsnext-intlSEO

This site runs in three languages: Persian, English and German. Wiring up the translations took an afternoon. Convincing Google that these are versions of one page rather than three duplicates took weeks. This post is about the second part.

One: pick a URL structure early

Key Insight

next-intl solves routing and strings. Hreflang, canonicals and sitemaps remain your job — and those are exactly what decide whether you rank.

You have three options: a separate domain per language, subdomains, or a path prefix like /fa and /en. For a product or portfolio site the path prefix is almost always right — domain authority stays in one place and maintenance costs nothing. What matters more than the choice is not changing it later; restructuring after indexing means months of redirects and ranking noise.

Two: hreflang, where most sites get it wrong

Hreflang tells Google these pages are translations of one another. Three rules have to hold at once: every page must reference all versions including itself, references must be reciprocal, and an x-default must exist so a visitor with no matching language has somewhere to land. In the App Router all of this comes out of generateMetadata.

export async function generateMetadata({ params }) {
  const { locale } = await params;
  return {
    alternates: {
      canonical: `https://example.com/${locale}`,
      languages: {
        fa: "https://example.com/fa",
        en: "https://example.com/en",
        de: "https://example.com/de",
        "x-default": "https://example.com/en",
      },
    },
  };
}

Three: the canonical points at its own language

This is the most common mistake I've run into: every version canonicalises to the English page. The result is that Google drops the Persian version from the index, because the site itself declared the original lives elsewhere. Each page's canonical is that page. The relationship between languages is expressed only through hreflang.

The second common failure is www versus non-www. If both respond, every page has two addresses, and Search Console reports it as "duplicate, Google chose a different canonical". One 301 in middleware ends the whole story.

Four: the sitemap has to be trilingual too

The sitemap should list all three versions separately and carry the language alternates on each entry. In Next.js the sitemap function with an alternates field generates exactly that, with no hand-written XML.

export default function sitemap() {
  const locales = ["fa", "en", "de"];
  return locales.map((locale) => ({
    url: `https://example.com/${locale}`,
    alternates: {
      languages: Object.fromEntries(
        locales.map((l) => [l, `https://example.com/${l}`])
      ),
    },
  }));
}

And one thing the tooling won't tell you

Machine translation doesn't make pages multilingual. Someone searching in Persian types a different phrase than someone searching in English, even when they want the same thing. Titles and descriptions for each language have to be written from that market's vocabulary, not translated word for word from the original. Correct hreflang tells Google which language a page is for. Correct content decides whether it gets found at all.