Skip to content

Bike Trip Planner

Plan your bikepacking adventures with confidence.

Paste a Komoot, Strava or RideWithGPS link, or upload a GPX file, and get a day-by-day roadbook with smart pacing, safety alerts, accommodation suggestions and weather. Take it on the road with the web app or the native mobile app, or let an AI agent plan it for you over MCP.

New here? Follow Plan your first trip. Setting up the code? Start with Getting Started.

Architecture overview

Web (Next.js 16) / Mobile (Expo)      PHP backend (API Platform 5 on Symfony 8.1)
  @btp/core: shared types + SSE         REST API + MCP server (OAuth 2.1)
  reducers                              GPX parsing + pacing engine
  Zustand + Immer (in-memory)           Valhalla routing + PostGIS reference data
  openapi-fetch (typed)                 Async workers (Symfony Messenger)
  Mercure SSE (real-time)        <--    Mercure publisher, Open-Meteo weather
                                        PostgreSQL 18 + Redis

The clients send trip requests over REST; the backend computes the stage breakdown, then enriches it asynchronously across WORKER_REPLICAS workers and pushes updates over Mercure SSE. PostgreSQL persists accounts, trips and stages, and holds the OpenStreetMap / DataTourisme reference index (PostGIS); Redis holds transient computation state, the Messenger transport and external API caches.

Type safety is enforced end-to-end: PHP DTOs define the schema -> API Platform exports an OpenAPI spec -> npm run typegen generates TypeScript types into the shared @btp/core package -> openapi-fetch provides type-safe API calls in both the web and mobile apps.

See Architecture for the full picture and the reasoning behind each choice.

Tech stack

Layer Technology
Backend PHP 8.5, Symfony 8.1, API Platform 5.0, Doctrine ORM 3, FrankenPHP (Caddy + Mercure)
Data PostgreSQL 18 + PostGIS, Redis 8, Valhalla (routing)
Web Next.js 16 (App Router), React 19, TypeScript (strict), Tailwind CSS 4
Mobile Expo SDK 57, React Native 0.86, MapLibre React Native
State Zustand + Immer (in-memory), Mercure SSE (real-time)
Testing PHPUnit 12 (api/) and 13 (provisioner/), Playwright 1.63, Jest (mobile)
Quality PHPStan level 9, PHP-CS-Fixer, Rector, ESLint, Prettier
Async Symfony Messenger, Redis transport, WORKER_REPLICAS workers (2 by default)

Explore the documentation

Section Page For
Tutorials Plan your first trip Riders: from a route link to an exported roadbook
Getting Started Developers: run the stack locally
How-to guides Connect an AI agent Plug Claude or another MCP client into your account
Contributing Development workflow, standards, and tooling
Deployment CI/CD pipeline, secrets, rollback
Runbooks Operations playbooks: workers, DB, Redis, Mercure, zones, releases
Reference Features What the product does, by area
Supported route sources Accepted Komoot / Strava / RideWithGPS / GPX inputs
Accommodation types Logical types, OSM tags, pricing heuristic
Alert engine Every alert code: severity, priority, trigger
External data sources OSM, DataTourisme, OpenAgenda, Wikidata, Open-Meteo, Nominatim
MCP tools The tools an AI agent can call
Mobile Mercure auth SSE authentication for the native app
Claude Code tooling MCP servers, hooks, and skills for AI-assisted development
Legal & Licensing Licence, data attribution, GDPR posture
Explanation Architecture System overview and the reasoning behind it
Architecture Decision Records Every major technical choice, with context and alternatives
MCP feasibility spike Time-boxed investigation behind the MCP server
OAuth2 server feasibility spike Time-boxed investigation behind the MCP authorization server
DataTourisme flux audit What the DataTourisme feed actually carries