← Back to home

Case study

Splitshape

AI race pacing plans for real courses.

A free web app that turns a race course, a goal time and a recent result into a km-by-km pacing plan with coach-style notes. Built solo, end to end.

Splitshape pacing plan for the Poznań Marathon: goal time, realistic finish in race-day conditions, course summary and forecast
Plan overview: goal vs. realistic finish in race-day conditions, course summary and forecast.
The same Splitshape pacing plan on a phone
The same plan on a phone.

Problem

Even splits fail on real courses. Generic pacing tools adjust for elevation at best and ignore race-day weather, wind relative to the course heading, and the late-race fade.

What I built

  • A free web app that turns a course (34 major races with verified elevation profiles, or any GPX) plus a goal time and a recent result into a km-by-km (or mile) pacing plan.
  • Coach-style notes for every segment, a printable pace band, a Garmin course export and guides for COROS and Apple Watch.
  • Solo, end to end: product, UX, code and infrastructure.
  • Built for my own sub-3 attempt at the Frankfurt Marathon (current PB 3:02). I'm user #1.
Chart of target pace for every kilometer over the course elevation profile
Pace for every kilometer over the elevation profile, with uphill sections and the modeled fade zone.
Advanced km-by-km table with grade, pace, weather adjustment and cumulative time
Advanced view: grade, pace, weather adjustment and cumulative time for every kilometer.

Architecture

App
Next.js 15 (App Router), TypeScript and Tailwind CSS, deployed on Vercel with a preview deployment per branch.
Data & auth
Supabase: Postgres plus magic-link auth.
Pacing engine
Deterministic: GPX parsing, segmentation, Grade Adjusted Pace and a late-race fade model. Metric internally, unit conversion only in the presentation layer.
Weather
Open-Meteo forecasts, including wind by course heading. One free forecast refresh before race day re-solves splits and notes together.
i18n
Full EN / DE / PL with next-intl.
E-mail & analytics
Transactional e-mail via Resend. Cookieless, self-hosted analytics (Umami).
Payments
Stripe one-time checkout implemented and kept behind a feature flag. The product is currently free.

Strava integration: planned, coming soon.

Where AI is used, and where it isn't

<Engine />

The numbers: deterministic

  • The numbers come from the deterministic engine, tested against reference fixtures from courses I've raced (Warsaw, Frankfurt).
  • An LLM should never invent a split.

<Claude />

The narrative: LLM

  • Claude writes the segment-by-segment race narrative from the engine's structured output, in the user's language (one prompt parameter, not a separate localization pipeline).
  • Model choice: Claude Sonnet, not Opus. It's production text generation on structured data, and latency matters when a user is waiting for their plan.
  • Output guardrails: hard rules in the prompt plus deterministic post-processing of the generated text, with tests enforcing copy rules.
Race strategy cards: pace range and coach-style notes for each course segment
Race strategy: pace ranges come from the engine, the segment narrative is written by Claude.

How I built it: AI-native workflow with Claude Code

Project CLAUDE.md
Domain rules, conventions and a definition of done.
Custom slash commands
A feature workflow (explore, plan, approval, branch, code, verify, preview, PR) and a UX audit command.
Code-reviewer subagent
Checks every diff against project rules: units, migrations, i18n, LLM model choice, payment flag.
Post-edit hook
Runs ESLint on changed files.
Preview before production
Every branch gets a Vercel preview, checked on mobile before merge.

Links