The Playbook unlocked traqqit.com →

Module 3 · ~12 min

🧱 Build static-first

You'll leave knowing how far one HTML file really goes — and how to borrow any public API without a backend.

Default posture: one HTML file

Almost every app in the fleet started as a single index.html. Static is free to host, impossible to crash, trivial to deploy (next module), and it ships today. Your default is static until something concrete forces otherwise.

What a single static file can genuinely do:

Real fleet examples. habooby = Leaflet + a wind/air-quality layer. acento = a full spaced-repetition trainer that lives in the browser with localStorage. who owns phoenix / ticket heat = MapLibre over public parcel and citation data. Every one a static single-page app — no backend for the core experience.

Data: bake it or fetch it

Two ways to get data into a static app:

💡 Baking is the lazier default: a build step you run on your machine turns messy source data into one clean JSON. Your app never depends on the source API being up.

The CORS wall — and how to walk through it

The one thing that stops a static page from fetching a public API is CORS: the data is public, but the server doesn't send an Access-Control-Allow-Origin header, so the browser blocks your fetch. You do not need a backend to fix this. You proxy the call through your own reverse proxy, which adds the header server-side.

You'll set that reverse proxy up in Module 4 — here's the shape. Expose a path on your own domain that forwards to the upstream and injects CORS:

yourapp.yourdomain.com {
    # /ext/* -> a public, keyless upstream that blocks CORS
    handle /ext/* {
        uri strip_prefix /ext
        reverse_proxy https://public-api.example.gov {
            header_up Host public-api.example.gov
            header_up -Origin
            header_down Access-Control-Allow-Origin "*"
        }
    }
    handle {
        reverse_proxy yourapp:80
    }
}

Now your static page calls /ext/whatever — same origin, no CORS — and Caddy relays it upstream and adds the header. Keyless, backend-free, a few lines. This is exactly how the fleet borrows CORS-blocked government feeds (a pollen API, a county rain-gauge feed, the Census geocoder).

⚠️ Only proxy public, keyless endpoints this way. The moment a secret key is involved, that key lives server-side (Module 5) — never in the static page, never in a public proxy.

When you actually DO need a backend

Resist until one of these is true:

Everything else stays static. A backend is a decision you justify — not a default you reach for.

Your turn ✅