11/08/2026

Nous avons déménagé notre propre outil

Pendant deux ans, taper piks-l.com menait à notre site public. Taper piks-l.com/admin menait à notre outil de travail. Une seule application servait les deux, un seul déploiement, un seul risque. Ça marchait — jusqu'au jour où la question n'était plus « est-ce que ça marche ? » mais « est-ce qu'on peut encore toucher à l'un sans faire trembler l'autre ? ».

Cet article raconte la séparation : ce qu'on a déplacé, dans quel ordre, et surtout les trois pièges qui ne se voient ni à la compilation, ni aux tests, ni au premier chargement de la page.

Un site public et une administration n'ont rien en commun

Sauf le nom de domaine, et c'est précisément le problème.

Un site public doit être rapide, indexable, léger. Il n'a pas de session, pas de base de données à interroger, pas de formulaire à valider. Sa page d'accueil doit se rendre au serveur en quelques dizaines de kilo-octets et se laisser lire par un robot de moteur de recherche.

Une console d'administration est l'exact opposé : elle est privée, lourde, bavarde. Elle charge un éditeur de texte riche, un moteur 3D, des graphiques, un calendrier. Elle interroge la base à chaque écran. Et elle ne doit surtout pas être indexée.

Les faire cohabiter dans un même projet Nuxt, c'est accepter trois choses :

  • Le poids du gros contamine le léger. Une dépendance ajoutée pour la console — un éditeur de code, une bibliothèque de rendu — se retrouve dans le graphe du site public. Elle n'est pas chargée par le visiteur, mais elle allonge chaque build, chaque analyse, chaque déploiement.
  • Une panne d'un côté arrête l'autre. Un déploiement en échec parce qu'un écran d'administration ne compile plus, et le site public s'arrête d'être mis à jour. Le commercial et l'outil interne partagent le même destin.
  • Les règles se mélangent. Le robots.txt devait à la fois ouvrir le site et fermer l'administration. Le plan du site devait lister les pages publiques en ignorant les autres. Chaque fichier de configuration portait deux logiques et un commentaire pour expliquer laquelle s'appliquait où.

Aucun de ces trois points n'est un incident. Ce sont des frottements, et un frottement ne provoque jamais de réunion de crise — il se paie en petites monnaies, tous les jours.

Première décision : notre vitrine devient un site client

Nous vendons un CMS qui génère des sites. La question s'est posée franchement : pourquoi notre site ne serait-il pas généré par lui ?

Notre site public est donc devenu un site enfant comme les autres. Même socle, même API publique, mêmes contraintes : aucun identifiant de base de données, aucune session, aucun accès direct à quoi que ce soit. Il lit son contenu par la même route publique que le site d'un tatoueur ou d'une brasserie :

GET /api/public/sites/:siteId/pages?path=/tarifs

Ce n'est pas une coquetterie de communication. C'est un test permanent. Le jour où l'API publique devient lente, c'est notre site qui ralentit. Le jour où un bloc de l'éditeur se rend mal, c'est notre page d'accueil qui se rend mal. Nous ne pouvons plus livrer un outil que nous n'utilisons pas nous-mêmes — et un défaut que nous rencontrons est un défaut que nous corrigeons.

Conséquence concrète, et elle nous a coûté : tout se compose dans l'éditeur. Pas de page écrite à la main qui prendrait la main sur une page composée. Cette règle-là a d'ailleurs révélé un défaut ancien, dont nous avions la sensation sans savoir le nommer : « le site n'est pas celui de mon éditeur ». C'était vrai. Une page écrite l'emporte toujours sur une page composée, et nous modifiions dans l'éditeur des pages que personne ne voyait.

Deuxième décision : la console prend la racine

Une fois le site public parti sur www, la console s'est retrouvée seule sur son domaine — et continuait pourtant de répondre sous /admin. Taper l'adresse de son propre outil de travail menait à une redirection vers le site commercial.

Cinquante pages ont donc remonté d'un cran. /admin/sites s'écrit /sites, /admin/facturation s'écrit /facturation. Cinq cent dix-huit adresses réécrites dans cent quatre-vingt-treize fichiers : liens, redirections après connexion, raccourcis clavier, visites guidées, documentation interne, et les tests qui vérifient tout cela.

Un déplacement de cette taille ne se fait pas à la main. Il se fait avec un script qu'on relit — et c'est là que les choses deviennent intéressantes.

Piège n° 1 : la boucle de redirection qui passe tous les tests

Déplacer des pages, c'est aussi promettre que les anciennes adresses continueront d'aboutir. Des liens ont été mis en favori, envoyés par mail, collés dans des messages internes. On ajoute donc deux règles de redirection :

routeRules: {
  '/admin': { redirect: { to: '/', statusCode: 301 } },
  '/admin/**': { redirect: { to: '/**', statusCode: 301 } },
}

Puis on lance le script qui réécrit /admin/quelque-chose en /quelque-chose partout dans le dépôt. Vous voyez venir la suite. Ces deux lignes sont les seules du projet qui doivent conserver /admin écrit en toutes lettres. Passées à la moulinette, elles deviennent :

'/': { redirect: { to: '/', statusCode: 301 } },
'/**': { redirect: { to: '/**', statusCode: 301 } },

Autrement dit : toute page redirige vers elle-même, définitivement. Une boucle infinie sur l'intégralité du site.

Ce qui rend ce défaut mémorable, ce n'est pas la faute — elle est évidente une fois écrite. C'est ce qui ne l'a pas attrapée :

  • la vérification des types : verte ;
  • les 1 642 tests : verts ;
  • la compilation de production : réussie, sans un avertissement ;
  • le démarrage du serveur : normal.

Le premier signe est arrivé au moment de demander une page au serveur local : 301 → /login. Puis /login301 → /login. Chaque adresse, y compris /robots.txt, renvoyait vers elle-même.

C'est la raison pour laquelle nous imposons un contrôle de rendu avant toute livraison, en plus des tests : le site doit répondre, pas seulement compiler. Plusieurs de nos pannes les plus longues ont passé le build sans une alerte.

Piège n° 2 : deux routes sœurs qui n'en sont pas

La console avait une page avis.vue — la liste des avis clients. Le site avait un dossier avis/ contenant [token].vue, la page qu'un client ouvre depuis un mail pour déposer son avis. Deux choses différentes, deux adresses différentes : /avis et /avis/le-jeton.

Sauf que dans un routage par fichiers, avis.vue posé à côté d'un dossier avis/ ne donne pas deux routes sœurs. Il donne un parent et son enfant. Le fichier devient le cadre de la page à jeton — et comme il n'affiche pas de <NuxtPage />, l'enfant ne s'affiche pas du tout.

Le client qui clique sur son lien d'avis voit la page d'administration, vide, sans son formulaire. Rien ne le signale : pas d'erreur, pas de 404. La page se rend parfaitement — ce n'est simplement pas la bonne.

La correction tient en un renommage : avis.vue devient avis/index.vue. Même adresse, plus d'imbrication. Le vrai enseignement est ailleurs : déplacer un fichier, c'est modifier un contrat que personne n'a écrit. Le routage par fichiers est confortable jusqu'au jour où l'arborescence devient une syntaxe qu'on ne relit plus.

Piège n° 3 : le CORS qui ne se voit qu'au deuxième clic

Celui-ci est notre préféré, parce qu'il est invisible exactement là où on regarde.

Notre site public lit son contenu sur l'API de la plateforme. Après la séparation, ces deux-là ne sont plus sur le même domaine : la page vit sur www, l'API sur app. Une requête entre deux domaines exige un en-tête d'autorisation explicite du serveur.

Or une page moderne charge ses données deux fois, de deux façons :

  • au premier chargement, c'est le serveur qui va chercher les données pour rendre la page complète. Node ne connaît pas la politique de même origine : la requête passe, la page est parfaite ;
  • à la navigation suivante, c'est le navigateur qui va chercher lui-même les données, sans recharger la page. Là, la politique s'applique. Sans en-tête, la requête est refusée.

Le résultat, c'est une page qui s'affiche impeccablement quand on arrive dessus par un lien direct, et qui se vide quand on y arrive en cliquant dans le menu. Le rechargement la répare. Un rapport de bug qui dit « parfois c'est vide » est parfaitement exact et parfaitement inexploitable.

Nous avions déjà rencontré ce défaut sur un site client — des liens en 404 au clic, corrects en accès direct — et la cause était voisine : le domaine sans www répond par une redirection sans en-têtes d'autorisation. Le rendu serveur suit la redirection ; le navigateur, lui, s'arrête.

La règle qui en découle tient en une ligne : toute route publique destinée à un autre domaine pose ses en-têtes en même temps que son cache, par une seule fonction, jamais à la main.

export const setPublicCache = (event, seconds = 60) => {
  setResponseHeader(event, 'Cache-Control', `public, max-age=0, s-maxage=${seconds}, …`)
  setResponseHeader(event, 'Access-Control-Allow-Origin', '*')
  setResponseHeader(event, 'Access-Control-Allow-Methods', 'GET, OPTIONS')
}

Ce qui restait accroché

Une migration ne se termine pas quand le nouveau fonctionne. Elle se termine quand l'ancien a cessé de traîner. Trois restes, tous invisibles depuis un navigateur :

Un plan du site qui décrivait des pages parties ailleurs

Le sitemap.xml de la console listait consciencieusement les pages du site public — sous l'adresse de la console. Il annonçait donc aux moteurs des adresses qui répondaient ailleurs. Il a suivi la vitrine, et se construit désormais à partir des pages composées dans l'éditeur : une page ajoutée y apparaît sans qu'on touche au code.

Un robots.txt qui ouvrait la porte de l'administration

Il disait : autorise tout, sauf /admin et /compte. Fidèle à un monde où la racine était le site public. Depuis que la racine est le tableau de bord, cette règle proposait aux moteurs d'indexer l'administration. Le domaine de la console ne dit plus qu'une chose : Disallow: /.

Des redirections orphelines

Trois anciennes adresses de notre offre — /fonctionnalites, /pour-qui, /tarifs — avaient été redirigées vers leurs nouvelles pages des mois plus tôt. Ces redirections étaient restées dans le dépôt de la console, c'est-à-dire sur un domaine que plus personne ne visite pour ça. Sur le site public, ces trois adresses indexées tombaient en 404. Elles vivent maintenant à côté des pages qu'elles désignent.

Aucun de ces trois restes n'aurait provoqué d'appel client. Tous les trois coûtaient du référencement, silencieusement.

Ce que ça change pour nos clients

Un client ne devrait jamais avoir à s'intéresser à l'architecture de son prestataire. Voici pourtant ce qu'il gagne à celle-ci :

  • Son site ne partage plus rien avec notre outil interne. Un déploiement raté de notre console n'a aucun effet sur son site. Ce n'était pas vrai avant.
  • Le CMS qu'il utilise est celui que nous utilisons. Notre site public se compose dans le même éditeur, avec les mêmes blocs, servi par la même API. Ce qui nous gêne le gêne, donc ce qui nous gêne est corrigé.
  • Ce qui change se lit. Notre changelog et notre feuille de route étaient enfermés derrière une session. Ils sont publics, et depuis peu filtrables par outil : on peut regarder ce qui a bougé sur la facturation, ou sur l'éditeur, sans lire les cent trente autres versions.

Ce qu'on en retient

Si vous avez la même architecture — un site vitrine et une application dans le même projet — voici ce que nous ferions différemment, et ce que nous referions à l'identique.

Ce qu'on referait

  • Écrire la migration comme un script, pas comme une série de gestes. Un script se relit, se rejoue à l'identique, et se corrige. Le nôtre a tourné cinq fois avant d'être le bon — la cinquième était strictement identique à la quatrième, au correctif près.
  • Vérifier le rendu, pas seulement le build. Le piège n° 1 n'a été attrapé que par une requête réelle sur un serveur réel. Aucun test statique ne l'aurait vu.
  • Ne rien supprimer. Tout ce qui sort du dépôt part dans un dossier de côté. Une migration où l'on hésite est une migration où l'on peut revenir.

Ce qu'on ferait autrement

  • Séparer plus tôt. Les frottements décrits au début ne sont pas apparus le jour de la migration : ils étaient là depuis des mois. Le coût du déplacement croît avec le nombre de fichiers, et il ne décroît jamais.
  • Traiter le référencement comme une partie du déménagement, pas comme une vérification d'après-coup. Un plan du site et un robots.txt décrivent une topologie ; changer la topologie sans les changer, c'est mentir aux moteurs pendant plusieurs semaines.
  • Se méfier des fichiers de configuration lors d'un remplacement massif. Ce sont les seuls fichiers qui parlent des anciennes adresses au futur. Ils doivent être exclus de la réécriture, puis modifiés à part.

Un dernier mot sur le calendrier. Toute cette migration — cinquante pages déplacées, cinq cent dix-huit adresses réécrites, trois défauts corrigés, deux dépôts livrés — a tenu dans une journée. Ce n'est pas un exploit : c'est ce que permet un projet couvert par des tests et documenté au fil de l'eau. Le temps n'est pas passé à écrire le code du déménagement. Il est passé à décider où chaque chose devait vivre — et cette question-là, aucun outil ne la répond à votre place.

UN PROJET DE SITE ?

Décrivez votre besoin, nous revenons vers vous avec une proposition claire.

PIKS-L

Chargement en cours