Get in touch

From the workshop

Trail Running NZ

The home of all things trail running in Aotearoa.

A full rebuild of trailrunningnz.com for Bradley Houghton — race & run-group directory, stories, and a live Strava-synced elimination competition, shipped in three weeks.

Live Web
Laravel Filament Strava API Resend Stripe-ready
The build story

The build story

The unicycle and the Ferrari.

On the 3rd of June 2026 an email arrived from Bradley Houghton — race director, and the writer behind the Trail Running NZ newsletter I’d been happily subscribed to for years. He was planning VERTual July: a daily-elimination vertical-gain challenge, and wanted to know if there was “a real easy way” to pull everyone’s daily vert from Strava into a Google Sheet, because the alternative was tallying two hundred runners’ climbing by hand. Every night. For a month.

There wasn’t a real easy way. There was, however, a much better hard way — and while I was at it, I’d noticed his Squarespace site was fighting him. So I made an offer: forget the spreadsheet, let me build the leaderboard properly — and if the site itself ever felt like it was holding TRNZ back, I’d genuinely enjoy rebuilding the whole thing. His reply: “Yes to rebuilding the site — it’s holding me back… I have to relearn everything every time I try pick it up. In conclusion: Yes.”

One weekend later there was a working concept site — competition already wired to the Strava club, admin backend and all. Bradley’s review: “Ok you’re a wizard… that site is mint. The back end is SO user friendly.” Eleven days after that it was live on trailrunningnz.com, and the day after that, 270 runners started climbing. Three weeks, 178 commits, roughly sixteen thousand lines — from “can you fill a spreadsheet?” to race day.

A leaderboard built on a feed that tells you almost nothing

The engineering heart of the site is the VERTual engine, and it’s shaped by one brutal constraint: the Strava club feed gives you no activity ID, no date, and no athlete ID — just “Mike L. climbed 412m”, roughly in order. So every activity is fingerprinted from its identity and metrics into an append-only ledger, its day decided by when it was first seen (with a grace window around midnight for late uploads). A poller runs every fifteen minutes; the standings are then computed as a pure projection of that ledger — day N requires N×100m, eliminations only fire once a day has actually ended, and because the projection is idempotent, correcting the ledger quietly un-eliminates anyone who was wrongly knocked out. No fragile state to repair, ever.

CLUB FEED “Mike L. climbed 412 m” no id, no date FINGERPRINT who + what = identity dedupe key LEDGER append-only, day-stamped the one truth PROJECTION day N needs N × 100 m recompute, idempotent LEADERBOARD safe · unsafe · out what 270 runners see polled every 15 min HUMANS IN THE LOOP two Mike L.s? the review queue waits for a human wrong vert? one fix — the wrongly-out come back ambiguous? looks wrong? the fix re-enters the ledger solid arrows — data, every 15 minutes  ·  dashed arrows — humans stepping in
The whole engine is a projection of one append-only ledger — there is no state to break, only history to correct.

Ambiguity gets escalated to humans, not guessed at. When the feed says “Mike L.” and there are two Mike L.s entered — which happened, twice over, on day one — the activity lands in a review queue in the admin with the candidates lined up, and nobody gets eliminated over a coin flip. Day one was a proper firefight (private profiles invisible to the poller, walkers’ hikes not counting, the name collisions), and the fact that every fix was a ledger correction rather than surgery on live standings is what made it survivable.

It adds up — the least a survivor has climbed

20,000 m 40,000 m Everest — 8,849 m, passed on day 13 Day 1 — at least 100 m climbed since day 1 Day 2 — at least 300 m climbed since day 1 Day 3 — at least 600 m climbed since day 1 Day 4 — at least 1,000 m climbed since day 1 Day 5 — at least 1,500 m climbed since day 1 Day 6 — at least 2,100 m climbed since day 1 Day 7 — at least 2,800 m climbed since day 1 Day 8 — at least 3,600 m climbed since day 1 Day 9 — at least 4,500 m climbed since day 1 Day 10 — at least 5,500 m climbed since day 1 Day 11 — at least 6,600 m climbed since day 1 Day 12 — at least 7,800 m climbed since day 1 Day 13 — at least 9,100 m climbed since day 1 Day 14 — at least 10,500 m climbed since day 1 Day 15 — at least 12,000 m climbed since day 1 Day 16 — at least 13,600 m climbed since day 1 Day 17 — at least 15,300 m climbed since day 1 Day 18 — at least 17,100 m climbed since day 1 Day 19 — at least 19,000 m climbed since day 1 Day 20 — at least 21,000 m climbed since day 1 Day 21 — at least 23,100 m climbed since day 1 Day 22 — at least 25,300 m climbed since day 1 Day 23 — at least 27,600 m climbed since day 1 Day 24 — at least 30,000 m climbed since day 1 Day 25 — at least 32,500 m climbed since day 1 Day 26 — at least 35,100 m climbed since day 1 Day 27 — at least 37,800 m climbed since day 1 Day 28 — at least 40,600 m climbed since day 1 Day 29 — at least 43,500 m climbed since day 1 Day 30 — at least 46,500 m climbed since day 1 Day 31 — at least 49,600 m climbed since day 1 100 m 49,600 m Day 1 Day 10 Day 20 Day 31
Day N demands N×100 m, so the minimum-possible total compounds: survive the whole month and you’ve banked at least 49,600 m — five and a half Everests. 270 runners started; by day five, a hundred were already out.

The rest of the platform

The competition is the headline act, but the point of the rebuild was everything around it: a races and run-groups directory with public submission forms feeding a moderation queue (Bradley’s long-term plan for funding the whole TRNZ ecosystem), a stories section with the newsletter archive auto-synced from Substack, sponsor and prize management, and email alerts that are deliberately quiet — the poller emails once when it breaks, not every fifteen minutes while it stays broken. There’s even a pixel-board mode: the live standings rendered as an animated LED matrix, built for a little display and beloved on Instagram. Stripe is scaffolded and waiting behind config for the day the directory starts paying its way.

Above all of it sits an admin panel Bradley runs entirely himself — approving submissions, managing prizes, fixing a day’s vert, linking ambiguous activities. The thing Squarespace never gave him wasn’t a prettier site; it was the feeling of being able to drive.

The part that grew while everyone watched

Once the eliminations started, the leaderboard turned into a spectator sport — and a lot of the best features got built live, in response to how people were actually using it. The last runners standing earned proper profiles, and anyone with a verified account can drop a cheer in their corner; every comment clears an AI moderator first, because a public wall on a tight-knit community still needs a bouncer. The whole board installs as an app and — if you opt in — buzzes your phone the instant someone survives another day. (The public side is all delight and notifications; the admin side, as above, stays deliberately silent.) A live “numbers so far” band keeps a running count of the collective climb — the whole field’s vertical gain, tallied up into Everests — and prizes are claimed through the site itself, day-gated and auto-checked against the board, so Bradley isn’t refereeing claims by DM.

Why I built it

Because I’m one of them. I’ve run these hills for years, Bradley’s newsletter has been a bright spot in my inbox since 2023, and the NZ trail community is small enough that giving something back to it actually moves the needle. This one was built for the fun of it — and I entered VERTual myself, walking my vert while the site counted it. There is something uniquely motivating about being a user of the leaderboard you’re debugging.

Next up is more interesting than a version bump. The club feed this whole engine rides on is a Strava feature that switches off in September 2026 — and the obvious replacement, every runner connecting their own Strava account, runs straight into Strava’s platform rules, which draw a far harder line around per-athlete data than around a club feed. So the next VERTual isn’t a migration onto a shinier API; it’s a real design problem — how do you run a fair, live, whole-field competition once the data source that made it easy is going away? That’s the part I’m actually looking forward to. Bradley’s verdict, mid-build, remains the best project review I’ve ever had: “This already feels like a Ferrari compared to the unicycle I was planning on.”

Got a community platform stuck on a site builder that's holding it back? Here’s how we could work together.

Visit Trail Running NZ