Route Mapping for Multilingual Sites

Route mapping in headless architectures is a deterministic bridge between CMS content models and frontend routing. Unlike monoliths where routing is coupled to the database schema, Jamstack frameworks decouple URL resolution from content storage — which demands explicit route manifests, locale-aware slug normalization, and predictable cache invalidation. Get it right and routing becomes the foundation for internationalization: precise SEO signals, clean CDN distribution, faster builds, and resilient deploys across markets.

URL strategies for multilingual sitesSubdirectories, subdomains and country-code domains compared on setup effort, domain authority, geo-targeting signal, CDN configuration and robots/sitemap handling.StrategySetupAuthorityGeo signalOperationsSubdirectories /de/simpleconsolidatedvia hreflangone hostSubdomains de.DNS per localepartly splitvia hreflanghost per localeCountry domains .dedomains, certssplitstronghost per market
Subdirectories are the usual default for headless sites; country domains suit strong national brands.

Integration Contract

Route mapping is the contract between content identity and public addresses. Four decisions make it explicit. Identity: every routable entry has a stable id, and a URL is derived from id, locale and slug fields, never stored as free text that can drift. Slugs: each locale has its own slug field, validated for character set and uniqueness within its parent and locale. Hierarchy: paths are built from a parent chain or a section field, with a maximum depth, so moving a page changes its path predictably and creates a redirect. Configuration: supported locales, the default locale, the prefix rule for the default locale and the trailing-slash convention live in one shared configuration read by router, sitemap, canonical resolver and hreflang builder.

Bash
# .env: routing configuration shared across the app
SUPPORTED_LOCALES=en,de,fr,ja
DEFAULT_LOCALE=en
PREFIX_DEFAULT_LOCALE=true      # /en/about rather than /about
TRAILING_SLASH=false
MAX_PATH_DEPTH=4

The Route Schema

Multilingual routing uses one of three URL strategies: subdirectories (/en/about), subdomains (en.example.com/about), or country-code TLDs (example.de/about). Subdirectories are the Jamstack default — simpler DNS, unified analytics, straightforward CDN config, and consolidated domain authority that search engines reward.

The schema maps CMS locale identifiers to URL segments while preserving content hierarchy. A robust manifest normalizes slugs into a predictable tree:

One content tree, localized pathsThe same content tree of about, products and product A entries is published under English paths and under German paths with translated slugs, both generated from one route manifest keyed by entry id and locale.Route manifestentry id + locale/en/about/en/products/product-a/de/ueber-uns/de/produkte/produkt-aen slugsde slugs
Entries are shared; paths are per locale and come from localized slug fields.

This structure drives static generation and dynamic route resolution. Align route decisions with broader Localization & SEO Optimization strategy so hreflang, canonicals, and language negotiation stay consistent. Normalization should also transliterate non-Latin scripts, strip diacritics where appropriate, and enforce lowercase to avoid duplicate-content penalties.

Querying and Normalizing CMS Locales

Headless platforms expose localized content through locale-scoped queries or language-specific endpoints. Fetch all routes across target locales at build time or via ISR. A flat query keeps payload overhead and client-side mapping simple.

GraphQL
query GetAllLocalizedRoutes {
  allPages(locale: "*", status: "published") {
    edges {
      node {
        id
        locale
        slug
        parent { slug }
      }
    }
  }
}

Normalize the response into a route map grouped by locale; this drives generateStaticParams in Next.js or equivalent hooks elsewhere. Nested categories need recursive slug reconstruction. When a translation is missing, the routing layer must handle fallback chains without breaking navigation or 404ing — Content Fallback & Routing serves the closest available variant while preserving crawlability and session continuity.

Building paths from the manifest

The route manifest is the single structure every consumer reads: static generation, the router’s lookup from path to entry, the sitemap, hreflang and the redirect generator. Build it from a flat query of all published routable entries in all locales, reconstructing each path from the parent chain.

TypeScript
// lib/routes/manifest.ts
interface RouteRow { id: string; locale: string; slug: string; parentId: string | null; type: string }
export interface RouteEntry { id: string; locale: string; path: string; type: string }

export function buildManifest(rows: RouteRow[], maxDepth = 4): RouteEntry[] {
  const byKey = new Map(rows.map((r) => [`${r.id}:${r.locale}`, r]));
  const out: RouteEntry[] = [];
  for (const r of rows) {
    const segments: string[] = [];
    let cur: RouteRow | undefined = r;
    let depth = 0;
    let missingParent = false;
    while (cur) {
      if (++depth > maxDepth) throw new Error(`Path too deep or cyclic at ${r.id} (${r.locale})`);
      segments.unshift(cur.slug);
      if (!cur.parentId) break;
      const parent = byKey.get(`${cur.parentId}:${r.locale}`);
      if (!parent) { missingParent = true; break; } // parent not published in this locale
      cur = parent;
    }
    if (missingParent) continue; // excluded until the parent exists in this locale
    out.push({ id: r.id, locale: r.locale, path: `/${r.locale}/${segments.join("/")}`, type: r.type });
  }
  const seen = new Set<string>();
  for (const e of out) {
    if (seen.has(e.path)) throw new Error(`Duplicate path ${e.path}`);
    seen.add(e.path);
  }
  return out;
}

The builder fails loudly on cycles, excessive depth and duplicate paths, which are exactly the problems that otherwise surface as broken pages. Pages whose parent is not published in their locale need a decision: publish them under the parent’s path in the fallback locale, exclude them until the parent exists, or flatten them to the section root. Excluding them is the safest default. The route manifest guide covers incremental updates.

Framework-Specific Implementation

Frameworks abstract locale detection into middleware and routing primitives, but headless setups need explicit config to bridge CMS data and framework expectations. In Next.js App Router, locale negotiation happens in middleware.ts. The URL path is the source of truth for the locale of every page; Accept-Language and a preference cookie are only consulted for requests without a locale prefix, such as the bare domain, to choose where to redirect. Never redirect a request that already names a locale, or crawlers and readers following a link to /de/... can be bounced elsewhere.

TypeScript
export async function generateStaticParams() {
  const routes = await fetchAllLocalizedRoutes();
  return routes.map((route) => ({
    locale: route.locale,
    slug: route.slug.split('/').filter(Boolean),
  }));
}

For deeply nested or user-generated content, static pre-generation becomes impractical. Dynamic route mapping for headless i18n handles catch-all segments, locale-specific URL conventions, and on-demand ISR without hardcoding route trees — shifting the routing burden from build-time compilation to edge execution for real-time updates.

Cache Invalidation and Edge Distribution

Route mapping shapes CDN cache keys and invalidation. Each localized path resolves to a unique entry, typically [locale]/[path]. On content updates, emit precise cache tags matching the affected locale and path hierarchy — broad purges raise origin load and degrade performance, while targeted invalidation preserves edge hit ratios.

Synchronizing route changes across edges requires coordinated cache tagging and webhook-driven purges. Teams with localized media libraries must also account for asset variants, triggering Asset Duplication & CDN Sync on route updates. Surrogate keys or Cache-Tag headers let Cloudflare or Fastly invalidate specific locale-path combinations without disrupting unrelated routes.

SEO Signal Alignment

Route structure dictates how search engines read language targeting. Every route must emit self-referencing canonicals, cross-locale hreflang, and language-specific meta tags. RFC 5646 is the authoritative standard for locale formatting and should be enforced in route generation.

Search engines rely on consistent, predictable routing to crawl internationalized content (Google Search Central). Map CMS locale fields directly to lang attributes, Open Graph tags, and structured data. Dynamic Sitemap Generation must reflect the finalized route manifest so search engines get an accurate, locale-segmented index of every crawlable path.

Localized Slugs and Redirects

Translated slugs make URLs readable in each language, /de/produkte/wanderschuh instead of /de/products/hiking-boot, and they carry a small relevance signal. They also create work: every slug change in any locale is a URL change that needs a redirect, and translators must know that editing a slug after publication has consequences. Handle this in the model and the pipeline rather than by policy. Make slugs editable freely until first publication, then warn on change and create a redirect automatically from the old path to the new one when the change is published. Keep the redirect table per locale, flattened so chains never form, and served at the edge, as described in handling redirects. Moving a page to another parent changes every descendant’s path, so the same mechanism must generate redirects for the whole subtree.

A slug change becomes a redirectAn editor changes the German slug of a published page; on publish, the webhook handler computes the old and new paths for the page and its descendants, adds 301 redirects to the edge redirect table, revalidates the affected pages and updates the sitemap and hreflang clusters.EditorCMSRoute handlerEdge redirectsslug: wanderschuhe → wanderstiefelpublish webhook (old + new slug)recompute paths for pageand descendants301 /de/produkte/wanderschuhe → /de/produkte/wanderstiefelrevalidate pages, sitemap, hreflang
Slug changes are safe because every path change produces its redirect automatically.

Locale Detection Without Harm

Detecting a visitor’s language is useful at exactly one point: when they arrive without a locale in the URL, typically at the bare domain. There, a redirect based on Accept-Language, a preference cookie or geolocation can send them to the right locale. Everywhere else, the URL decides. Redirecting /de/... to /fr/... because the browser prefers French traps readers who deliberately chose German, breaks shared links and confuses crawlers, which usually send no Accept-Language or always the same one. Offer a language suggestion banner instead of a redirect when the page’s locale differs from the browser’s preference, and remember the choice in a cookie. The locale detection topic covers the edge implementation.

Preview & Draft Routes

Drafts need paths before they are published, and editors expect preview links that look like the final URL. Resolve draft routes from the draft slug in draft mode, and make preview links carry the locale in the path exactly as production would. Unpublished parent pages create a special case: a draft child under a draft parent has no published path yet. Resolve such paths from draft data in preview only, and make sure the route manifest used for production never includes them, or the sitemap and navigation will point at pages that return 404.

Validation and CI

Route-mapping failures surface late — broken links, duplicate content, misrouted fallbacks. Integrate route validation into CI/CD to catch them. Automated checks should verify:

  • Locale parity across primary navigation nodes
  • Slug normalization consistency (no mixed casing or trailing slashes)
  • hreflang reciprocity (every translated version references every other translated version)
  • Fallback chain resolution without 404 loops

Tools like next-sitemap or custom auditors can parse the generated manifest against CMS export snapshots. On discrepancy, halt the deploy and surface the diff. Continuous validation keeps route mapping deterministic as content scales and frameworks evolve.

Menus, breadcrumbs, related-content lists and links inside rich text all need localized paths, and hard-coded paths are the most common source of broken links on multilingual sites. Store internal links in the CMS as references to entries, never as URL strings, and resolve them through the manifest in the reader’s locale at render time. A link from a German article to a product then points at the German product path automatically, and keeps working when the product’s slug changes. When the target has no German version, the resolver decides: link to the fallback page, which shows a notice, or omit the link. Rich text editors in most CMSs support entry links for exactly this purpose; turn off free URL entry for internal links, or at least validate pasted URLs against the manifest and convert them to references.

Adding a Locale

Launching a new locale touches every part of routing, and a checklist prevents the usual gaps. Add the locale to the shared configuration, including its fallback chain and whether the default-locale prefix rule applies. Create localized slug fields or confirm the CMS will require them for the new locale. Decide what happens to pages that are not yet translated: fall back with notices, or exclude them until translated, which keeps the new locale small and clean at launch. Generate the manifest for the new locale and review it with the market team, especially slug choices for top-level sections, which are hard to change later. Extend the sitemap, hreflang clusters and robots configuration, add the locale to the language switcher, and set up the title template and brand form described in title templates across locales. Finally, add the locale’s known URLs to the regression suite. Most of these steps are configuration once routing is data-driven; the ones that are not usually reveal hard-coded assumptions worth removing.

Error Handling & Resilience

Routing errors are among the most visible failures a site can have, because they turn into 404s on pages that exist. Protect against the common causes. A failed manifest build must never replace a working one: keep serving the previous manifest and alert, as with sitemaps. A path lookup that misses the manifest should check the redirect table before returning 404, so recently renamed pages still resolve during propagation. Uppercase letters, trailing slashes and encoded characters in incoming URLs should be normalized with a redirect to the canonical form rather than a 404. And when the CMS is unreachable, cached pages keep serving; only new paths fail, which is an acceptable degradation.

Testing Route Mapping

Test the manifest builder with fixtures that cover nested pages, translated slugs, unpublished parents, duplicates and cycles. Snapshot the manifest for a staging space in CI and review diffs in pull requests: an unexpected mass change of paths, for example after a slug normalization change, is visible before it becomes thousands of redirects. After deploys, crawl the sitemap and a sample of navigation links per locale and check for 200 responses, self-referencing canonicals and reciprocal hreflang. Keep a small set of known URLs per locale, including old ones that should redirect, as a regression suite that runs on every deploy.

Worked Example

A retailer relaunched in four languages with English slugs in every locale, /de/products/hiking-boot, because the frontend built paths from the default-locale slug. German marketing asked for German URLs. The team added localized slug fields, built a route manifest from a flat all-locales query, generated redirects from the old English-slug paths to the new German ones, and moved the manifest into the sitemap and hreflang generation. The switch produced 6,400 redirects in the German locale alone, all flattened to single hops. Organic traffic in Germany dipped for two weeks during reindexing, then exceeded its previous level within two months, and support stopped hearing that German URLs looked foreign.

The team kept the redirect table under review for a year: old paths continued to receive traffic from external links and bookmarks throughout, which confirmed that redirects for changed slugs must be kept long after a relaunch.

Frequently Asked Questions

Should the default locale have a prefix?

Prefixing every locale, including the default, keeps paths symmetric and simplifies routing and caching. Unprefixed default locales give shorter URLs for the main market. Either works if applied consistently, with redirects from the other form.

Should slugs be translated?

Usually yes, for readability and search relevance in each market. Keep them stable after publication, with automatic redirects when they change.

How do we handle regional variants like en-US and en-GB?

As separate locales with their own prefixes when content differs, with fallback from one to the other for untranslated pages and hreflang listing both.

Can one page have different parents in different locales?

It is possible but rarely worth the complexity. Keep hierarchy shared across locales and localize only slugs, which keeps breadcrumbs, navigation and hreflang clusters straightforward.

How do we handle URLs with non-Latin characters?

Either transliterate slugs to ASCII, which keeps URLs easy to share, or use native characters, which browsers display but encode when copying. Choose per market with local SEO advice, apply it consistently, and never mix both forms within one locale.

Where should the route manifest be stored?

In a fast store read by the router, such as an edge key-value store or a cached data file, regenerated incrementally on publish webhooks and fully on a schedule. The router should never query the CMS per request just to map a path to an entry.

Do query parameters belong in localized routes?

No. Keep the locale and the content identity in the path, and use query parameters only for filters and pagination, with canonicals that strip the ones that do not change the content.

Should the language switcher link to the same page in other locales?

Yes, using the manifest to find the page’s path in each locale where it exists, and linking to the locale’s homepage otherwise.