How WatchMy.bike Is Built: A Solo Stack on the Edge

Marien van Os

6 min read

How WatchMy.bike Is Built: A Solo Stack on the Edge

Quick answer: WatchMy.bike is a monorepo running on Cloudflare Workers with Supabase behind it. Three workers do the real work: a React Router v7 SSR app, a Hono REST API, and a sync worker that processes Strava webhooks with a 6-hour cron as backstop. An MCP server makes it the first cycling tracker you can manage from Claude, ChatGPT or Gemini. Every choice traces back to one constraint: a single person has to run this reliably.

The interesting constraint in this product isn't scale. It's that the entire engineering department is one cyclist who also has a day job and, ideally, time left to ride. Solo plus reliable is the requirement, and everything downstream follows from it. Boring beats clever. Managed beats self-hosted. Anything that can page me at 3am has to be something I can actually fix at 3am.

I've been building for the web for 25 years, from PHP and MySQL through to TypeScript on the edge, and the main thing that career arc taught me is that architecture decisions are really maintenance decisions. You're not choosing what to build with. You're choosing what you'll be debugging in year three.

The Shape of It

One monorepo, deployed as separate Cloudflare Workers:

Piece Job
Web worker React Router v7 with SSR, the app and public pages
API worker Hono REST API, all reads and writes
Sync worker Strava webhooks, queues and the cron backstop
Satellites Smaller workers for admin, notification emails and MCP

Architecture diagram: Strava webhooks flow into a sync worker with queues and a 6-hour cron backstop, web and MCP workers talk to a Hono API worker, and Supabase holds the data
The whole system on one napkin: three workers doing the real work, Supabase behind them.

Supabase sits behind all of it: Postgres for data, its auth for login, its storage plus Cloudflare R2 for photos and documents. Stripe handles billing, Resend sends the email.

Why the edge? Cost and operations, in that order. Workers scale to zero, deploy in seconds and there is no server to patch on a Sunday. For a product whose traffic is bursty and global, per-request pricing with no idle cost is hard to argue with.

What it costs you: Workers are not Node. The code compiles to a webworker target, so any library that reaches for Node APIs is out. You feel this most in the long tail of npm, where a package works perfectly in dev tooling and then dies at the platform boundary. You learn to check the runtime story before you adopt anything.

The Sync Problem

Component tracking is only useful if the distances are trustworthy, so ride sync is the part that has to work when nobody is watching. It's also the part I've been practicing longest: I've worked with the Strava API since 2017, and the Strava Developer Summit in October 2025 sharpened my view of what a good citizen of that ecosystem looks like.

Webhooks are the primary path. When a ride lands on Strava, an event arrives, goes onto a queue, and a consumer works through it. The queue matters more than it looks: webhook processing has to survive retries, duplicate deliveries and the occasional malformed event, and a queue with a dead-letter lane turns those from incidents into log entries.

Webhooks alone are not enough. Events get missed, by every webhook system ever built. So a cron runs every 6 hours as the backstop, reconciling anything the event path dropped. The design goal is that the webhook path is fast and the cron makes it honest. Riders should never have to think about which path their ride took.

The same philosophy applies to derived numbers. Distances are recalculated from the underlying rides rather than incremented forever, because increments drift and full recalculation self-heals. When a rider deletes an activity, the totals rebuild instead of trusting a running counter.

Why MCP

WatchMy.bike was the first cycling tracker to offer MCP integration: you can manage your garage from Claude, ChatGPT or Gemini. Record a service, ask what's due, add the component you just installed, from the chat you already have open.

The reasoning is that maintenance logging dies at the friction point. The moment you're most likely to log a service is when your hands are covered in chain wax, which is the moment you're least likely to open an app and fill a form. Saying it to an assistant lowers that barrier below the give-up threshold. MCP also happens to be a standard rather than a per-assistant integration, which for a solo builder is the difference between one server and three.

It changed my own workflow more than I expected. When a part I ordered arrives and the receipt lands in my mail, I go to Claude and just ask: add the component I bought and put it in storage. Or, when I'm fitting it the same day, install it on my Scalpel or my Synapse. No forms, and the data is accurate because it comes straight from the order in front of me. Dogfooding the MCP server is also how its rough edges get found before anyone else hits them.

Solo on Cloudflare: The Honest Tradeoffs

What's great: deploys take seconds, previews are free, the platform absorbs traffic spikes without me noticing them, and the bill for all of this is closer to a hobby than a business expense. The zero-ops promise mostly holds.

What bites: local development is a simulation of the edge, not the edge, and some bugs only exist in the difference. Debugging across four workers means the log tail is your best friend. And content that gets baked into bundles at build time has to be baked into every bundle that uses it: this blog's search index ships inside three separate workers, and forgetting one deploy means the app's chat quietly serves stale posts. I know because I've done it.

What I'd tell someone starting the same way: take the managed services and the boring architecture without guilt. The clever version of this stack would impress other developers and consume the exact hours that should go into the product. Or the riding.

The Stack Is Not the Moat

None of the above is defensible, and that's fine. Anyone can rent the same workers and the same Postgres. What's harder to copy is the combination: public bike profiles where every component carries a real synced distance, the growing directory of published bikes, and the speed of one person who rides the product shipping what riders ask for. The stack's job is to keep those possible while staying out of the way.

If the product side is more your thing, the story of why this exists is in the founder post. And if you want to see the output of all this machinery, my own garage is public.


Stack questions? Reach out at marien@WatchMy.Bike.

Keep reading

Share this article

Ready to track your bikes?

Start managing your cycling gear with WatchMy.bike. Track components, get maintenance alerts, and share your builds.

Learn More