Dev.to WebDev ๐Ÿ›  Dev ๐Ÿ‘ 0 ๐Ÿ“– 4 min read

I built a live "story of Earth" in one HTML file, on free open data and a free database published: true

Three weeks ago I published EarthVault, a web app where the whole planet is the game board: quiz games on real World Bank numbers, a 3D globe with live fires, storms and earthquakes, guided data stories, and a country pr

Three weeks ago I published EarthVault, a web app where the whole planet is the game board: quiz games on real World Bank numbers, a 3D globe with live fires, storms and earthquakes, guided data stories, and a country profile for every nation. It is free, has no login, no ads, works in English and Greek.

โ–ถ https://chamitro.github.io/EarthVault/ ยท code: https://github.com/chamitro/EarthVault

This post is about the engineering decisions behind it and the things that broke along the way. If you want to build something on open data, most of these will save you a day.

The stack, deliberately boring

  • One index.html (about 9,000 lines) with Tailwind from the Play CDN, Chart.js, canvas-confetti and Globe.gl loaded lazily when the globe opens. No build step, no framework. git push is the deploy, GitHub Pages is the host.
  • Supabase free tier for the only server-side state: a leaderboard, a daily log, planet records, and one cached sheet (more on that below). Everything is reached with the public key under row-level security; the browser can read five tables and insert into one.
  • A service worker that is network-first for the shell, so updates arrive on the next open. Installed phones get an "Update now" banner.

Data sources that are free, keyless and CORS-open

World Bank (indicators and country list), Our World in Data (grapher CSVs), Open-Meteo (weather, archive, marine, air, flood), USGS (earthquakes), GDACS (disasters), NASA EONET and EPIC, NOAA (COโ‚‚, space weather), GISTEMP via global-warming.org, Wikidata SPARQL, Wikipedia REST, GBIF, Where the ISS at. All of them answer a plain fetch() from a static site. That is the whole reason this app can exist without a backend.

Four things that broke, and the fixes

1. Open-Meteo rate limits. The globe colours every capital by its current temperature: 208 locations. Open-Meteo counts each location as a request, with roughly 10,000 per day per IP. Fine for one user, not for a classroom behind one NAT. The fix moved the fetch off the clients entirely: a pg_cron job in Supabase calls Open-Meteo once an hour through pg_net, folds the answer into one JSON row, and every visitor reads that row. Visitors now make zero Open-Meteo calls for the globe. Bonus: the same hourly sheet feeds "hottest capital on Earth right now" records and a daily log, with no browser open.

2. The World Bank is slow in bursts. Single-country calls take under a second. The same all-countries request took 6 s, 10 s and 34 s in a row during one test. Timeouts and one retry were not enough on phones. Every response from the slow yearly sources is now also kept in localStorage (80 most recent, under 400 KB each), and a failed call falls back to the stored copy, up to seven days old, with one small notice. The country list and the 25-measure dataset behind Country Twins have their own stores.

3. Upstream changed under me, silently. Our World in Data switched its CSV id columns from Entity,Code,Year to lowercase, which quietly emptied two of my stories. The World Bank renamed the threatened-birds indicator from EN.BRD.THRD.NO to EN.BIR.THRD.NO, so birds vanished from three vaults. Neither threw an error; the charts just lost data. Lesson: normalise headers, and keep a smoke test that asserts a minimum row count per source. I run mine headless with Chrome before every push.

4. Google Analytics with no hits. The Measurement ID from the GA admin page returned 404 from gtag/js, because the container tag has a different G- id. Only gtag('config', <container id>) sends anything. Verify with the network tab, not the dashboard.

Keeping a free database alive

Supabase pauses free projects after a week without API traffic. Real visitors cover that, but as a backstop a GitHub Actions workflow runs one curl against a public row every morning. Every hourly job also writes a public status row (ok/error, time, HTTP status), so I can check the server's health from a terminal without opening a dashboard, and a failed round retries twenty minutes later instead of waiting an hour.

Things that turned out to matter to players

  • Streak games with one forgiven mistake per streak kept people playing far longer than the unforgiving version.
  • Explain every answer. The emoji game shows what each emoji stood for; the quiz shows the real numbers and a link to the vault they came from.
  • A small animated Earth in the corner that tells live facts ("the hottest capital right now is Muscat, 35.5 ยฐC") is the most commented feature, and it costs nothing: it reuses data already in memory.

Numbers after three weeks

14 named players, about 500 rounds, two of them with every game tried. Tiny, but the whole thing runs on $0 a month, and the server has been refreshing itself every hour without a hand on it.

If you build on open data, the short version is: assume every source will be slow or change shape, cache in the browser with a stale fallback, and move anything rate-limited to a scheduled job. Happy to answer questions in the comments.

๐Ÿ“ฐ Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.