AutoTravel — un voyage en 30 secondes.
Web app B2C qui transforme 6 questions en 3 itinéraires personnalisés via IA — Budget, Confort, Alternative, jour par jour, avec carte interactive.
01 — Le briefRéduire les 3 heures de planification à 30 secondes.
L'expérience type quand on veut partir : trois onglets ouverts (Booking, Skyscanner, Google Maps), deux heures de comparaison, et au final un voyage qui ressemble à celui du voisin. Le constat de départ : la friction est le problème, pas le manque d'options.
Mécanique d'AutoTravel : un formulaire 6 étapes (durée, budget, ambiance, période, contraintes, départ), puis l'IA génère trois propositions distinctes — Budget Strict, Confort, Alternative — chacune avec un itinéraire jour par jour et une carte interactive. L'utilisateur garde la main : il peut ajuster, échanger un jour, sauvegarder son voyage.
02 — Les contraintesAI-assisted, mais pas du vibe coding.
- Mode de dev — Construit en AI-assisted development de bout en bout. Démontrer qu'on peut livrer une app riche en quelques semaines avec l'IA bien pilotée.
- TypeScript strict — Pas d'
any, pas de// @ts-ignore. Si le compilo râle, c'est un signal. - Validation double — Front ET back. Les LLM hallucinent, les utilisateurs aussi. Les deux mentent, donc Zod aux deux bouts.
- État persistant — L'utilisateur peut quitter à l'étape 4, revenir le lendemain, reprendre où il en était.
- Carte interactive sans Mapbox — Coût Mapbox = non pour un MVP. Solution gratuite obligatoire.
03 — Stack & arbitragesPourquoi ces outils.
Next.js 14 (App Router)
Server Actions pour les appels OpenAI (clé API jamais exposée côté client). Streaming natif pour l'UX d'attente IA — l'utilisateur voit l'itinéraire se générer plutôt qu'un spinner aveugle. Edge runtime pour les routes simples.
Zod au centre du système
Le retour OpenAI est imprévisible par nature. Sans validation runtime, c'est l'enfer en prod (clé manquante, type cassé, format inattendu). Schémas Zod réutilisés au formulaire (client), à l'API (server), et à la base (Supabase). Un seul schéma, trois usages. Si la donnée ne passe pas, on échoue tôt et clairement.
Zustand (pas Redux)
Pour persister 6 étapes de formulaire, Redux serait du bazooka. Zustand fait le job en 30 lignes, persiste en localStorage, supporte l'hydration Next côté client uniquement (subtilité useStore.persist.rehydrate() à laquelle j'ai pris une heure pour comprendre).
Leaflet + OpenStreetMap (pas Mapbox)
Mapbox = belle mais payante au-delà du free tier. Leaflet + OpenStreetMap = gratuit, illimité, suffisant pour afficher des marqueurs et un tracé d'itinéraire. Subtilité Next.js : Leaflet n'est pas SSR-friendly, il faut l'importer dynamiquement avec { ssr: false }, sinon erreur window is not defined au build.
Supabase + Resend
Auth + storage des voyages composés (chaque utilisateur peut sauvegarder ses propositions). Resend pour les notifications de devis. Migrations SQL versionnées dans le repo — pas de "j'ai modifié la table à la main, je m'en souviens".
04 — Le livrév1.0.0, prête pour la production.
- 8 routes complètes —
/,/formulaire,/pre-resume,/devis,/voyage/[id],/profil,/onboarding,/contact. - Hero rotatif — 6 phrases d'accroche qui s'alternent toutes les 7 secondes, sur fond bleu ciel avec formes blur en parallax.
- Formulaire 6 étapes — état persistant Zustand, validation Zod à chaque étape, navigation arrière-avant sans perte.
- Intégration IA — appel OpenAI via Server Action, prompt structuré avec retour JSON validé en runtime.
- Carte Leaflet — marqueurs jour par jour, tracé du parcours, popups personnalisées.
- 3 cards options — Budget Strict / Confort / Alternative, comparables d'un coup d'œil.
- Architecture clean — server components / client components bien séparés, pas de
"use client"abusif. - Migrations SQL — versionnées, reproductibles, prêtes pour un déploiement Supabase propre.
05 — ApprentissagesCe que j'ai appris à mes dépens.
Le prompt est le composant le plus critique. Avant d'écrire un seul composant React, il faut une douzaine d'allers-retours avec OpenAI pour stabiliser le format de retour. Sans Zod en runtime + un fallback explicite si le LLM dévie, on plante en prod le premier jour. Le prompt mérite son propre fichier, ses propres tests, sa propre revue.
Zustand persist + App Router = piège d'hydration. Au premier paint côté serveur, le store est vide. Côté client après hydration, il est rempli depuis localStorage. Si on ne gère pas ce moment, on a un mismatch React et un warning permanent en console. Solution : useStore.persist.rehydrate() dans un useEffect + un état "hydrated" à attendre avant d'afficher.
Leaflet en Next : import dynamique obligatoire. const Map = dynamic(() => import('./Map'), { ssr: false }). Sinon le build casse au moment du SSR avec window is not defined. Trois minutes à comprendre, une fois qu'on sait où chercher.
06 — Posturev1 livrée, déploiement prod en arbitrage.
Le code est prêt pour la production. Le déploiement public attend un arbitrage sur le modèle (gratuit complet, freemium, ou B2B). C'est une décision business, pas technique.
Ce que ce projet montre : la capacité à livrer une app B2C complète seul, intégrant un LLM en production avec validation, persistance d'état, base de données, mails transactionnels, carte interactive. Pas une démo Hello World — une v1.0.0 qui tourne.