We built Glia Quest to catch navigation failures in web applications. Then we shipped our own marketing site with one of the worst navigation coverage scores we had ever seen: 5.6% across nine language versions. Four distinct failure classes, four deploys, thirty hours. Traditional QA, code coverage, and Lighthouse caught none of it.
Navigation Coverage is the percentage of an application’s declared pages that are reachable through actual user navigation — clicking links, following menus, traversing the interface the way a real visitor would.
This is not a hypothetical case study. It is a post-mortem of our own deployment.
The setup
The Glia Quest marketing site at glia.quest is an Astro SSG with 28 pages across ten language versions. The site ships with full SEO infrastructure: hreflang tags, a locale-aware sitemap, translated navigation, and a language switcher.
On 26 April 2026, we shipped the internationalisation rollout. The build passed. The sitemap looked correct. The homepage in every locale rendered cleanly. We called it deployed.
It was not deployed. It was broken in four independent ways, each invisible to the previous one, and each requiring its own deploy to fix.
Failure 1: routes the framework generates but no navigation link reaches
The framework emitted 170 locale pages. Not one was reachable by clicking. Navigation Coverage across nine non-English locales: 5.6%.
The first deploy shipped Astro’s i18n configuration with fallbackType: 'redirect' and three pages migrated to the locale-prefix route table. The remaining seventeen pages stayed at their root English paths. The build completed successfully. The sitemap included all expected URLs. The homepage in every locale rendered with translated content.
The problem was structural. Every navigation link in the header, footer, and sidebar pointed to the English route. When a user on /zh/ clicked “About,” the link resolved to /zh/about/. But /zh/about/ was not in Astro’s locale route table — it existed only as a redirect rule that bounced the user back to /about/. The user left their locale on every single click.
A code-coverage tool would have shown 100% of the route source files exercised by the build. They were. The problem was that “exercised by the build” did not mean “reachable from a user navigation path.” The homepage worked. The LanguageSwitcher appeared to work. Every other interaction silently ejected the user from their language.
The discovery was manual. The founder clicked a non-homepage locale URL and noticed the redirect. No automated system flagged the failure.
Failure 2: a correct fix applied to the wrong architectural layer
The first diagnosis attempted the obvious Astro configuration change. The Code agent halted before applying it — the fix was correct for routes Astro controlled, but the broken URLs were not in Astro’s route table at all.
After Failure 1 surfaced, the immediate instinct was to flip fallbackType from 'redirect' to 'rewrite'. The reasoning was sound: instead of redirecting /zh/about/ to /about/, render the English content at the requested URL so the user stays in their locale loop.
The Code agent running the fix discovered during pre-flight that Astro’s fallback machinery only engages for routes with at least one declared locale variant. The seventeen non-migrated pages had zero variants. From Astro’s perspective, /zh/about/ was not a route at all. The fallbackType setting was a no-op for every URL that was actually broken.
A team could legitimately ship this fix, deploy, retest the homepage, see it still working, and conclude the site was fixed. The homepage would pass because it was one of the three migrated pages. The seventeen broken pages would remain broken, silently, because the config change never touched them.
This is the failure mode where the fix is correct in isolation but does not connect to the symptom. The debugging language was implementation-level (“flip the config”), not outcome-level (“does clicking About from /zh/ preserve the locale?”). The gap between those two framings is where this class of bug lives.
Failure 3: application correct, edge layer overrides
After migrating all seventeen pages, generating translated content for every locale, and deploying 286 pages to the server, the symptom was identical. The pages were on disk. The web server was intercepting requests before they could be served.
The full migration shipped correctly. Every locale page existed as a real translated HTML file in the build output. Local development worked perfectly — navigating the site on localhost:4321 showed every locale page rendering with translated content. The deployment to the production host succeeded without errors.
After FTP deploy, /zh/about/ still returned a 301 redirect to /about/. The symptom was byte-for-byte identical to Failure 1.
A discovery probe against the live origin identified the cause. The production host runs LiteSpeed. A pre-existing .htaccess rule was stripping locale prefixes from every sub-path request:
RewriteRule ^(de|hi|ja|zh|ko|es|zh-HK|pt|fr|ar)/(.+)$ https://www.glia.quest/$2 [R=301,L]
This rule had originally been correct. Before the i18n migration, when only the English site existed, it cleanly handled accidental locale-prefixed URLs by redirecting to the English equivalent. The migration had not touched .htaccess. The rule continued to fire, intercepting every request for a locale-prefixed sub-path before LiteSpeed checked whether the requested file existed on disk.
The newly translated pages were sitting on the host, fully formed, valid HTML, never reached.
Three independent reasons explain why no pre-deploy check caught this. Local development never exercises the host’s edge layer — npm run dev does not simulate .htaccess evaluation. CI builds also never exercise the host edge — even a Playwright-based end-to-end suite running in CI hits the dev server, not the production host. And the bug existed in the integration boundary between two systems that each looked correct in isolation: Astro emitted valid pages, LiteSpeed enforced a valid-looking rule, and the combination was broken.
Failure 4: a locale-aware migration that breaks a single-locale asset
After the .htaccess fix, an Ahrefs Site Audit surfaced 20 new 404 errors. Every one was a locale-prefixed RSS feed URL that did not exist.
The migration had wrapped every internal <a href> with a localePath() function to ensure links from localised pages go to the matching locale version of the destination. This was correct for every page link. It was wrong for one asset: the RSS feed.
The RSS feed at /insights/rss.xml is intentionally single-locale. Article bodies are English-only. The feed serves English-only content. There is no /zh/insights/rss.xml and no reason for one to exist. But the migration wrapped the RSS link along with everything else. The RSS button on /zh/insights/ now linked to /zh/insights/rss.xml, which 404’d. Every locale, both hostname variants, twenty broken links pointing at non-existent files.
The migration was the result of careful work. It successfully wrapped internal navigation in localePath() for every other link on every other page and got those right. The pattern was correct: links from a localised page should go to the matching localised version of the destination. The pattern just does not apply to single-locale assets.
This is the failure shape of a globally-applied transformation operating without knowledge of which targets are exempt. A unit test of localePath() would pass — the function correctly produced /zh/insights/rss.xml from the inputs. A component test of the RSS button would pass — the button rendered and the href was set. Only a link checker that resolved every internal href against the deployed origin would have caught it. We did not run one. Ahrefs caught it post-deploy.
The timeline
| Time | Event | Failure |
|---|---|---|
| 26 Apr afternoon | Partial i18n deploy: 3 pages migrated, 17 at root | F1 latent |
| 26 Apr evening | Founder clicks /zh/about/, reports broken | F1 visible |
| 26 Apr night | Fix prompt written with wrong architectural diagnosis | F2 |
| 27 Apr morning | Code agent halts fix, discovery identifies LiteSpeed | F2 caught |
| 27 Apr morning | Full migration deploys 286 pages | F3 latent |
| 27 Apr afternoon | Founder reports symptom unchanged | F3 visible |
| 27 Apr afternoon | .htaccess patched, redeployed | F3 fixed |
| 27 Apr evening | Ahrefs Site Audit surfaces 20 404s | F4 visible |
| 27 Apr evening | RSS reference un-wrapped, redeployed | F4 fixed |
The site was technically “deployed” three times before it actually worked. Each deploy passed the team’s existing verification: build exited zero, dist HTML was structurally complete, sample URLs were spot-checked. The deployments were not sloppy. The verification checklist was wrong — it tested for the wrong things.
What each failure means for testing
Each failure maps to a capability gap in existing QA tooling.
Failure 1 requires per-persona navigation measurement. The question “can a user on /zh/ click through the nav and stay in their locale?” cannot be answered by code coverage, by component tests, or by Lighthouse. It can only be answered by a crawl that starts at the locale homepage, follows every link, and asserts that each destination preserves the locale prefix.
Failure 2 requires outcome-based assertions, not configuration-based assertions. Instead of “is fallbackType set to 'rewrite'?”, the assertion must be “does /zh/about/ return HTTP 200 with the URL preserved?” The outcome-based assertion survives any underlying mechanism change. The configuration-based assertion is brittle to architectural assumptions that may not hold.
Failure 3 requires live-origin testing, not local or preview testing. A deploy is not complete until a post-deploy probe runs against https://glia.quest/... — not localhost:4321 and not a branch preview — and confirms the navigation contracts hold. Every site that uses an edge layer (LiteSpeed, nginx, Cloudflare Workers, Vercel middleware, AWS CloudFront) has an integration boundary where application-layer assumptions can be silently overridden.
Failure 4 requires asset-class awareness. When a locale-aware migration wraps every internal href, assets that are intentionally single-locale need an exemption mechanism. Without one, the transformation is applied uniformly and silently breaks the exempt assets. This pattern recurs in multi-tenant SaaS (tenant prefixes applied to platform-shared assets), theming systems (CSS namespaces applied to global utilities), and authentication wrappers (route gates applied to public pages).
The punchline
A Navigation Coverage scan against the first deploy would have reported 5.6% and blocked the release. We know this because we built the tool that measures it. We just had not run it against our own site yet.
The irony is precise. We spent months building a product to catch the exact class of failure we then shipped. The failures were not caused by carelessness. Every decision in the chain was reasonable given the information available at the time. The problem was that “reasonable decisions given the information available” is exactly the wrong target. The product’s job is to make better information available so reasonable decisions stop producing broken deployments.
After the fourth deploy, the site reached 100% Navigation Coverage across all ten locales. Every page in the sitemap was reachable by clicking through the navigation from the locale homepage. Every internal link resolved to a live page. The RSS feed pointed to the single-locale asset it was always meant to serve.
The distance between 5.6% and 100% was four failure classes, four deploys, and thirty hours. A single metric, measured at the right moment, would have compressed that to one deploy and one hour.
Glia Quest measures the gap between what your sitemap declares and what your users can actually reach. Test your site at glia.quest.
Frequently asked questions
What does 5.6% Navigation Coverage mean? It means that out of all the pages declared in the sitemap across nine non-English locales, only 5.6% were reachable by clicking links from any page a user could access. The remaining 94.4% existed on the server but had no inbound navigation path.
Why didn’t the Astro build catch the broken locale routes? Astro’s build system generates pages based on its route configuration. It does not verify that those pages are linked from other pages. A page can be generated, included in the sitemap, and completely unreachable through the application’s navigation.
What is an edge-layer override in the context of i18n? An edge-layer override occurs when a web server rule (such as an .htaccess rewrite, nginx redirect, or Cloudflare Worker) intercepts a request before the application can serve it. The application emits correct pages, but the edge layer redirects or rewrites the URL, making the application’s output invisible to the user.
How do you detect locale routing failures in production? The only reliable method is a live-origin crawl that starts at each locale’s homepage, follows every navigation link, and asserts that each destination preserves the locale prefix and returns HTTP 200. Local development and CI builds cannot exercise the edge layer, so they cannot detect this class of failure.
What is the difference between a phantom route and an orphan route? A phantom route is a page the framework generates but no navigation link points to — users cannot find it by clicking. An orphan route is a page that exists on the server but is not declared in the sitemap — search engines do not know about it. Both represent gaps in coverage, but they are detected by different mechanisms.