Back to Blog
Side Project2026.08.25· 8 min

Building Gridwatch: A Fan Timing Board for Race Weekends

The full walkthrough of Gridwatch — pages, OpenF1 + Jolpica data, the Node ingest, Vercel deploy trade-offs, and what I deliberately didn’t scrape.

Next.jsOpenF1TypeScriptWebSocketF1

I wanted a timing board for race weekends that I actually liked looking at — positions, gaps, tyres, a sense of the championship, and a simple podium pick with friends. Not a clone of F1 TV. Not covered in official logos. Just a fan tool that stays out of the way when the lights go out.

That’s Gridwatch. It’s live at gridwatch.ayushd70.dev. It’s a fan project — not F1, not the FIA, not a team — and the README says that on purpose.

The product surface

There are a handful of pages, each with one job. Live timing shows the current (or last) session: gaps, sectors, pit count, weather, race control, and a rough live championship estimate when OpenF1 has one. Standings, results, and calendar are the post-session tables — the stuff you check after the flag, not while you’re watching the fight for P3. Predict is the social bit: lock a podium before lights out, score it later, and keep a light form guide based on standings and the last race (not a fantasy ML model).

How Predict scores

Exact P1 / P2 / P3 are worth 5 / 3 / 1. If you had a driver on the podium but in the wrong slot, you still get a consolation point. It’s meant to be something you can do on your phone five minutes before the formation lap, not a season-long fantasy league.

Data: the awkward part of F1 side projects

Formula 1 doesn’t ship a public developer API. A lot of hobby boards scrape livetiming.formula1.com / SignalR. I didn’t. FastF1’s own docs are pretty clear that feed is for recording, not for shipping a product. Breaking on every weekend patch wasn’t interesting to me.

OpenF1 for the session

OpenF1 covers what’s happening (or just happened) in a session: order, intervals, laps, stints, pits, weather, race control, and an in-race championship estimate. Historical REST is free. “Live” is roughly thirty minutes before a session until thirty minutes after, and that tier wants a sponsor login. Delay is usually a couple of seconds. REST lives at api.openf1.org; live MQTT is mqtts://mqtt.openf1.org:8883.

Jolpica for the books

Once a race is classified, Jolpica (the Ergast successor) is the source of truth for driver/constructor standings, the calendar, and race / qualifying / sprint results. No API key. Base URL is the Ergast-compatible jolpi.ca endpoint. Timing drama belongs to OpenF1; classified tables belong to Jolpica.

Architecture

The repo splits into three obvious pieces. server/ is a Node ingest: one OpenF1 connection, token refresh about every fifty minutes, and a single timing snapshot fanned out over WebSocket. src/app/ is the Next.js UI. shared/ holds the snapshot shape both sides agree on. Locally, npm run dev runs the site on :3000 and ingest on :4001 together.

Credentials stay on the server

Browsers never talk to OpenF1 with your password. If they did you’d rate-limit yourself and leak the account. The ingest is the only process that logs in. If a session is live and you’re not authenticated, OpenF1 returns a lock message — Gridwatch falls back to a labeled sample board so the UI still works while you’re iterating.

Theme and polish

There’s a light/dark toggle in the header. It remembers your choice; if you haven’t picked one, it follows the OS. I kept branding deliberately generic — no stolen marks, no fake “official” chrome. The screenshots in the README cover both themes so the gallery isn’t dark-mode only.

Deploying next to the portfolio

ayushd70.dev stays on GitHub Pages. Gridwatch is only the subdomain so DNS doesn’t fight. The Next.js app deploys on Vercel. On Squarespace DNS you add a CNAME for host gridwatch → cname.vercel-dns.com. You leave the apex A records pointing at GitHub Pages alone. Do not flip the whole zone to a Vercel nameserver preset or the portfolio disappears.

What production does not do

Vercel can’t run the always-on MQTT ingest. In production, NEXT_PUBLIC_INGEST_WS stays unset and the timing page polls OpenF1 REST through /api/timing about every thirty seconds. Podium picks live in the browser — no writable disk on Vercel. For true live MQTT you’d still run server/ on something like Fly or Railway and point the client at that WebSocket.

Stack, briefly

Next.js and React on the front, TypeScript everywhere it matters, mqtt + ws in the ingest, Tailwind for UI, Vercel Analytics in production. Node 20+. It’s a weekend-shaped codebase on purpose: small enough to reason about during FP3, structured enough that the ingest and the UI don’t share secrets by accident.

If you want to poke it

Clone the repo, npm install, copy .env.example, leave OpenF1 blank for historical sessions, and open localhost:3000. Live weekends need an OpenF1 sponsor account in .env. Personal / educational use — check OpenF1’s terms before you do anything commercial with their feed. The board itself is at gridwatch.ayushd70.dev.