If you have shipped internationalisation in a web framework, you have probably had the conversation: “the locales work on localhost but not in production.” The bug reports are vague. The symptoms look intermittent. The root causes are structural — and they are invisible to your test suite.
i18n deployment failures are a class of production defect caused by the interaction between framework routing, build output, edge-layer configuration, and asset classification. They are structural, not logical, and they resist detection by any test that runs before or outside the production environment.
We shipped four of these failures in thirty hours while deploying our own marketing site. The full post-mortem is published. This article extracts the four failure classes as reusable patterns — named, defined, and mapped to detection signals.
The four failure classes
Every i18n deployment failure we have encountered or documented fits into one of four categories. Naming them gives teams a shared vocabulary for debugging conversations. “We have a phantom routes problem” is more actionable than “the i18n is broken.”
Phantom routes — pages the framework generates but no navigation link points to.
Layer mismatch — a correct fix applied to the wrong architectural layer.
Edge-layer override — application routing silently overridden by the web server, CDN, or middleware.
Uniform transformation — a globally applied migration that breaks assets requiring exemption.
Failure 1: phantom routes
A phantom route is a page that exists in the build output, appears in the sitemap, and returns HTTP 200 when accessed directly — but has no inbound link from any navigation element on any other page.
Every framework that supports i18n routing generates locale-prefixed pages at build time. Astro creates dist/zh/about/index.html. Next.js generates pages at /zh/about through its i18n routing configuration. Nuxt produces the same structure via its @nuxtjs/i18n module. The pages are real. The framework considers them generated. The sitemap includes them.
The problem arises when the navigation components — header menus, sidebar links, footer links, language switchers — are not updated to point to the locale-prefixed versions. This can happen in several ways. A partial migration that moves some pages into the locale route table but not others. A navigation component that hardcodes English paths instead of using a locale-aware path helper. A language switcher that rewrites only the current page’s URL without accounting for sub-pages.
The result is a locale homepage that works and every subsequent click that ejects the user from their locale. The pages exist. The framework generated them. No user can reach them by clicking.
Detection requires a per-locale navigation crawl: start at the locale homepage, follow every link in the rendered navigation, and assert that each destination preserves the locale prefix. Pages in the sitemap that the crawl does not reach are phantom routes. In our own deployment, this crawl would have reported 5.6% Navigation Coverage across nine locales — 170 pages existed, fewer than 10 were reachable.
Failure 2: layer mismatch
A layer mismatch occurs when the debugging diagnosis correctly identifies the symptom but prescribes a fix at the wrong architectural layer — a layer that does not control the URLs that are actually broken.
After discovering phantom routes, the natural first instinct is to fix the framework configuration. In Astro, this might mean changing fallbackType from 'redirect' to 'rewrite'. In Next.js, it might mean adjusting the i18n configuration in next.config.js. In Nuxt, it might mean modifying the @nuxtjs/i18n module options.
The fix can be technically correct for the routes it controls while having zero effect on the routes that are broken. If the broken routes are not in the framework’s locale route table — because they were never migrated, because they are generated by a different system, because they are served by a CDN rather than the framework — the configuration change is a no-op.
The danger is that the fix feels correct, deploys successfully, and passes a spot check against the homepage (which was likely already working). The team concludes the issue is resolved. The broken pages remain broken, silently, because nobody retested them.
Detection requires outcome-based assertions rather than configuration-based assertions. Instead of verifying “is fallbackType set to 'rewrite'?”, the assertion should be “does /zh/about/ return HTTP 200 with the URL preserved?” The outcome-based assertion survives any change to the underlying implementation. The configuration-based assertion assumes the configuration controls the URL, which may not be true.
Failure 3: edge-layer override
An edge-layer override occurs when the web server, CDN, or middleware intercepts a request and rewrites or redirects it before the application can serve the response. The application is correct. The deployment succeeded. The user sees broken behaviour.
This is the most universal i18n failure class because every production deployment involves at least one layer between the application and the user. Apache’s .htaccess, nginx’s location blocks, Cloudflare Workers, Vercel middleware, AWS CloudFront behaviours, LiteSpeed rewrite rules — all can silently override the application’s routing decisions.
In our deployment, a pre-existing .htaccess rule was stripping locale prefixes from every sub-path request. The rule had been correct before the i18n migration: when only the English site existed, it cleanly handled accidental locale-prefixed URLs. After the migration shipped 286 locale pages, the same rule redirected every locale URL back to its English equivalent. The translated pages were on the host, fully formed, never reached.
This failure class has three properties that make it particularly resistant to detection. Local development environments do not exercise the edge layer. CI pipelines running against preview deployments do not exercise the production edge layer. And the bug exists in the integration boundary between two systems that each appear correct in isolation — the application emits valid pages, the edge rule enforces a valid-looking redirect, and the combination is broken.
Detection requires a live-origin crawl against the production URL. Not localhost. Not a preview deployment. Not a staging environment with a different edge configuration. The production origin, with its production edge rules, probed with the same requests a real user’s browser would send.
Failure 4: uniform transformation
A uniform transformation applies a globally scoped change — such as locale-prefixing every internal link — without an exemption mechanism for assets that should not be transformed.
When a locale-aware migration wraps every internal <a href> with a locale helper function, the transformation is correct for page links and incorrect for single-locale assets. RSS feeds, API endpoints, sitemaps, webhook URLs, and other machine-readable assets are typically single-locale by design. They do not have locale-prefixed variants. When the transformation wraps them anyway, the result is links pointing to URLs that do not exist.
In our deployment, the migration wrapped the RSS feed link on locale pages with localePath(), producing /zh/insights/rss.xml — a file that was never generated and would not make sense to generate (the RSS content is English-only). Twenty broken links across ten locales, all generated by a correct-in-aggregate transformation applied without exemption awareness.
The failure shape extends beyond i18n. Multi-tenant SaaS applications that add tenant prefixes to all URLs encounter the same pattern with platform-shared assets. Theming systems that namespace all CSS classes encounter it with global utility classes. Authentication wrappers that gate all routes encounter it with intentionally public pages.
Detection requires an asset-class exemption registry: a maintained list of assets that are excluded from the transformation, combined with a post-transformation link check that resolves every internal href against the deployed origin. Assets that 404 after transformation are flagged.
Why these failures compound
The four classes cascade. Fixing one reveals the next. The deployment timeline is non-linear.
In our thirty-hour deployment cycle, each fix resolved the visible symptom and uncovered a deeper structural issue:
Fixing phantom routes (by migrating all pages into the locale route table) revealed the edge-layer override (the .htaccess rule that was stripping locale prefixes). Fixing the edge-layer override revealed the uniform transformation (the locale-wrapped RSS feed links that 404’d). The layer mismatch was caught before deployment only because the Code agent halted during pre-flight — without that halt, it would have added a fourth failed deploy to the timeline.
Each “fix” that addresses the visible symptom may mask a deeper structural issue. A team that tests only the symptom they just fixed — “does the homepage still work in /zh/?” — will miss the next failure class in the cascade. The verification must be comprehensive: every page, every locale, every link, every asset.
Detection framework
For each failure class, there is a specific signal, a specific layer where it is detectable, and a specific point in the deploy pipeline where the check should run.
Phantom routes are detected by a per-locale navigation crawl that compares reachable pages against the sitemap. The check runs post-build, before deploy. The signal is the gap between sitemap URLs and crawl-discovered URLs.
Layer mismatch is detected by outcome-based assertions that test user-observable behaviour rather than configuration values. The check runs as part of the fix verification process. The signal is a passing config check alongside a failing outcome check.
Edge-layer override is detected by a live-origin probe that fetches production URLs and verifies HTTP status codes and response headers. The check runs post-deploy, against the production origin. The signal is a redirect or rewrite that does not appear in local development.
Uniform transformation is detected by resolving every internal link on every page against the deployed origin and checking for 404 responses. The check runs post-deploy. The signal is a broken link on a locale page pointing to a non-existent locale-prefixed asset.
All four checks share a common requirement: they must run against the live production environment, not against local builds or preview deployments. The edge layer, the host configuration, and the deployed file system are all part of the user’s experience. Testing without them is testing half the system.
Glia Quest detects all four failure classes automatically. The locale probe runs on every scan at no additional cost. Test your deployment at glia.quest.
Frequently asked questions
Which i18n frameworks are affected by these failure classes? All of them. Phantom routes, layer mismatches, edge-layer overrides, and uniform transformations are architectural patterns, not framework-specific bugs. We have documented these patterns in Astro, but the same structures exist in Next.js, Nuxt, SvelteKit, Remix, and any framework that generates locale-prefixed routes.
Can I detect edge-layer overrides without deploying to production? Not reliably. The defining characteristic of an edge-layer override is that it only manifests when the request passes through the production edge layer. You can maintain a staging environment with an identical edge configuration, but the configuration must be actively synchronised — and configuration drift between staging and production is itself a common source of edge-layer overrides.
What is the difference between a phantom route and a broken link? A phantom route is a page that exists but has no inbound link — it is unreachable. A broken link is a link that exists but points to a page that does not — the user reaches a 404. Both are navigation failures, but they are inverse problems. Phantom routes are discovered by comparing the sitemap against the crawl. Broken links are discovered by resolving every link on every page against the deployed origin.
How do I maintain an asset-class exemption registry? Start with a simple list of URL patterns that should not be locale-prefixed: RSS feeds, sitemap files, API endpoints, webhook URLs, and any other machine-readable asset. Store the list in your project configuration alongside your i18n settings. When a migration wraps internal links, the transformation should check each href against the exemption list before applying the locale prefix.
Do these failure classes apply to subdomain-based i18n (e.g., de.example.com)? Yes, with modifications. Phantom routes become pages on the subdomain with no inbound link from the subdomain’s navigation. Edge-layer overrides become DNS or reverse proxy rules that redirect subdomain requests incorrectly. Uniform transformations become link-wrapping that produces subdomain URLs for single-domain assets. The structural patterns are the same; the URL format differs.