Symptom
Pages return HTTP 200 with an empty article body — shell, menu and title render, article does not. Invisible to link checkers because it is a 200.
Not uniform across languages. Measured 2026-08-11 by matching source sentences against the served RSC payload:
| URL |
|
/using-zcash/zimppy · /guides/shapeshift-zcash · /zcash-organizations/zodl |
empty |
/it/… for those same three |
render fine |
/using-zcash/zecmap and /it/using-zcash/zecmap |
empty (no Italian file) |
A regression: Wayback has /guides/shapeshift-zcash rendering in full on 2026-04-14.
Cause
getFileContentCached (src/lib/authAndFetch.ts) wraps its body in catch { return null } and is registered with unstable_cache(..., { revalidate: false }) — cache forever. One transient GitHub contents-API failure (403/429, 5xx, blip, expired token) yields null, cached permanently for that path. [locale]/[...slug]/page.tsx cannot tell that from "page absent" and renders an empty body.
Why translations mostly survive: getLocalizedFileContentCached routes non-English to getTranslationProbeCached, a different cache that has always had revalidate: 300, so it self-heals in five minutes. Only a locale with no file of its own falls through to the TTL-less cache — which is where English always goes. That is exactly the table above, zecmap included.
Which pages break is request timing, not content. An earlier version of this issue listed 36 pages and blamed slug→filename reconstruction; both were wrong. A sweep since finds blanks among filenames that reconstruct cleanly and ones that don't.
Also ruled out directly: transformUri(), the case-insensitive folder fallback, and MDX serialization.
Fix
#721 — null only for a genuine 404, rethrow everything else so nothing is cached; revalidate: 3600; owner/repo/branch in the cache key. It also closes a second, independent mechanism: a directory response and a >1 MB blob both decoded to "", indistinguishable from a missing page.
Worth doing separately: a CI check walking sitemap.xml asserting each page serves an article body.
Symptom
Pages return HTTP 200 with an empty article body — shell, menu and title render, article does not. Invisible to link checkers because it is a 200.
Not uniform across languages. Measured 2026-08-11 by matching source sentences against the served RSC payload:
/using-zcash/zimppy·/guides/shapeshift-zcash·/zcash-organizations/zodl/it/…for those same three/using-zcash/zecmapand/it/using-zcash/zecmapA regression: Wayback has
/guides/shapeshift-zcashrendering in full on 2026-04-14.Cause
getFileContentCached(src/lib/authAndFetch.ts) wraps its body incatch { return null }and is registered withunstable_cache(..., { revalidate: false })— cache forever. One transient GitHub contents-API failure (403/429, 5xx, blip, expired token) yieldsnull, cached permanently for that path.[locale]/[...slug]/page.tsxcannot tell that from "page absent" and renders an empty body.Why translations mostly survive:
getLocalizedFileContentCachedroutes non-English togetTranslationProbeCached, a different cache that has always hadrevalidate: 300, so it self-heals in five minutes. Only a locale with no file of its own falls through to the TTL-less cache — which is where English always goes. That is exactly the table above,zecmapincluded.Which pages break is request timing, not content. An earlier version of this issue listed 36 pages and blamed slug→filename reconstruction; both were wrong. A sweep since finds blanks among filenames that reconstruct cleanly and ones that don't.
Also ruled out directly:
transformUri(), the case-insensitive folder fallback, and MDX serialization.Fix
#721 —
nullonly for a genuine 404, rethrow everything else so nothing is cached;revalidate: 3600;owner/repo/branchin the cache key. It also closes a second, independent mechanism: a directory response and a >1 MB blob both decoded to"", indistinguishable from a missing page.Worth doing separately: a CI check walking
sitemap.xmlasserting each page serves an article body.