Skip to content

Extract shared fetchJsonOrNull helper to fix silent failures on missing /data/*.json files #1829

@ericboucher

Description

@ericboucher

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-813fetchPreprocessedDates 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:76admin-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:

  1. Does a normal fetch.
  2. Returns null for HTTP 404 and for any 200 response whose content-type isn't JSON (the SPA-fallback case).
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions