Comment fonctionne FolioPress : une page, toutes les langues
La plupart des sites web ne parlent qu'une seule langue. Dès qu'une entreprise a besoin d'une deuxième, les choses se compliquent : une extension de traduction greffée par-dessus, une deuxième copie du site à garder synchronisée, ou une formule plus chère juste pour afficher « Hello » à côté de « Bonjour ».
FolioPress est parti de l'idée inverse : une page n'est pas une page à laquelle on accroche des traductions, elle est toutes ses langues à la fois. Le titre, l'adresse, le menu, les étiquettes, ce que voit Google : tout existe dans chaque langue que vous choisissez, dès la première minute.
Tout a commencé avec l'envie de remplacer un CMS fait maison dans lequel plusieurs langues avaient été greffées après coup. En cherchant des alternatives, j'ai découvert qu'elles échouaient toutes lamentablement à intégrer du contenu multilingue dans un créateur de sites simple à utiliser.
Voici un coup d'œil dans les coulisses : comment l'ensemble s'articule, les compromis qui le sous-tendent et ce qui a cassé en chemin.
Vue d'ensemble
Une visite voyage de gauche à droite. La tour de contrôle ne se trouve jamais sur ce chemin. Elle se contente de construire, de déménager et de veiller sur les sites.
Une visite sur un site FolioPress fait trois sauts. Cloudflare l'intercepte près du visiteur et filtre les indésirables. Une porte d'entrée – Traefik, qui gère aussi les certificats HTTPS – regarde l'adresse et trouve le bon site. Le site lui-même répond, soit depuis un immeuble partagé, où de nombreux petits sites vivent côte à côte, soit depuis une maison à lui quand il devient très fréquenté. À l'écart, une tour de contrôle s'occupe de tout ce qu'un visiteur ne voit jamais : inscriptions, facturation, adresses web, mises à jour et bilans de santé, avec un petit gardien sur chaque serveur qui démarre et arrête les sites.
Tout est en Python, en trois couches que j'ai écrites moi-même : un framework web (pystarter, bâti sur FastAPI), le système de gestion de contenu par-dessus (foliopress) et la tour de contrôle (Forestry). Les paiements passent par Polar, qui agit en tant que vendeur officiel (merchant of record) : la TVA européenne, c'est donc leur casse-tête et non le mien.
Chaque page, chaque langue
Chaque langue a sa propre adresse lisible et, si vous le souhaitez, son propre domaine.
Quand une maison d'hôtes de Lisbonne rédige sa page « À propos », elle la rédige une fois par langue, et chaque version reçoit une adresse qui se lit naturellement dans cette langue. En coulisses, une traduction est un enregistrement à côté de l'original, pas une copie de la page : titres, adresses, menus, étiquettes et descriptions pour les moteurs de recherche existent tous par langue. Les visiteurs arrivent automatiquement dans leur langue, et Google sait exactement quelle page est laquelle (grâce aux balises hreflang et à un sitemap par langue) : la version portugaise apparaît donc dans les recherches en portugais. Vous voulez le site allemand sur son propre domaine en .de ? Ça marche aussi. Et si vous préférez ne pas partir d'une page blanche, un premier jet de chaque traduction rédigé par IA est à un clic : vous relisez, vous décidez.
Chaque site conserve tout cela dans son propre petit fichier de base de données (SQLite). Pas de serveur de base de données à faire tourner, la sauvegarde nocturne hors site est une simple copie de fichier, et un site entier tient dans un dossier qu'on peut déplacer, restaurer ou supprimer d'un seul coup.
Les petits sites partagent, les sites fréquentés déménagent
La plupart des sites web sont calmes presque tout le temps. Donner à chacun sa propre machinerie, ce serait comme louer une maison entière pour quelqu'un qui passe deux fois par an. Les petits sites partagent donc un immeuble – un seul processus, des données strictement séparées – et la plateforme surveille le trafic de chaque site. Quand l'un d'eux s'anime, il emménage dans une maison à lui. Quand il se calme pendant quelques semaines, il revient.
Partager un immeuble ne fonctionne que si les murs sont solides. Quand une requête arrive, le processus partagé regarde l'adresse, puis bascule sur la base de données et les réglages de ce site le temps de cette seule requête (grâce aux contextvars de Python), si bien qu'un site ne voit jamais les données d'un autre. Le conteneur de chaque site dispose en plus de son propre réseau privé, avec un pare-feu sortant. Et comme les propriétaires peuvent téléverser et modifier leurs propres thèmes, ces gabarits s'exécutent dans un bac à sable avec des limites de temps, de taille de sortie et de mémoire.
Définir ce qui compte comme « fréquenté » a demandé plusieurs essais. Une seule newsletter, ou un robot d'indexation pris d'une frénésie passagère, ne devrait pas faire passer un site pour populaire. Le score combine donc la journée typique du site sur la semaine écoulée (la médiane) et l'intensité de ses heures de pointe (l'heure au 95e centile), ce qui écarte la poignée d'heures les plus folles.
Jour de déménagement, sans fermer boutique
Chaque mise à jour, et chaque déménagement entre l'immeuble partagé et une maison à lui, se fait pendant que le site continue de servir ses visiteurs. Les ingénieurs appellent ça un déploiement blue/green : une nouvelle copie du site démarre à côté de l'ancienne, et ce n'est que lorsqu'elle est en bonne santé que la porte d'entrée commence à lui envoyer du monde. Deux détails rendent l'opération vraiment transparente. Le gardien ne suppose pas que la porte d'entrée a basculé : il consulte les propres compteurs de la porte d'entrée jusqu'à ce que ce soit le cas. Et l'ancienne copie reçoit d'abord l'ordre de se « vider » : elle n'accepte plus de nouveaux visiteurs, termine ce qu'elle faisait, et seulement ensuite s'éteint.
Ce qui a cassé
Rien ne vous apprend autant sur un système que de le regarder tomber en panne. Petit florilège :
La fois où j'ai tout fait tomber moi-même. Pour qu'une page lente ne retarde pas toutes les autres, j'ai confié le rendu des pages à un pool de threads. Ensuite, pour vérifier que les déménagements de sites se faisaient sans accroc, j'ai envoyé un déluge de trafic de test sur les sites en production. Ils se sont figés pendant neuf minutes, et sont restés figés après l'arrêt du trafic. Chaque page gardait une connexion à la base de données tout en en demandant une deuxième ; avec des dizaines de threads et un pool de quinze connexions, chaque thread a fini par en attendre un autre. Pendant ce temps, le bilan de santé, qui ne touchait jamais à la base de données, annonçait joyeusement que tout allait bien.
La solution : des pools de base de données qui ne peuvent pas s'interbloquer, un limiteur qui transforme une surcharge en un rapide « réessayez » plutôt qu'en blocage (avec une part équitable pour chaque site, afin qu'un site très fréquenté ne puisse pas affamer les autres), un bilan de santé qui repère un processus coincé, un watchdog qui le redémarre et une règle ferme sur l'endroit où se font les tests de charge.
Le hoquet de 45 secondes. La plupart des déménagements de sites se passaient sans accroc, sauf un type : si le déménagement modifiait les règles de routage du site, l'ancienne et la nouvelle copie décrivaient brièvement la même route de deux façons différentes. La porte d'entrée, incapable de décider laquelle croire, n'en servait aucune pendant environ 45 secondes. Tout le reste du déménagement était correct, et c'est justement pour ça qu'il a fallu du temps pour repérer le problème.
Un site minuscule à 100 % de CPU. Une petite boutique de quelques centaines de visiteurs par jour saturait sans cesse son processeur. Le coupable n'était pas la clientèle, mais des scanners automatiques qui envoyaient plus de cent requêtes en dix secondes, à la recherche de fichiers comme /.env ou /wp-login.php qu'un site FolioPress n'a jamais. Chacune recevait une belle page « introuvable » entièrement mise en forme : environ 70 ms de travail, multipliées par cent.
La solution : ces requêtes reçoivent désormais un « Not found » de neuf octets avant tout vrai travail, et Cloudflare refoule la plupart d'entre elles avant même qu'elles n'arrivent.
« Serveur introuvable » pour un site en ligne. L'adresse web d'un tout nouveau site est créée pendant sa mise en place. Si quoi que ce soit cherchait le nom un instant trop tôt, les fournisseurs d'accès retenaient « ce nom n'existe pas » jusqu'à trente minutes, alors que le site fonctionnait parfaitement. La solution : une adresse générique (wildcard), pour que tout nom sous foliopress.io existe toujours ; les noms sans site reçoivent une page sympathique « il n'y a pas de site ici ».
Les voisins. Héberger beaucoup de sites côte à côte soulève des questions qu'un site isolé n'a jamais à se poser : ce que le code d'un site peut faire à un autre, et ce qu'un site peut revendiquer qui ne lui appartient pas. Un audit de sécurité avant l'ouverture des inscriptions a mis au jour quelques problèmes de ce genre. Tous sont corrigés : des cookies plus stricts, une preuve de propriété pour les domaines personnalisés, et chaque site monté dans son propre dossier séparé, pour qu'aucun site ne puisse atteindre les fichiers d'un autre.
Ce que je dirais à quelqu'un qui se lance
- Des briques ennuyeuses et éprouvées mènent loin. SQLite, un seul framework, de simples conteneurs. La plupart des problèmes difficiles se cachaient dans les jointures entre elles.
- Mesurer avant de croire. Le score de trafic, la panne et les pics de CPU ressemblaient tous à autre chose au départ.
- Concevoir pour la majorité silencieuse. La plupart des sites sont petits. Rendre le petit bon marché, c'est ce qui rend tout le reste possible.
La suite
La publication sur les réseaux sociaux, les diagrammes mermaid, mais surtout attendre les retours des utilisateurs pour façonner l'avenir.
Si vous avez un site qui doit parler plus d'une langue, faites l'essai sur foliopress.io — les 30 premiers jours sont gratuits. Et si vous êtes du genre technique, j'aimerais beaucoup savoir comment vous l'auriez construit.