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 calendar, coaches and run-group directory, stories, an apparel shop, and a Strava-synced elimination competition that ran the whole of July and left ten runners standing out of 270.

Live Web
Laravel Filament Strava API AhaSend Stripe
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 had 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. The alternative was tallying two hundred runners’ climbing by hand. Every night. For a month.

There was no easy way. There was a much better hard way. And I had 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 was holding TRNZ back I would 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, the 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.” It went live on trailrunningnz.com a day behind the race: 270 runners had already started climbing on the 1st of July while the DNS was still moving. Two weeks from the first commit, 158 commits and about twenty-two thousand lines, from “can you fill a spreadsheet?” to a live site with a competition running on it.

VERTual July · 270 start, 10 finish 3 Jun the email 7 Jun concept site, one weekend 2 Jul live, a day behind the race 1 Aug winner confirmed, by a human 1 Sep Strava withdraws the club feed
Ninety days, first email to the feed being switched off. The deadline at the right was in the very first email I sent back.

A leaderboard built on a feed that tells you almost nothing

The engineering heart of the site is the VERTual engine, and it is 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 a pure projection of that ledger: day N requires N×100m, eliminations fire only once a day has actually ended, and because the projection is idempotent, correcting the ledger quietly un-eliminates anyone who was wrongly knocked out. There is 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, and 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 is escalated to humans, never 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 on a coin flip. Day one was a proper firefight: private profiles invisible to the poller, walkers’ hikes not counting, the name collisions. Every fix was a ledger correction rather than surgery on live standings, and that 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 race calendar, a directory of coaches, support crew and run groups with public submission forms feeding a moderation queue, a stories section with the newsletter archive 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 is even a pixel-board mode, the standings rendered as an animated LED matrix, built for a little display and beloved on Instagram.

The directory was always the point. Bradley’s line in that first email was that it could fund the whole TRNZ ecosystem if it were done properly, and in August it started to. Stripe is on and has taken its first real payments: one-off paid race listings on the calendar, and a monthly ladder for the directory. The ladder is the more interesting build, because a listing moves down it as well as up, without anybody asking us to. A subscription belongs to a listing rather than to an account, so a physio group with three clinics is three listings on three tiers and one cancellation cannot quietly downgrade the other two. A lapsed card lowers a listing’s tier without ever unpublishing it. Every allowance is applied on read, so the surplus photos and the longer bio are hidden rather than deleted, and come straight back on re-subscribe. Pulling a business off the directory the day their card expires punishes the runner looking for them, who did nothing wrong.

Mountain Skag, Bradley’s apparel brand, came off Shopify and onto the same codebase in the same month: 24 products imported through the Shopify Admin API, guest checkout, postage rates, order fulfilment. It runs as a second merchant rather than a second product line, with its own Stripe account, its own webhook endpoint and its own GST number, because a gear receipt and a race-listing invoice must not name the same seller.

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 was not 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 were built live, in response to how people were 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. A live “numbers so far” band keeps a running count of the collective climb, tallied up into Everests. Prizes are claimed through the site itself, day-gated and checked against the board, so Bradley is not refereeing claims by DM.

The finished VERTual July board on trailrunningnz.com: 1,795,841 metres of total vert, 203 Everests, 3,122 activities, 10 of 270 still standing, and a bar chart of each day of the climb.
The board as it stands today, final numbers in. The daily bars fall off a cliff in the second week, which is what a hundred eliminations look like.

It went the distance. Of the 270 who started, 10 were still standing when the month ran out, and between them the field had climbed 1,795,841 metres, a bit over two hundred Everests, off 3,122 runs. The winner was confirmed by a human on the 1st of August, not by the machine.

Why I built it

Because I am one of them. I have 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. 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 are debugging.

What comes next is more interesting than a version bump. The club feed this engine rode on has gone: Strava removed the endpoint on the 1st of September 2026, on the schedule it had published, and asking for it today returns a 404. That deadline was in the very first email I sent Bradley, which is why VERTual July was scoped to fit inside the window rather than pretending it was not closing. The obvious replacement, every runner connecting their own Strava account, runs into platform rules that draw a far harder line around per-athlete data than around a club feed. So the next VERTual is a real design problem: how do you run a fair, live, whole-field competition once the data source that made it easy has been taken away? That is the part I am looking forward to. Bradley’s verdict, mid-build, remains the best project review I have 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