Context
PR #1828 fixes the "Dashboard configuration is not valid JSON" toast by handling the case where Firebase Hosting's SPA rewrite (** → /index.html, see frontend/firebase.json) serves HTML with status 200 for missing files. That fix is localized to fetchDashboardConfig.ts.
The same failure mode affects other /data/... JSON fetches in the frontend. They don't surface an error toast today, but they fail silently in ways that can mask real bugs.
Affected call sites
frontend/src/utils/server-utils.ts:797-813 — fetchPreprocessedDates loading data/{country}/preprocessed-layer-dates.json. If the file is missing: HTML comes back with 200, response.json() throws, the outer try/catch sets cachedPreprocessedDates = {}. The allowlist frontend/src/config/countriesWithPreprocessedDates.json currently shields us, but if it drifts from what's actually under public/data/, layer dates quietly go empty.
frontend/src/components/MapView/Layers/CompositeLayer/index.tsx:76 — admin-boundary-unified-polygon.json
frontend/src/components/ExportView/index.tsx:89, 119 — same file
frontend/src/components/NavBar/PrintImage/image.tsx:288 — same file
frontend/src/components/DashboardView/DashboardExport/DashboardExportDialog.tsx:136, 166 — same file
All five admin-boundary-unified-polygon.json fetches use .then(r => r.json()).catch(console.error). A missing file → Firebase HTML fallback → JSON parse throws → only a console error; the export/print country mask silently breaks.
Proposal
Extract a small shared helper — e.g. fetchJsonOrNull(url) — that:
- Does a normal
fetch.
- Returns
null for HTTP 404 and for any 200 response whose content-type isn't JSON (the SPA-fallback case).
- Throws distinct errors for network failures, real JSON corruption, and schema validation, so callers can tell "file not configured" apart from "file exists but is broken".
Then migrate the 6 call sites above to use it. fetchDashboardConfig.ts already implements this logic and can be refactored on top of the shared helper.
Why go this way
- Deploy-unaware. Works regardless of the hosting layer (Firebase, Netlify, Cloudflare, S3 website endpoint, local dev), so it survives any future infra move. The alternative — narrowing the SPA rewrite in
firebase.json via a negated source like !/data/** — would fix Firebase only.
- No repo churn. No need for empty placeholder JSON files per country.
- Surfaces real corruption. Today, silent
.catch(console.error) hides actual parse bugs. Explicit "missing vs corrupt" distinction lets each call site decide whether to warn.
Out of scope / later
- Consider whether the allowlist in
countriesWithPreprocessedDates.json is still needed once the helper distinguishes "missing" from "broken" — could potentially be derived at runtime.
- Consider adding a build-time validator that warns if a referenced
data/{country}/*.json file is missing, since silent misses are hard to notice.
References
Context
PR #1828 fixes the "Dashboard configuration is not valid JSON" toast by handling the case where Firebase Hosting's SPA rewrite (
** → /index.html, seefrontend/firebase.json) serves HTML with status 200 for missing files. That fix is localized tofetchDashboardConfig.ts.The same failure mode affects other
/data/...JSON fetches in the frontend. They don't surface an error toast today, but they fail silently in ways that can mask real bugs.Affected call sites
frontend/src/utils/server-utils.ts:797-813—fetchPreprocessedDatesloadingdata/{country}/preprocessed-layer-dates.json. If the file is missing: HTML comes back with 200,response.json()throws, the outertry/catchsetscachedPreprocessedDates = {}. The allowlistfrontend/src/config/countriesWithPreprocessedDates.jsoncurrently shields us, but if it drifts from what's actually underpublic/data/, layer dates quietly go empty.frontend/src/components/MapView/Layers/CompositeLayer/index.tsx:76—admin-boundary-unified-polygon.jsonfrontend/src/components/ExportView/index.tsx:89, 119— same filefrontend/src/components/NavBar/PrintImage/image.tsx:288— same filefrontend/src/components/DashboardView/DashboardExport/DashboardExportDialog.tsx:136, 166— same fileAll five
admin-boundary-unified-polygon.jsonfetches use.then(r => r.json()).catch(console.error). A missing file → Firebase HTML fallback → JSON parse throws → only a console error; the export/print country mask silently breaks.Proposal
Extract a small shared helper — e.g.
fetchJsonOrNull(url)— that:fetch.nullfor HTTP 404 and for any 200 response whosecontent-typeisn't JSON (the SPA-fallback case).Then migrate the 6 call sites above to use it.
fetchDashboardConfig.tsalready implements this logic and can be refactored on top of the shared helper.Why go this way
firebase.jsonvia a negated source like!/data/**— would fix Firebase only..catch(console.error)hides actual parse bugs. Explicit "missing vs corrupt" distinction lets each call site decide whether to warn.Out of scope / later
countriesWithPreprocessedDates.jsonis still needed once the helper distinguishes "missing" from "broken" — could potentially be derived at runtime.data/{country}/*.jsonfile is missing, since silent misses are hard to notice.References
[]placeholder files (superseded)frontend/firebase.json— the catch-all rewrite that causes the 200-HTML responses