Tous les articles

Tous les articles

Publier les documents juridiques comme pages du site

Les contrats et politiques quittent le repo pour devenir des pages du site, avec un PDF archivé, un lien par clause et le security.txt promis.

Les conditions générales, la politique de confidentialité, l'accord de traitement des données et les autres étaient rédigés, relus et versionnés dans le repo, et nulle part où un client pouvait les consulter. Un lien vers des fichiers sur un hébergeur de code n'est pas une publication, et un contrat que personne ne peut ouvrir est un contrat que personne n'a accepté. Le 2026-09-22, ils sont devenus des pages du site public, en anglais et en français, et chacun a reçu un PDF que le lecteur peut conserver.

Rendus au moment du build#

Les documents restent du Markdown dans le repo, où ils sont relus comme n'importe quel autre changement. Le build les convertit en HTML, si bien qu'aucune bibliothèque Markdown n'arrive dans un navigateur, et un lecteur qui a désactivé JavaScript reçoit quand même le texte entier : la page est prérendue, comme toutes les autres pages du site.

Une seule vue sert tous les documents. Elle obtient le sien par un guard de route et un cache au niveau du module plutôt que par le Suspense de Vue, parce que le rendu côté serveur et l'hydratation dans le navigateur doivent s'accorder exactement sur ce qu'ils affichent, et un composant asynchrone leur donne deux occasions de diverger. Chaque document est son propre chunk. Un lecteur ouvre une politique, et les regrouper mettrait toutes les politiques dans la charge de chaque visiteur.

Le serveur de développement a été la première surprise. Les documents vivent en dehors de la racine Vite du site, il fallait donc l'autoriser à les lire, et nommer un répertoire dans server.fs.allow remplace la liste par défaut de Vite au lieu de s'y ajouter. Le site a perdu le droit de servir ses propres sources, et chaque route s'affichait comme un simple fond uni. Rien de ce qui tourne sur un build de production n'aurait pu le détecter, puisque l'option n'y existe pas ; la correction redéclare la racine du workspace à côté du nouveau répertoire.

Ce qu'un contrat exige et qu'une page web n'exige pas#

Une clause doit pouvoir être citée. Le build écrit un lien dans chaque titre, libellé avec le texte du titre lui-même, parce qu'un libellé commun donnerait à un lecteur d'écran une liste de liens identiques. Une clause atteinte par ce lien s'arrête sous l'en-tête fixe, et le routeur connaît le même décalage que celui lu par le navigateur, faute de quoi les deux chemins d'accès n'aboutiraient pas au même endroit.

Les renvois entre documents avaient un défaut plus discret. Un lien reprenait le slug que la cible déclarait pour elle-même, tandis que la table des routes décidait vers quoi ce slug pointait, et rien ne comparait les deux. Ils concordaient parce que les documents avaient été rédigés avec soin, exactement le genre de concordance qui passe le build et sert ensuite un lien vers une route inexistante. Le site confie désormais cette comparaison au build, qui refuse un renvoi vers un document qu'il ne route pas.

L'impression devait aussi fonctionner, parce que pendant un temps Ctrl+P a tenu lieu de bouton de téléchargement. Les règles d'impression retirent l'habillage du site, libèrent la largeur de lecture et redéclarent les tokens de couleur : le mode sombre est une classe sur le document, rien en CSS ne peut la retirer, et c'est du gris sur blanc qui s'imprime sinon.

Un PDF par document, par langue, par version#

Un client qui archive une politique avant de signer, ou un auditeur après, a besoin d'un fichier plutôt que d'une page. Chaque page juridique renvoie désormais vers un PDF d'elle-même, nommé d'après le document, la langue et la version, pour qu'une copie dans un dossier de téléchargements dise ce qu'elle est, et qu'un avenant reçoive une nouvelle adresse plutôt que de nouveaux mots sous une ancienne.

Les copies sont imprimées au moment du build à partir des pages prérendues, dans un navigateur headless sans scripts, de sorte qu'aucun second moteur de rendu ne peut contredire la page. Les assertions du build refusent un lien sans fichier derrière, un fichier qu'aucune page ne lie, une version que la page n'indique pas et un fichier au-delà de son budget en octets, si bien que le lien et le fichier ne peuvent pas diverger. Si la police de la marque ne se charge pas pendant l'impression, le build échoue plutôt que d'archiver des copies dans une police de secours. Le navigateur qui imprime est installé dans l'étape de build de l'image, qui est jetée, et aucune des images d'exécution ne le contient.

Le security.txt promis par la politique#

La page de divulgation des vulnérabilités invitait un chercheur à consulter /.well-known/security.txt, et rien ne le servait. Une politique publiée qui renvoie vers une 404 est pire qu'une politique muette : quelqu'un qui tient une faille suit le lien, tombe sur une page d'erreur, et en conclut qu'il n'y a personne à prévenir.

Le fichier est généré plutôt que déposé comme ressource statique, parce que chaque URL absolue qu'il contient porte l'origine pour laquelle le build a été fait : une copie de staging se désigne elle-même au lieu d'envoyer le signalement vers la production. Son champ Policy vient de la route où la politique de divulgation est publiée, et renommer cette route ne peut pas faire pointer les deux dans des directions différentes. Expires décide si le lecteur fait confiance au reste du fichier, puisque la RFC 9116 permet d'ignorer un fichier dont la date est dépassée ; il est donc fixé à un an moins un jour après chaque build, tronqué à minuit pour que deux builds du même jour produisent les mêmes octets. Il est écrit même sur les builds qui refusent l'indexation : un hôte que personne n'est censé trouver est justement l'endroit où un signalement spontané a besoin d'une adresse.

Quatre jours plus tard, l'origine de l'application a reçu sa propre copie. C'est là qu'un chercheur regarde le plus probablement, et elle répondait sur ce chemin avec la coquille de l'application et un 200. Le prérendu génère désormais le fichier une fois par origine avec une seule fonction, et seul Canonical diffère. Je l'ai préféré à une redirection, dont la RFC 9116 dit de se méfier d'une origine à l'autre, et à un proxy, qui aurait rendu l'application dépendante de la disponibilité du site.

Le rafraîchissement que personne ne voyait#

Le lendemain de la mise en ligne, le contrôle de performance a montré des pages juridiques qui affichaient leur plus grand élément vers 3,6 s pour un budget de 2,5 s sur son profil mobile, et seulement par moments. Le corps est du balisage rendu par le build, lié avec v-html, et Vue 3.5 réapplique les propriétés dynamiques d'un template pendant l'hydratation, innerHTML compris. Chaque paragraphe, titre et cellule de tableau était analysé à nouveau une fois arrivés, l'un après l'autre, le point d'entrée, le catalogue de messages et le chunk du document, et aucune divergence d'hydratation n'est signalée dans ce cas : tous les contrôles existants restaient au vert.

Le corps passe désormais par un petit composant à fonction de rendu dont le noeud ne déclare aucune propriété dynamique, si bien que l'hydratation adopte les noeuds du serveur, et qu'une navigation ultérieure les remplace toujours. Le prérendu précharge aussi le chunk du document de chaque page en même temps que le point d'entrée, au lieu de trois requêtes plus loin. Ces pages s'affichent désormais dès le premier rendu, vers 1,7 s. Le contrôle de rendu vérifie que chaque bloc de texte prérendu survit à l'hydratation, en gardant les noeuds d'avant l'hydratation et en contrôlant qu'ils sont toujours dans le document après ; contrôler un ancêtre n'aurait pas suffi.