Skip to content
WC26·local

How this site is built

A look under the hood at WC26 Local — what it's made of, the decisions behind it, and why they matter. Written to make sense whether or not you write code for a living, by Internet Native Mechanics Tech.

No servers to crash, no scores gone stale

Most sites that show live data run a server that rebuilds each page the moment you ask for it. This one doesn't. Every page is built ahead of time into plain HTML and served exactly as-is — closer to handing out a printed programme than printing a fresh copy for each reader. For you that means there's no server to fall over under a traffic spike, very little to hack, pages that load almost instantly, and hosting (on Cloudflare Pages) that costs next to nothing.

It's also what engineers call a hermetic build — when the site is assembled (the astro build step), it never reaches out to the internet. All 104 fixtures across 48 teams are stored as data files kept right alongside the code (in site/src/data/), so an outside service having a bad day can't break a build.

Fresh results still get in — here's how. A scheduled job on GitHub (a cron, running every few hours during the tournament) fetches the latest scores and runs them through a strict check: a tool called Zod that confirms the new data is shaped exactly as expected. If it passes, the data is saved and the site rebuilds. If anything looks off, the job stops and refuses to overwrite the good data it already has.

GitHub Actions (cron)
  → scripts/fetch-live.ts
    → Zod schema validation
      → passes: commit JSON → Cloudflare Pages rebuild
      → fails:  exit non-zero, existing JSON untouched

So if the upstream data source goes down mid-tournament, the site simply keeps showing the last good results, stamped with an "as of" time. It can never silently display wrong numbers or a broken page. (12 of 104 matches played and saved so far.)

The 2026 rules, written once and tested hard

FIFA quietly changed how group standings are settled for 2026. When teams finish level on points, the new rules look first at the results between those specific level teams — their "head-to-head" record (Articles 13a–c) — before falling back to goal difference (goals scored minus goals conceded) across the whole group (Article 13d). That's the opposite order to every World Cup since 1994. It's an easy detail to miss, and plenty of trackers got it wrong.

All of that logic — including the fiddly "best eight of the twelve third-placed teams" qualification table — lives in one self-contained file: src/lib/advancement.ts. It's a pure piece of code: give it the same input and it always returns the same answer, and it touches nothing else — no database, no network. That's exactly what makes it easy to test and trust. The standings pages and the interactive "what-if" tool (we call it the Permutator) both call the very same functions, so the rules are written once, not copied around to quietly drift out of sync.

Its test suite hammers the awkward cases real tournaments throw up: three-way ties for the last qualifying spot, three-way ties among third-placed teams, and half-finished groups mid-tournament. The exact rule is cited in a comment in the code, pinned to the paragraph in FIFA's regulations PDF.

Every possible knockout bracket, checked

Working out who plays whom in the Round of 32 — the first knockout round — is trickier than it looks. It depends not just on who wins each group, but on which eight of the twelve third-placed teams scrape through, and which groups they came from. There are 495 different ways that can shake out.

Rather than spot-check a handful of those, src/lib/bracket.ts and its tests generate all 495 combinations and confirm every team lands in the correct bracket slot in each one. (Testing against every possible case like this, instead of a few hand-picked examples, is called property-based testing.) This was the biggest surprise of the project — the official seeding table is really a puzzle to be solved, not a simple lookup.

Share a link, get a real preview card

When you paste a link into WhatsApp, X, or Slack and a little preview image with a title appears, that's powered by "Open Graph" tags (OG for short). Every one of the 12 groups and 48 teams here gets its own custom preview card — a 1200×630 image showing live standings — instead of a blank grey box.

Those 60 cards are generated automatically when the site is built, using satori (which turns a layout into an SVG — a vector image) and @resvg/resvg-js (a fast Rust-based renderer that converts that SVG into a PNG — an ordinary image file). The Inter font is bundled into the project itself, so even this step makes no outside calls, keeping the whole build hermetic — and each card reflects the standings as of that build.

What it's built with

The toolset, for the curious — nothing exotic. Each piece was chosen to be fast, type-safe, and boringly reliable.

Framework
Astro 5 — builds the site into static pages
Interactive bits
Svelte 5 — the what-if, bracket, and format explainers
Styling
Tailwind v4 via @tailwindcss/vite
Language
TypeScript, strict mode throughout
Data checks
Zod — validates every batch of incoming results
Tests
Vitest — ranking rules + bracket seeding
Hosting
Cloudflare Pages
Data source
openfootball/worldcup.json (public domain)
Share images
satori + @resvg/resvg-js
Times & zones
date-fns-tz — your local time first, stadium-local alongside

Built by Internet Native Mechanics Tech

Internet Native Mechanics Tech builds fast, dependable software and infrastructure for companies whose business runs on the internet — from early strategy through to shipping it and keeping it running, AI-ready systems included. The approach you've just read about — build it so it can't quietly break, write the hard logic once and test it relentlessly, leave nothing to chance — is the same standard we bring to client work. If your product needs that kind of care, or something harder, get in touch or visit internetnativemechanics.com.