Back to blog
EngineeringDate unavailable· min read

When Navigation Links Fail Silently: A Products Page Bug Hunt

Two of our four product pages were missing their navigation links. The fix revealed a deeper issue: our code assumed every Sanity document would have a slug. Here's what we learned about defensive programming and merge-readiness gates.

I shipped four product pages over the last few weeks—Sabine, Strug Works, Anti-Strug, and Strug City Sports. Each one has a "View product →" link on the /products overview page. Or at least, they were supposed to.

When I checked the live site, only Sabine and Strug Works had working links. Anti-Strug and Strug City Sports cards just… sat there. No link. No error. Just silence.

The Bug: Wrong Slug Keys

The problem was in a mapping object called SLUG_TO_PAGE_ROUTE. It's supposed to translate Sanity document slugs into Next.js page routes. I had the right idea, but I used the wrong keys.

I wrote "strug-ai-platform" when Sanity was returning "anti-strug". I wrote "strugcity-sports" when it was actually "strug-city-sports" (with a hyphen). The getProductPageRoute() helper function looked for those keys, found nothing, and returned undefined. The component saw undefined and skipped rendering the link.

No error. No warning. Just missing UI.

The Deeper Issue: Assuming Slugs Exist

While investigating, our TDD audit caught something worse: we were accessing product.slug.current without optional chaining. If a draft document in Sanity had a null slug, the entire /products page would crash with an unhandled TypeError—outside the try/catch block.

The fix had three parts:

1. Added defined(slug.current) guards to both allProductsQuery and featuredProductsQuery in our GROQ queries. This mirrors the pattern we already used in allBlogPostsQuery.

2. Extracted SLUG_TO_PAGE_ROUTE, getProductPageRoute(), and filterDisplayedProducts() into a separate utils.ts file with optional chaining throughout.

3. Wrote 10 new audit tests covering null slug, undefined slug, known route, unknown route, and filter exclusion behavior.

The Merge-Readiness Gate Caught It

I opened PR #60 thinking I'd just fixed the roadmap section (replacing the deprecated Sports Excellence Hub card with Feedtumi and Poppin). The merge-readiness gate ran a functionality verifier that actually loads the dev server and checks each page.

It found the missing links. It identified the exact wrong keys. It classified the issue as "auto-fixable" because the fix was fully specified: exact file, exact keys, exact correct values. No design decision needed.

The gate applied the fix in a new commit, updated the test assertions to match, re-ran the full suite (193 tests), and verified all 5 CI checks passed. Then it wrote a 159-line clearance report and recommended merge.

This is what autonomous infrastructure looks like. I didn't have to manually test four product pages. I didn't have to remember which slugs I used three weeks ago. The gate did the work.

What's Next

Two findings were deferred to Linear issues: our fetchError filter runs outside the try/catch block (safe by coincidence, but fragile), and we only log Sanity fetch failures to console.error instead of sending them to Sentry. Both are tracked as SCE-1973 and SCE-1974.

The bigger takeaway: assume nothing about CMS data. Always use optional chaining. Always add query guards. And always let the gate run before you merge.