Skip to main content
StrivPath

Stop logging workouts.
Start tracking progress.

A web app for athletes to set concrete goals and track their progress after every Strava session — per-sport dashboards, real-time webhook sync, recurring goal cycles.

7 monthsevenings & weekends
Soloarch to prod
80%+test coverage
TypeScriptNestJSGraphQLNext.jsTailwind CSSPostgreSQL
strivpath.titouanauclair.com/dashboard
StrivPath global dashboard
Genesis

Built for a real need. Shipped with purpose.

Running solo in Australia, I kept hitting the same wall: Strava records every session, but gives me no way to set real goals and watch them progress. Frequency streaks, distance targets — none of it, unless you pay for a feature that barely delivers. I was tracking things in a spreadsheet.

There was another layer to it: a year abroad with a near-empty GitHub — my best projects were private, my open-source presence non-existent. StrivPath let me address both: build the tool I actually wanted, and do it the way I'd do it professionally — full stack, full discipline, no shortcuts.

Seven months of evenings and rest weeks between mine shifts later: a fully deployed SaaS, 200+ tests, automated CI/CD from commit to production. Not a prototype — a product.

13 monthsAustralia · New Zealand
StravaThird-party API — OAuth · webhooks
v1.0in production
Features

Strava records. StrivPath tracks progress.

Goal tracking

Training without a clear goal is running blind: hard to stay motivated without knowing where you're going. Strava captures everything, but data without direction is just noise. StrivPath lets you set personalized, concrete and measurable goals, tracked automatically after every session.

DistanceDurationElevationFrequency

Real-time sync

The moment you finish a session, StrivPath receives it via Strava webhook and updates your goals within seconds. No action needed. Full history importable up to 2,000 activities.

Per-sport dashboards

Global view and dedicated per-sport dashboards: running, cycling, swimming. Performance trends, personal records, activity heatmap and active goal tracking.

Activity explorer

Your full Strava history imported and filterable by sport and date. The raw data behind every goal, visible at a glance.

Accessibility & quality

Built to be usable by everyone: bilingual FR/EN interface, light, dark and system themes, keyboard navigation and screen reader support. The details that make a real product.

Architecture

Architected like a real SaaS.

Two separate applications in a Turborepo monorepo: a NestJS backend exposing a GraphQL API, a Next.js frontend consuming it exclusively. A 100% TypeScript stack: consistent, robust and production-grade. The kind of architecture a serious engineering team would choose.

GraphQL over REST

The data model is naturally nested: activities, goals, progress, statistics. REST would multiply endpoints and bloat every response: GraphQL resolves what each view needs. Frontend and backend types are auto-generated from the same schema.

A complete backend

Next.js API routes are fine for simple CRUD, not for StrivPath: async webhook processing, full OAuth layer, per-activity progress recalculation. A dedicated backend, extensible to other clients. NestJS imposes the structure Express leaves you to invent.

Next.js: React in production

React is a library, Next.js is the production framework: SSR, middleware auth, App Router. The standard for production React for a SaaS. Never used before StrivPath: from first use to production.

Turborepo monorepo

TypeScript on both sides: worth leveraging fully. A Turborepo monorepo shares config, unifies tooling and eliminates duplication across both apps. The structure a team of 10 would use, applied solo.

Challenges

Three problems worth solving.

Security

OAuth + JWT: Strava tokens never leave the backend

OAuth · JWT · httpOnly

The problem

After the OAuth callback, the backend receives the Strava tokens. Forwarding them to the frontend would expose third-party credentials to JavaScript, vulnerable to any XSS attack. The challenge: authenticate the frontend without ever handing it what it shouldn't see.

The solution

Strava tokens are stored in the database at callback time, never exposed to the frontend. The backend issues its own JWTs, set as httpOnly cookies: unreachable from JavaScript. JTI rotation: each refresh revokes the previous token, and any reuse of a revoked token revokes all sessions. Silent refresh via Apollo Link.

Real-time

Instant sync via Strava webhooks

Webhooks · Async · Security

The problem

Strava sends a POST for every new or modified activity. The app must acknowledge within 2 seconds. Past that window, Strava retries the delivery. But recalculating progress across all active goals takes longer than 2 seconds: responding in time and processing correctly are two distinct problems.

The solution

Immediate 200 OK to Strava, async processing in the background. Webhook signature verified on every delivery to reject any non-authentic request. Goal progress recalculated automatically after each new activity. Handles create, update, delete, and user access revocation.

Production

Strava API compliance: from Stravanalytics to StrivPath

API · Compliance · Privacy

The problem

Deploying with the Strava API in production requires a non-negotiable approval process: a rejection means no access. An audit of the API agreement revealed unanticipated requirements, some of which imposed structural changes to the app.

The solution

The Strava name being protected, the project was renamed from Stravanalytics to StrivPath. GPS route data was stripped from all stored activities. An access revocation webhook was implemented to delete user data on disconnect. Outbound rate limiting with proactive cutoff before hitting Strava's limits.

Gallery
Global dashboard — heatmap, goal progress, recent sessions
Goals — progress tracking and milestone view
Sport dashboard — progression charts, stats, personal records
Activity detail — pace, HR, cadence, splits
Onboarding — sport selection wizard
Quality

Shipped like a team would.

Testing

3 000+

Test cases

15

E2E scenarios

93 %

Backend coverage

82 %

Front coverage

Three layers of tests cover the project: unit tests verify business logic and React components, integration tests validate critical flows against a real PostgreSQL database (auth, webhooks, sync, goals), and E2E tests cover complete journeys from the API to the browser.

CI / CD

PR
Lint
Build
Unit
Integration
Release
E2E
Docker
Deploy

No code reaches production without passing through three gates. PRs to develop are validated automatically, while PRs to main add E2E tests as an extra safety net. On release, the full suite is re-run, Docker multi-arch images (amd64/arm64) are built, then deployment is triggered with blocking health checks on the API and the frontend.

Infrastructure

VPS · Dokploy
Traefik · Let's Encrypt
Web · Next.js
API · NestJS
PostgreSQL
Uptime Kumamonitoring
Umamianalytics

No managed services, no external dependencies. Non-root containers, isolated database, automatic TLS. Monitoring and analytics co-located — nothing sent to third parties.

Retrospective

Looking back.

What I learned

  • GraphQL at depth

    GraphQL wasn't new to me. What StrivPath added: Apollo cache hydration under SSR, the discipline of an end-to-end codegen pipeline, and the resolver trade-offs that only a complete project surfaces. That's where mastery parts ways with familiarity.

  • Next.js: learning through practice

    Next.js was completely new to me. The framework demands a choice for every component, every data fetch: server or client. Building SSR auth from scratch made that boundary concrete before I could articulate it. That's what turns Next.js from a tool into a mental model.

  • Full ownership, end to end

    Architecture, API, frontend, DevOps, compliance, design. Building alone forces clarity: no one else will untangle my shortcuts.

What I'd do differently

  • Demo mode

    The app was built as much as a portfolio piece as a real product. But without a Strava account, there's no way to see what it actually does. A pre-seeded account with mock data should have been the first priority after the MVP: it's the most impactful decision for the project's actual goal.

  • GraphQL Subscriptions

    GraphQL Subscriptions is the only part of the protocol I've never experimented with. I deliberately chose polling for the sync progress: the complexity of subscriptions was disproportionate to the need. What I'd add regardless: implement them, for the learning, not for the product.