Topics
Recent articles

Developer Tools

Multilingual SEO Without Broken Hreflang

Design localized URL matrices, reciprocal hreflang, edge redirects, and scalable QA for programmatic multilingual sites.

Table of Contents10 sections
Close-up of a laptop screen displaying code, set against a dark backdrop with blue lighting for a tech-focused ambiance.
Close-up of a laptop screen displaying code, set against a dark backdrop with blue lighting for a tech-focused ambiance.

Multilingual programmatic SEO fails when route generation, localization, canonicals, sitemaps, and edge redirects are built as separate systems.

Quick solution:

Create a single locale route matrix that generates all page paths, canonical URLs, and hreflang alternates deterministically.

type PageIdentity =
  | { kind: "hub" }
  | { kind: "duration"; minutes: number }
  | { kind: "use-case"; name: string };

function alternates(identity: PageIdentity, locales: { code: string }[]) {
  return locales.map(locale => ({
    lang: locale.code,
    href: absoluteUrl(routeFor(locale.code, identity)),
  }));
}

Google provides detailed advice on Google’s localized-version guidance, while setup patterns for modern frameworks are documented in Astro’s internationalization guide.

A more reliable design starts with a locale route matrix and makes every search-facing surface consume that same matrix.

Start with page identity, not translated strings

Before translating metadata, define what makes two URLs versions of the same page.

For a timer site, a page identity might be:

type PageIdentity =
  | { kind: "hub" }
  | { kind: "duration"; minutes: number }
  | { kind: "use-case"; name: string };

Then each locale maps that identity to a path:

routeFor("en", { kind: "duration", minutes: 15 });
// /timer/15-minutes/

routeFor("es", { kind: "duration", minutes: 15 });
// /es/temporizador/15-minutos/

This looks like a routing convenience, but it becomes the core search engine optimization primitive. The same function can generate the page URL, the canonical URL, all hreflang alternates, sitemap entries, language-switcher destinations, and legacy redirect targets.

Hreflang is a graph, not a tag

Google’s documentation is explicit about a rule that breaks many implementations: each language version must list itself and the other language versions, and alternate relationships need return links.

Think of the localized pages as a graph. If English points to Spanish, Spanish needs to point back to English. For five locales, every page identity should produce the same five-node alternate set.

That makes hreflang generation deterministic:

function alternates(identity, locales) {
  return locales.map(locale => ({
    lang: locale.code,
    href: absoluteUrl(routeFor(locale.code, identity)),
  }));
}

The current page is not a special case. It is one member of the set. A good test does not just inspect one page. It generates the alternate matrix for a representative identity and verifies that every destination generates the same set in return.

Localize URLs around user language, not developer vocabulary

Translation and search intent are not identical. A product may internally call a feature a timer, while native searchers use a different common word. A direct English slug carried into every locale can be technically consistent and still feel unnatural.

The route matrix gives you space to separate internal identity from public wording:

const locale = {
  es: {
    timerRoot: "temporizador",
    duration: minutes => minutes + "-minutos",
  },
  de: {
    timerRoot: "timer",
    duration: minutes => minutes + "-minuten",
  },
};

Do not infer keyword demand from intuition alone. Validate important route vocabulary with current search research before expanding thousands of pages.

Use edge redirects for aliases, not as a second router

Localized URL structures evolve over time. You may launch Spanish pages under /es/timer/ and later decide that /es/temporizador/ is the canonical form. The wrong response is to let both variants render indexable content.

Instead, keep one canonical route in the route matrix, map old or informal aliases to it, redirect aliases permanently when the migration is stable, preserve query strings, and verify the destination does not redirect again.

At the edge, the rule can stay small:

if (pathname.startsWith("/es/timer/")) {
  return redirect(
    pathname.replace("/es/timer/", "/es/temporizador/"),
    301
  );
}

The important part is that middleware is normalizing to the canonical route model. It should not invent a parallel model.

Canonical and hreflang solve different problems

A localized Spanish page should normally canonicalize to itself, while hreflang connects it to equivalent English, German, Indonesian, or Japanese pages. Do not canonicalize every translation back to English and then expect hreflang to preserve the localized versions.

The useful mental model is that canonical answers what the preferred URL is for this page, while hreflang answers what the localized equivalents of this page are. Those are related, but not interchangeable.

Do not let the sitemap become a second source of truth

Programmatic sites often generate page files one way and sitemap URLs another way. The mismatch appears months later when a new locale or route family is added.

Generate sitemap entries from the same page identities and locale route functions:

identity
  -> routeFor(en)
  -> routeFor(es)
  -> routeFor(id)
  -> routeFor(ja)
  -> routeFor(de)

Then apply eligibility rules before emitting the URLs. A language version with missing main content should not become indexable merely because the route generator can produce a path.

Build reciprocal tests before scale

International search engine optimization errors multiply with the page count. A single bug in a template can produce hundreds of broken relationships. Before a large rollout, automate checks such as:

A compact test can model reciprocity as a set comparison rather than scraping pages by hand.

A release checklist for multilingual page systems

Before expanding to the next locale, verify these steps:

Conclusion

The scalable part of multilingual programmatic sites is consistency. When route identity is centralized, adding a locale is mostly data, such as words, slug rules, and content. When identity is scattered, every new locale is another opportunity for canonicals, alternates, middleware, and navigation to disagree. Build the route graph first so that search tags become a natural consequence of a coherent system.

Continue Exploring

You Might Also Like

View all articles
Preserve Captions in Markdown Publishing
6 min read

Preserve Captions in Markdown Publishing

Prevent image captions from leaking into article prose and insert internal links without rewriting the author's sentence around a destination title.

Fixing Zero-Click Technical Articles
5 min read

Fixing Zero-Click Technical Articles

Learn how to diagnose and resolve high-impression, zero-click search leaks on technical documentation by aligning metadata titles with developer error-intent queries.