How it was built

Building Formula One Explorer

The largest project on this site: 31 planned tasks across 8 phases, 26 commits, 96 unit tests, and a full product — learning platform, live race center, and 75 years of racing history — running on public APIs with zero configuration.

  • Next.js
  • React Server Components
  • TypeScript
  • Tailwind CSS
  • TanStack Query
  • Recharts
  • Vitest

The idea

Formula One has two audiences that never get served by the same product: newcomers drowning in jargon, and fans who want timing screens and historical depth. The brief was one application that treats both as first-class — an interactive course with the rigor of a textbook, next to a live race center with the density of a broadcast timing feed. And one constraint above all: clone, install, run. No API keys, no database to provision, no accounts.

Under the hood

Three public APIs, one resilient fetch layer

All data comes from free, keyless APIs: Jolpica (the Ergast successor) for every season since 1950, OpenF1 for live session timing, and Open-Meteo for circuit weather. Every request goes through a single fetchJson wrapper with timeouts, exponential-backoff retries, and typed ok / error results — so a failing API degrades one panel with an inline notice instead of crashing a page. Raw API shapes never leave the API layer; pages only ever see typed domain models.

Tiered caching instead of a database

The insight that removed the database: completed seasons never change. Historical data is cached indefinitely, current-season data revalidates hourly, schedules every 15 minutes, and live timing is polled client-side every 4 seconds — each tier expressed as a Next.js fetch revalidation policy. Jolpica allows ~500 requests per hour; the cache tiers keep the app comfortably inside that while feeling instant.

The live timing engine

OpenF1 exposes raw event streams — positions, intervals, laps, stints, pit stops, race control — that arrive unordered. A pure buildLeaderboard function folds them into the timing screen: latest position per driver, formatted gaps ("+1.234", "+1 LAP"), current compound and tire age, stop counts. It was built test-first, which caught a classic off-by-one (a tire fitted on lap 19 and running to lap 31 has done 13 laps, not 12). Verification against the real API also caught OpenF1 rate-limiting parallel bursts — the fix serializes the eight stream fetches with 200 ms gaps and retries 429s.

The course as typed content

All 22 lessons — sections, quizzes, flashcards, misconceptions, ten hand-drawn SVG diagrams — live as typed TypeScript modules, validated by an integrity test: every lesson must have enough sections, valid quiz answer indices, resolvable "next lesson" links. Same pattern for the 100+ term glossary, corner-by-corner circuit guides, stylized track-map SVG paths, and 75 years of champions. Content bugs fail CI like code bugs.

Business logic as pure functions

Career statistics aggregation, championship progression math, the "who can still win" simulator, a linear tire-degradation strategy model, cross-era comparison normalization (per-start rates, so Fangio's 52-race career compares fairly with Alonso's 400+), and the search index — all pure functions under lib/services/, all unit tested. The UI is a thin layer over logic that provably works.

Key design decisions

Keyless over "production stack"

The original spec suggested Supabase, PostgreSQL and Mapbox. Choosing public APIs plus cache tiers instead means anyone can run the full app in two commands — and there's nothing to leak, bill, or rotate.

Degrade panels, never pages

Every data call returns a typed result instead of throwing. A dead API turns into one inline notice; the other panels on the page keep working. The live center falls back from LIVE to REPLAY with an honest status badge.

TDD where math lives

Timing gaps, points accumulation, tire ages, title permutations — the places where silent bugs erode trust got failing-test-first treatment. 96 tests run in under a second.

Motion with an off switch

Track maps draw themselves in and leaderboard rows animate their overtakes — but every animation respects the OS reduced-motion preference plus an in-app toggle.

The Claude Code process

This was the most heavily planned build on the site: a brainstormed design spec (two decisions put to me: keyless APIs vs. full backend, and phased-full scope vs. core-only), then a 31-task implementation plan with per-task verification gates — typecheck, lint, tests, production build — and a commit after every task. The final phase ran the real app in a browser against live data, which is how the font fallback bug, the OpenF1 rate limit, and a mislabeled theme toggle were caught before shipping. The spec and plan are committed in the repo under docs/superpowers/, so the whole process is auditable.

Explore the code