All posts
Web design & development

Multilingual Next.js Sites: i18n Routing and Content Setup

Next.js i18n for marketing sites: locale routing in the App Router, CMS locales, translated metadata and generated hreflang, set up together in one pass.

By Mehdi Hassan8 min read

The short answer

Next.js i18n on a marketing site means three connected pieces: locale routing with a [locale] segment in the App Router, a CMS that stores content per locale, and metadata that generates hreflang and translated sitemaps from the same locale list. Set them up together from one config, and adding a language becomes a content task.

Key takeaways

  • The App Router has no built-in i18n config, so routing comes from a [locale] segment plus middleware or a library.
  • Keep one locale list in code and use it for routes, the CMS, metadata, sitemaps and the switcher.
  • Statically generate every locale with generateStaticParams so pages stay fast and crawlable.
  • Generate hreflang from metadata alternates, never by hand, so return tags can't drift.
  • Only redirect at the root based on browser language; never trap visitors on a locale they didn't pick.

Part of Multilingual Website Guide: Strategy, Structure and SEO

How do you set up Next.js i18n on a marketing site?

You set up Next.js i18n by defining your locales once, routing every page under a locale segment, pulling localized content from your CMS, and generating hreflang and sitemaps from the same list. Most broken multilingual Next.js sites have these pieces set up separately, by different people, at different times.

This is the build side of our multilingual website guide. It assumes you've already chosen subfolders such as /de/ and /fr/, which is the right default for most startups. The examples follow the App Router, which is what new Next.js marketing sites use in 2026. If you're still deciding whether Next.js is the right platform at all, read when Next.js is the right choice for a marketing website first.

Layer

What it does

Where it lives

Locale config

Lists supported locales and the default

One shared file imported everywhere

Routing

Serves /de/pricing, /fr/pricing and so on

app/[locale]/ segment and generateStaticParams

Detection

Suggests or redirects at the root only

Middleware (called proxy in recent Next.js versions) or the library's helper

UI strings

Buttons, nav, form labels

Message files or a library such as next-intl

Page content

Headlines, sections, proof, pricing

CMS with field-level or document-level locales

SEO output

hreflang, canonicals, translated titles, sitemaps

generateMetadata and sitemap.ts

How does i18n routing in Next.js work in the App Router?

i18n routing in the Next.js App Router works by nesting your pages under a dynamic [locale] folder, so the first part of the URL becomes a parameter every page can read. Unlike the old Pages Router, there's no i18n option in the config file; routing is just a route segment you control.

  1. Define locales once

    Create a small module exporting your locales (for example en, de, fr) and the default. Every other piece imports from here, so adding a language means changing one list.

  2. Add the locale segment

    Move your pages under app/[locale]/. The root layout reads the locale parameter and sets the html lang attribute, plus dir="rtl" for right-to-left languages.

  3. Generate every locale statically

    Export generateStaticParams from the locale layout so every locale is prerendered at build time. Marketing pages should be static or cached, not rendered on every request.

  4. Reject unknown locales

    If the locale parameter isn't in your list, call notFound() so /xx/pricing returns a real 404 instead of an empty page.

  5. Decide on the default locale URL

    Either prefix every locale (/en/, /de/) or keep the default unprefixed (/pricing, /de/pricing). Unprefixed defaults keep existing English URLs, which matters if you already have rankings.

  6. Handle the root gently

    At the root only, middleware can suggest a locale based on the Accept-Language header. Remember the visitor's choice in a cookie, and never redirect someone who clicked a link to a specific locale.

Should you use next-intl or build your own setup?

Use next-intl, or a similar maintained library, when your site has a meaningful number of UI strings, plurals or formatted dates and numbers. A small marketing site whose words mostly come from the CMS can get by with a plain dictionary per locale and the built-in Intl APIs.

Pros

  • next-intl handles locale-aware routing helpers, links and middleware for you
  • Message formatting covers plurals, dates, numbers and currencies correctly per locale
  • Works with Server Components, so strings don't have to ship to the browser
  • A maintained library tracks App Router changes so you don't have to

Cons

  • Another dependency to update when Next.js releases a major version
  • Overkill for sites where nearly all copy lives in the CMS
  • Its conventions shape your folder structure and imports
  • Message files can duplicate content that editors expect to find in the CMS

Whichever you choose, keep UI strings and page content apart. Buttons and form labels belong in message files that developers own. Headlines, proof and pricing belong in the CMS that marketing owns. Mixing them is how translators end up editing code.

How should the CMS handle locales?

The CMS should store content per locale with a clear fallback, so editors can localize the fields that need it without duplicating whole pages. Most headless CMSs offer field-level localization, document-level localization, or both.

  • Field-level localization keeps one document with a value per locale for each translatable field. It's tidy for pages with the same structure in every language, and it's how Payload's localization works.
  • Document-level localization creates a separate document per language. It suits content that differs a lot by market, such as localized case studies or region-only pages.
  • Fallbacks decide what happens when a translation is missing. For marketing pages, falling back silently to English can publish half-translated pages, so prefer showing the page only when it's complete.
  • Localized slugs let /de/preise exist alongside /pricing. Store the slug per locale and build routes from it, then generate redirects when a slug changes.

Our own site runs on Next.js with Payload on Cloudflare Workers, and we model locales in the CMS before writing a single page component. Our guide to hosting Next.js on Cloudflare covers the deployment side. Components also need to flex for longer text and right-to-left scripts, which our guide to multilingual web design covers.

How do you generate hreflang and sitemaps in Next.js?

Generate hreflang by returning alternates.languages from generateMetadata on each page, built from your locale list and that page's localized slugs. Generate the sitemap the same way in sitemap.ts, with an alternates entry for each URL. One source means return tags are always complete.

  1. In generateMetadata, look up the page's slug in every locale where it exists.
  2. Return alternates.canonical as the page's own URL in the current locale, so every version is self-canonical.
  3. Return alternates.languages with one entry per available locale plus x-default, using absolute URLs via metadataBase.
  4. Translate the title, description and Open Graph fields from the CMS, not from the default locale.
  5. In sitemap.ts, list every localized URL with its alternates, and skip locales where the page doesn't exist.

If you want the rules behind the output, our explainer on hreflang setup and mistakes covers self-references, return tags and codes. The Next.js documentation at nextjs.org/docs describes the metadata and sitemap APIs.

Hypothetical example: adding French to an English and German Next.js site

For example, suppose a site already serves English at the root and German under /de/, from one locale list, with Payload storing localized fields.

Adding French means: add fr to the locale list; enable fr in the CMS localization config; translate UI strings into a French message file; have editors localize the page fields and French slugs; review the pages in the preview deployment. Because routes, metadata, sitemaps and the language switcher all read the same list, hreflang on every English and German page gains a French entry automatically on the next build.

No component code changes. If a French page isn't ready, it's left out of the build and out of every hreflang set until it is.

What should you check before launching a multilingual Next.js site?

Before launch, check that every locale renders statically, returns the right status codes, outputs complete hreflang, and can be switched without losing the visitor's place. These are the failures we see most on multilingual Next.js builds.

  • Every locale URL returns 200, and unknown locales return 404.
  • The html lang and dir attributes are correct per page.
  • View the source of one page per locale: canonical, hreflang set and translated titles are all present in the HTML, not added by client-side code.
  • The language switcher links to the equivalent page, not the home page, and hides locales where the page doesn't exist.
  • Preview deployments are not indexable, and production is.
  • If you're moving an existing multilingual site to Next.js, every old URL redirects to its new locale URL. Our guide to Next.js internationalization for migrated sites covers that mapping.

Frequently asked questions

Does the Next.js App Router support i18n out of the box?
Not as a config option. The Pages Router had an i18n setting in next.config, but the App Router expects you to build locale routing with a dynamic segment and, if you want detection, middleware. That's more setup, but it gives you full control over URL structure, default locale prefixes and how detection behaves.
Is next-intl required for a multilingual Next.js site?
No. It's a popular, well-maintained library that saves time with routing helpers and message formatting, but small marketing sites can use a simple dictionary per locale and the browser's built-in Intl APIs. The bigger decision is where content lives: page copy should come from a CMS with locale support, whichever string library you use.
Can translated pages in Next.js be fully static?
Yes. With generateStaticParams returning every locale and slug, each translated page is prerendered at build time like any other static page. When editors publish changes in the CMS, on-demand revalidation can rebuild just the affected pages. That keeps multilingual pages fast and fully readable by search engines and AI crawlers.
How do I translate URL slugs in Next.js?
Store a slug per locale in the CMS, build routes from those slugs, and map between them when generating hreflang and the language switcher. When an editor changes a slug, add a redirect from the old one. Some teams keep English slugs in every locale for simplicity, which is acceptable but reads less naturally to local visitors.
Does i18n slow down a Next.js site?
Not when it's set up statically. Locale routing adds no runtime cost to prerendered pages. Slowdowns usually come from shipping every language's message file to the browser, or from middleware doing heavy work on each request. Load strings on the server, send only the current locale, and keep middleware limited to the root redirect.

· CEO & Design Director

Copywriter, designer and developer, and founder of Designify Studio. Over five years, 60+ projects across 14+ industries, for startups that have raised $300M+.

Want this done for you? Website, $8,000.

A multi-page marketing site, designed by us, developed in Next.js and animated. $8,000, one-time, up to 6 pages. Book a free 30-minute call to see if it fits.

Schedule Call