← Selected Engineering Work

World Kitchen Atlas

World Kitchen Atlas ● LIVE

A culinary encyclopedia that rates its own confidence per dish instead of presenting uncertain food history as fact.

World Kitchen Atlas culinary encyclopedia homepage
The Problem

Most recipe content online is thin: an ingredient list with little history, occasion or cultural context. And when a dish’s origin genuinely is not settled, most sites simply assert one version as fact.

Who Uses It

Public, anonymous visitors browsing world cuisine; there is no login. I do not have exported traffic numbers to quote, but it is a real, publicly reachable site with a working visit counter behind it.

Constraints

The recipe dataset lives in a private repository, and the token that can write to it can never reach the browser. Under Next.js static export, the whole build fails if any dynamic route has zero entries, so real content had to exist before the site could deploy at all. And a lot of culinary history is uncertain, so the site needed a real way to say so.

System Architecture
Next.js Static Export
Pure static HTML, CSS and JavaScript on Cloudflare Pages
Cloudflare Worker
Proxies the data repo; admin authentication and CRUD; visit tracking
Private GitHub Data Repo
One JSON file per country is the source of truth
Technical Decisions

The token never leaves the Worker. The GitHub token is a Worker secret. The Next.js build fetches recipe data through the Worker at build time, so the browser never handles a credential.

Confidence as data. Every dish carries a visible confidence rating, from verified down to thinner sourcing, instead of a hidden editorial judgement. Small nations with few well-documented dishes list only what can be confirmed rather than padding the count.

A Durable Object counter, not Workers KV. Visits are counted in a SQLite-backed Durable Object because the free-tier write limits on KV cannot survive a per-pageview counter.

A careful admin panel. Passwords are checked with PBKDF2, failed logins are throttled after 8 attempts per IP per 15 minutes, and changing the password invalidates every existing session. Write routes are locked until the default password has been changed. Every edit carries the file’s revision as an optimistic lock, so a stale edit or duplicate dish returns a 409 instead of overwriting.

Analytics without cookies. Unique visitors are counted from a one-way HMAC of IP, user agent and date that rotates daily; the raw IP is never stored, and bots are skipped.

Honest about scraping. A client-side deterrent is in the code, but the repository documents Cloudflare Bot Fight Mode and rate limiting as the controls that actually stop bulk scraping, rather than presenting the client-side layer as protection.

Validation

A dish is validated in the admin form and again in the Worker before anything is written, and the whole codebase is TypeScript. Known gap: there is no automated test suite in the repository yet.

Scale Today

The live sitemap lists 295 dish pages across 7 countries (India, Bangladesh, China, Pakistan, Sri Lanka, Nepal and Bhutan), with parallel Bangla routes. Europe, Africa, the Americas and Oceania are marked as coming soon rather than faked in.

Known Limits

Because the site is a static export, a recipe added to the data repository appears only after Cloudflare Pages rebuilds. The repository documents a data-repo-to-deploy hook as deferred work.

What I Learned

On a content-heavy project, sourcing and verifying real information is the actual bottleneck, not the code. Being upfront about what is not confirmed holds up better than presenting a thin dataset as a finished, authoritative record.