Plan your bikepacking adventures with confidence.
Paste a Komoot URL or upload a GPX file, and get a structured day-by-day roadbook
with smart pacing, safety alerts, and accommodation suggestions.
Desktop — Split view with day-by-day timeline, contextual alerts, and interactive map.
Mobile — Responsive timeline with weather, difficulty badge, and supply points.
Import your route in seconds — Paste a link from Komoot, Strava, or RideWithGPS, or upload a GPX file directly. The backend fetches, parses, and processes everything asynchronously.
Smart pacing engine — Automatically distributes distance across days, accounting for cumulative fatigue and elevation gain. Configurable daily targets with a safety minimum threshold.
20+ safety & comfort alerts — A rule-based alert engine analyzes each stage for steep gradients, dangerous traffic, headwinds, surface quality, e-bike range, sunset timing, resupply gaps, and more — with three severity levels (critical, warning, nudge).
Accommodation finder — Discovers bivouac spots, refuges, and gites near each stage endpoint via OpenStreetMap, with heuristic pricing estimates.
Cultural points of interest — Detects museums, monuments, castles, viewpoints, and other attractions along the route with an "add to itinerary" action.
Real-time processing — Async workers compute your trip in parallel; live status updates stream to the browser via Mercure SSE. No page reload needed.
Multi-format export — Export enriched GPX files with waypoints for accommodation, water points, and POIs — ready for your GPS device. Download per-stage FIT files for Garmin, or generate a text roadbook summary.
| Platform | Supported URL formats |
|---|---|
| Komoot | komoot.com/[xx-xx/]tour/123 and komoot.com/[xx-xx/]collection/123 |
| Strava | strava.com/routes/123 |
| RideWithGPS | ridewithgps.com/routes/123 |
| GPX upload | Direct file upload (up to 30 MB) |
| Logical type | OSM query | Pricing heuristic |
|---|---|---|
hotel |
tourism=hotel |
€50–€120 |
motel |
tourism=motel |
€45–€90 |
guest_house |
tourism=guest_house |
€40–€80 |
chalet |
tourism=chalet |
€30–€70 |
hostel |
tourism=hostel |
€20–€35 |
alpine_hut |
tourism=alpine_hut |
€25–€45 |
camp_site |
tourism=camp_site |
€8–€25 (€8–€15 if backpack=yes or tents=yes) |
wilderness_hut |
tourism=wilderness_hut |
free / donation (€0–€10) |
shelter |
amenity=shelter + shelter_type~basic_hut|weather_shelter|lean_to |
free (€0) |
git clone https://github.com/vincentchalamon/bike-trip-planner.git
cd bike-trip-planner
make start-devThe app is available at:
- https://localhost — Web application
- https://localhost/docs — API documentation (Swagger UI)
See Getting Started for prerequisites and detailed setup instructions.
The backend runs a pipeline of analyzers on each stage. Three severity levels are used:
| Level | Badge | Description |
|---|---|---|
critical |
Blocking issue requiring immediate attention | |
warning |
Significant issue to watch | |
nudge |
Informational suggestion |
Rules are executed in priority order (lower = higher priority):
Terrain rules (Continuity, Elevation, Steep gradient, Surface, Traffic, E-bike range, Sunset, Rest day) implement StageAnalyzerInterface and are auto-discovered via #[AutoconfigureTag('app.stage_analyzer')]. Rules with -- priority (Calendar, Wind + Comfort, Bike shops, Resupply, Accommodation, Water points, Cultural POI, Railway station, Health services, Border crossing) are separate async Symfony Message handlers; Comfort is co-located with Wind inside AnalyzeWindHandler.
Browser (Next.js 16) PHP Backend (API Platform 4.3)
Zustand + Immer (in-memory) Stateless computation
Zod validation GPX parsing + pacing engine
openapi-fetch (typed) OSM Overpass + weather APIs
Mercure SSE (real-time) <-- Async workers (Symfony Messenger)
Redis cache + Mercure publisher
The frontend sends a trip request via REST; the backend processes it asynchronously across multiple workers and pushes status updates via Mercure SSE. PostgreSQL 18 persists trip configuration and stages; Redis handles transient computation state, 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 -> openapi-fetch provides type-safe API calls. A schema change on the backend intentionally causes a TypeScript compilation failure.
| Layer | Technology |
|---|---|
| Backend | PHP 8.5, Symfony 8, API Platform 4.3, Caddy |
| Frontend | Next.js 16 (App Router), React 19, TypeScript (strict) |
| State | Zustand + Immer (in-memory), Mercure SSE (real-time) |
| Styling | Tailwind CSS |
| Testing | PHPUnit 13 (backend), Playwright 1.58 (E2E) |
| Quality | PHPStan level 9, PHP-CS-Fixer, ESLint, Prettier |
| Async | Symfony Messenger, Redis transport, 5 workers |
| Runtime | Docker (Caddy, Mercure, Redis, PostgreSQL, Node) |
| Document | Description |
|---|---|
| Getting Started | Requirements, installation, and local setup |
| Contributing | Development workflow, standards, and tooling |
| Architecture Decisions | 26 ADRs explaining every major technical choice |
| Claude Code Tooling | MCP servers, hooks, and skills for AI-assisted development |
| Source | Role | Licence | Coverage | Prerequisite |
|---|---|---|---|---|
| OpenStreetMap | Primary: roads, bike infra, water points, bike shops, resupply, base POIs & accommodations | ODbL | Global | None |
| DataTourisme | Complementary: enriched accommodations and cultural POIs; exclusive: dated events | Licence Ouverte 2.0 | France | DATATOURISME_API_KEY |
| Wikidata | Cross-cutting enricher: multilingual descriptions, images, Wikipedia links via Q-IDs | CC0 | Europe | None |
| data.gouv.fr | Weekly recurring markets (offline import) | Licence Ouverte 2.0 | France | make markets-import |
All geographic and infrastructure data is sourced from OpenStreetMap via the public Overpass API. OSM data is cached in Redis for 24 hours per query.
Licence: ODbL 1.0 — attribution required: "© OpenStreetMap contributors".
DataTourisme provides enriched POI data (accommodations, cultural sites, dated events) for France. It is used as an optional supplementary source alongside OpenStreetMap.
Licence: Licence Ouverte 2.0 Etalab — commercial use and modification permitted; attribution required.
Quota: 1 000 requests/hour, ~10 req/s sustained. Rate limiting is enforced server-side via a fixed_window limiter.
Registration: https://www.datatourisme.fr/ — free sign-up, personal API key delivered by email.
To enable DataTourisme integration, set the following environment variables:
DATATOURISME_API_KEY=your-api-key
DATATOURISME_ENABLED=trueWhen DATATOURISME_ENABLED=false (the default) or the API key is absent, all DataTourisme calls are skipped and the application falls back to OpenStreetMap data exclusively.
Wikidata enriches POI, accommodation, and event data already returned by other sources that carry a Wikidata Q-ID (via OSM tag wikidata=Q12345 or DataTourisme property owl:sameAs). Coverage is European. Licence is CC0 — no attribution required.
Fields added: multilingual description (FR/EN/DE/ES/IT), Wikimedia Commons thumbnail, Wikipedia article link, official website, and structured opening hours when available.
Configuration (optional):
WIKIDATA_USER_AGENT=BikeTripPlanner/1.0 ([email protected])Wikidata is always enabled. Results are cached in Redis for 7 days. Errors (timeout, 5xx) are handled silently — the application continues without enrichment.
The weekly market dataset from data.gouv.fr is imported into the PostgreSQL market table via a one-time (or periodic) CLI command. Markets are included automatically in the event scan for each stage.
make markets-importOptions available via bin/console app:markets:import:
| Option | Description |
|---|---|
--dry-run |
Prints statistics without writing to the database |
--limit N |
Limits the number of rows processed (debug / CI) |
The environment variable MARKETS_DATASET_URL can override the dataset URL.
Contributions are welcome! Please read the Contributing Guide before submitting a pull request.
make start-dev # Boot Docker environment
make qa # Run full QA suite (linting, static analysis, formatting)
make test # Run all tests (QA + PHPUnit + Playwright)This project is licensed under the GNU Affero General Public License v3.0.

