J'ai construit à ma famille une appli d'itinéraire de voyage — avec un assistant IA à l'intérieur

Publié
11 juillet 2026
Par
Jacob Lloyd — rédigé avec l'aide de l'IA, une fois le projet terminé
Temps de lecture
13 min de lecture

En clair : J'ai fait un petit site web rien que pour le voyage de ma famille au Japon — il montre le programme de chaque jour, la météo, des cartes, et fonctionne sur le téléphone même sans réseau. Il a aussi un petit bouton d'aide IA capable de répondre aux questions sur le voyage et d'ajuster le programme. Cet article raconte honnêtement comment je l'ai construit avec beaucoup d'aide de l'IA : ce qui a marché, ce qui a cassé, et comment j'ai transformé un export fragile d'outil de design en quelque chose de sûr à éditer et sûr à mettre en ligne. Vous pouvez télécharger un échantillon nettoyé et l'ouvrir dans n'importe quel navigateur.

Ma famille est partie en voyage au Japon, et je voulais que tout le monde ait le
programme dans sa poche — chaque jour détaillé, des cartes à portée de tap, la
météo, et les petits détails qu’on oublie facilement quand on a le décalage
horaire dans une gare. Alors j’ai construit une petite appli web rien que pour
nous : un itinéraire privé, verrouillé par code PIN, avec un programme
jour par jour, la météo en direct, un support hors ligne, et — parce que c’est
moi — un assistant IA intégré capable de répondre aux questions sur le
voyage et même de faire de petites modifications.

Voici le récit honnête de sa construction : les parties qui ont marché, celles
qui ont cassé de façons que je n’attendais pas, et comment un export fragile
d’outil de design est devenu quelque chose que j’étais à l’aise de mettre sur
le web public. Tout en bas, il y a un échantillon nettoyé à télécharger et
ouvrir
dans n’importe quel navigateur.

En bref

  • Ce que c'est : une appli web d'itinéraire pensée mobile d'abord — jours, étapes, météo en direct, aides de change et de fuseau horaire, hors ligne/installable, avec un onglet assistant IA.
  • L'idée clé : un export d'outil de design magnifique mais fragile est devenu sûr à éditer une fois que tout le programme a été piloté par un seul petit fichier de données et qu'une étape de build a régénéré l'appli.
  • La barrière : un code PIN côté client, c'est du théâtre — la vraie barrière est une session côté serveur avec un verrouillage limité en débit.
  • L'IA : un modèle local auto-hébergé, tenu en laisse courte (répondre aux questions ; ne faire que de petites modifications encadrées).
  • La leçon qui est revenue sans cesse : la seule façon de savoir qu'un changement a fonctionné était d'afficher l'appli dans un vrai navigateur et de regarder — ne jamais faire confiance à l'idée que le fichier « devrait » être bon.

Le point de départ : une magnifique boîte noire

J’ai conçu l’apparence dans un outil de design IA et je l’ai exportée. Ce qui
en est sorti était un seul fichier HTML autonome — une appli React complète
avec les polices et les images en inline — d’environ 14 Mo en un seul
fichier
. C’était magnifique visuellement et ça marchait dès le premier
double-clic.

C’était aussi une boîte noire. Toute l’appli, minifiée, vivait dans un seul
bloc géant, et le programme était enfoui à l’intérieur. Éditer le voyage à la
main revenait à faire de la chirurgie sur du code généré par une machine.
C’est acceptable une fois ; c’est un cauchemar à chaque fois que les plans
changent — et pendant un voyage en famille, les plans changent tous les
jours.

Alors la première vraie décision a été architecturale : arrêter d’éditer
l’export.

L’idée qui a tout rendu possible

Au lieu de toucher au bundle de 14 Mo, j’ai déplacé tout le programme dans un
petit fichier de données lisible — un seul content.json avec les
jours, les étapes, les notes météo, les photos et coûts par étape. Ensuite,
une courte étape de build lit ce fichier et régénère l’appli à partir de
l’export intact.

Cela a changé tout le caractère du projet. Désormais, « ajouter une étape » ou
« corriger un horaire » se résume à une édition de deux lignes dans un fichier
de données qu’un humain — ou un assistant IA — peut faire sans risque, suivie
d’un rebuild. La partie fragile (l’appli de 14 Mo) n’est jamais touchée à la
main. Chaque widget que j’ai ajouté ensuite — une carte météo en direct, une
horloge double heure locale ↔ Tokyo, un convertisseur de devises
bidirectionnel, des étapes dépliables avec photos et notes de parking — est
généré à partir de ce même build piloté par les données.

Le piège qui a englouti chaque widget : l’appli se reconstruit elle-même au démarrage

Voici le piège qui m’a coûté le plus de temps, écrit ici pour qu’il ne vous en
coûte aucun.

L’appli exportée reconstruit toute la page à son démarrage. Si vous
injectez votre propre balisage dans le HTML statique — une horloge, une
bannière, n’importe quoi — il est effacé à l’instant où l’appli se monte.
Vos identifiants d’éléments disparaissent, getElementById renvoie null
pour toujours, et votre widget n’apparaît jamais, sans le moindre signe. Pire
encore : si vous « vérifiez » en recherchant dans le fichier HTML sauvegardé,
votre balisage est bien là, donc ça a l’air correct. Il ne survit tout
simplement jamais jusqu’à l’écran.

Deux règles ont résolu ça définitivement :

  1. Injecter dans le propre gabarit de l’appli, pas dans la page. Ajoutez
    votre widget là où l’appli construit son interface, pour qu’il soit rendu
    comme faisant partie de l’appli et survive au démarrage.
  2. Câbler le comportement avec de la délégation d’événements sur
    document.
    L’appli remonte des sections entières à chaque changement
    d’onglet, en détruisant et recréant leurs nœuds. Un écouteur attaché
    directement à un bouton meurt au prochain rendu ; un seul écouteur délégué
    sur document (qui vérifie ce qui a été cliqué) survit à tout.

Le corollaire est une règle de test que je traite désormais comme une loi :
ne vérifiez jamais une interface web en grepant le fichier ou en vérifiant
un code HTTP 200.
Affichez-la dans un vrai navigateur (headless), après
son démarrage
, et regardez réellement le résultat. C’est la seule
vérification qui repère cette catégorie de bug — et j’en ai attrapé plusieurs
comme ça, y compris un convertisseur de devises resté invisible jusqu’à ce
que je le déplace dans le gabarit.

« Autonome » ne l’était pas tout à fait

L’export prétendait être un seul fichier autonome. Ce n’était pas tout à
fait vrai. Il récupérait discrètement son framework d’interface depuis un CDN
public au moment de l’exécution, assemblé à partir de fragments d’URL dans le
code — si bien qu’une simple recherche textuelle du CDN ne trouvait rien, et
le fichier avait l’air prêt pour le hors ligne tout en ne l’étant pas du
tout. Sur un hébergeur web avec une politique de sécurité stricte, cette
requête cachée était bloquée et toute l’appli échouait au démarrage. Sur mon
ordinateur portable, sans une telle politique, ça fonctionnait — donc c’était
invisible jusqu’à ce que ça touche le vrai serveur.

La solution a été de s’auto-héberger le framework (le regrouper avec
l’appli pour qu’il n’y ait aucune requête externe) et, pour l’échantillon
téléchargeable, de l’inliner directement pour que l’ensemble soit
vraiment un seul fichier qui s’ouvre depuis le disque sans aucun accès
internet. Si vous devez retenir une chose pratique de cette section :
« ça marche sur ma machine » et « ça marche derrière les en-têtes de sécurité
d’un vrai serveur web » sont deux affirmations différentes — testez la
seconde.

La barrière : du théâtre vs. un vrai verrou

La première version verrouillait le site avec un code PIN dans le JavaScript
de la page. C’est du théâtre de sécurité : tout l’itinéraire se trouve
dans le code source de la page, donc quiconque peut ouvrir les outils de
développement peut le lire sans jamais taper le code. Suffisant pour cacher
une fête surprise d’un simple coup d’œil ; pas suffisant pour quoi que ce
soit qui compte vraiment.

La vraie version a déplacé la barrière vers le serveur : on soumet le
code PIN, le serveur le vérifie et démarre une session, et c’est seulement
là qu’il livre l’itinéraire. Il y a aussi un verrouillage progressif par
IP
— quelques essais erronés et cette adresse est bloquée pour une durée
croissante, si bien que personne ne peut mitrailler tous les codes à six
chiffres.

Je vais être honnête sur les limites, parce que faire semblant du contraire
est la façon dont les gens se font avoir : un code PIN à six chiffres qui
n’est que limité en débit convient très bien à un itinéraire familial à
faible enjeu, mais ce n’est pas du niveau bancaire. Si vous verrouillez
quelque chose de sensible, utilisez une phrase de passe longue et une vraie
authentification. Adaptez le verrou à ce qui se trouve derrière la porte.

L’assistant IA, en laisse courte

La partie amusante : un petit assistant — je l’ai appelé Yoshi — qui vit
dans son propre onglet. Dans la version privée familiale, il peut répondre à
des questions sur le voyage (« c’est quoi le programme du jour de
l’aquarium ? », « qu’est-ce qu’on devrait emporter ? ») et faire de petites
modifications encadrées
au programme. Le point essentiel, c’est qu’il édite
ce même fichier de données sûr, jamais l’appli fragile — et il est
clôturé : il peut ajouter ou ajuster, mais il ne peut pas supprimer de
journées, et les vols et les réservations sont verrouillés en dur pour
qu’aucun message de chat ne puisse y toucher.

Fait important, Yoshi tourne sur un modèle local auto-hébergé — une
petite IA sur mon propre matériel, pas une API cloud payante — atteint via un
relais encadré. C’est un sujet à part entière (mettre en toute sécurité un
chatbot hébergé en local sur le web public, et à quel point c’est lent sur
du matériel grand public), donc je l’ai écrit séparément :
Mettre un chatbot sur le web public — sur mon propre matériel.

Remarque sur l’échantillon téléchargeable ci-dessous : l’onglet
assistant est présent pour que vous voyiez où il se trouve, mais dans
l’échantillon il est délibérément désactivé — il n’est branché à
aucune IA et ne répondra pas. Pour le faire fonctionner dans votre propre
copie, il faudrait le pointer vers votre propre modèle local. C’est
volontaire : je ne vais pas distribuer un fichier public qui communique
discrètement avec une IA en cours d’exécution.

Hors ligne, installable, et résistant aux voyages à l’étranger

Les dernières touches concernaient la réalité de l’utilisation en voyage :
Wi-Fi d’hôtel capricieux, pas de données en avion, itinérance qui va et
vient. L’appli est donc devenue une PWA installable — on l’ajoute à
l’écran d’accueil et elle s’ouvre comme une vraie appli — et elle
fonctionne hors ligne, en servant tout l’itinéraire depuis l’appareil
une fois qu’il a été chargé. La seule règle à laquelle j’ai fait attention :
elle ne met en cache l’itinéraire qu’après un déverrouillage réussi, pour
que la barrière ne soit jamais contournée par le cache hors ligne. En ligne,
elle revérifie toujours la barrière ; hors ligne, elle fait confiance à la
copie déjà présente sur votre téléphone. Pour un plan de voyage familial,
c’est le bon compromis.

(Les service workers ont des arêtes coupantes — une vieille version en cache
peut continuer à servir une page périmée longtemps après qu’on a déployé un
correctif. J’ai appris à toujours livrer un worker mis à jour, jamais
seulement supprimer l’ancien, pour que les navigateurs récupèrent
effectivement le changement.)

Le tester comme si c’était réel, parce que ça l’était

Comme la famille utilisait réellement ça pendant le voyage, j’ai traité
chaque changement comme un déploiement : builder, afficher la vraie page
dans un navigateur headless après le démarrage, actionner les vrais onglets
et boutons, et seulement ensuite publier. En plein voyage, j’ai poussé de
petites corrections en direct de cette façon — un badge « AUJOURD’HUI » qui
met en surbrillance le jour en cours (ancré sur l’heure de Tokyo, pas celle
du téléphone du lecteur), une météo rafraîchie, quelques ajustements de mise
en page — chacun vérifié d’abord dans un vrai navigateur. C’est la même
leçon que le piège du démarrage qui reconstruit tout, apprise à nouveau :
le fichier qui existe n’est pas la fonctionnalité qui marche.

Essayez — téléchargez l’échantillon

Le téléchargement ci-dessous est un échantillon public nettoyé de
l’appli. Chaque nom, date, adresse et détail est un contenu de démonstration
fictif ; c’est un plan fictif de 7 jours à Tokyo. Ouvrez index.html dans
n’importe quel navigateur — pas d’installation, pas de serveur, pas de
compte. Ça fonctionne hors ligne ; seules la carte météo en direct et les
liens de carte accèdent à internet.

Deux mises en garde honnêtes. D’abord, c’est un seul fichier d’environ
14 Mo
, parce que toute l’appli, les polices et les images sont inlinées
pour qu’elle fonctionne sans aucune dépendance — ce qui la rend lente au
premier ouvrant
; une version de production séparerait et compresserait
tout ça. Ensuite, comme indiqué, l’assistant IA est désactivé dans
l’échantillon — l’onglet s’ouvre et vous le dit.

Construisez-en un vous-même

L’encadré « pour l’implémenter vous-même » en haut de l’article n’est pas un
slogan — cet article est pensé pour être transmis tel quel à un assistant
IA. La recette, condensée :

  1. Mettez le plan dans un fichier de données. Jours, étapes, notes,
    météo — un seul fichier lisible qui est l’unique source de vérité.
  2. Générez l’appli à partir de là. Une étape de build déterministe
    transforme les données en interface, pour que les modifications se
    fassent toujours sur les données, jamais sur le code généré.
  3. Si vous partez d’un export d’outil de design, respectez son
    démarrage.
    Injectez dans le gabarit de l’appli, utilisez des
    événements délégués, et auto-hébergez tout ce qu’elle récupère.
  4. Verrouillez côté serveur, pas dans la page. Une session plus un
    verrouillage limité en débit bat un code PIN en JavaScript à tous les
    coups.
  5. Rendez-la hors-ligne d’abord si elle voyage — mise en cache
    seulement après un vrai déverrouillage.
  6. Vérifiez dans un navigateur, après le démarrage. Pas « le fichier
    existe ». L’écran.

Pièges, récapitulés

  • L’export reconstruit le DOM au démarrage — tout ce que vous injectez
    dans la page est effacé ; injectez plutôt dans le gabarit de l’appli, et
    déléguez les événements sur document.
  • « Autonome » peut ne pas l’être — un export peut récupérer son
    framework au moment de l’exécution ; auto-hébergez-le, et testez derrière
    les en-têtes de sécurité de votre vrai serveur, pas seulement en local.
  • Un code PIN côté client, c’est du théâtre — le contenu est dans la
    page ; verrouillez côté serveur.
  • Un code PIN à six chiffres n’est que limité en débit, pas
    incassable
    — suffisant pour un faible enjeu, inadapté à quoi que ce soit
    de sensible.
  • Les service workers mettent en cache de façon agressive — livrez un
    worker mis à jour pour déployer un correctif ; supprimer l’ancien fichier
    ne le désenregistre pas.
  • « Le fichier existe » ≠ « la fonctionnalité marche » — affichez et
    regardez, à chaque fois.

Téléchargements

Gratuit pour un usage personnel. Si ça vous fait gagner un après-midi, le bouton café n'est pas loin.


← Plus de IA et LLM locaux