TL;DR
- Un site de marque ou de contenu, ce sont des documents, pas une application. Le bon outil n'est donc pas un framework d'app.
- Vitesse : TTFB quasi instantané, Core Web Vitals au vert sans rien optimiser. Le HTML pré-écrit gagne toujours.
- Durabilité : aucune dépendance à pourrir. Ce site se « buildera » encore dans cinq ans, parce qu'il n'y a rien à builder.
- Souveraineté & sécurité : zéro surface d'attaque npm, aucun runtime, hébergeable n'importe où, même sur un VPS à 3 € en France.
- Le tout : un design system CSS avec des tokens, ~60 lignes de JS vanilla, et un petit script de déploiement en Perl. C'est tout.
La confession qui fâche
Je vais commencer par le truc impopulaire : pour 90 % des sites qu'on construit aujourd'hui, le framework est de trop. On a pris l'habitude de démarrer chaque projet par npx create-something, d'installer deux cents dépendances, de configurer un bundler, un linter, un serveur de dev avec hot-reload… pour afficher, au bout du compte, du texte et quelques boutons. On a confondu « faire un site web » avec « faire une application web ». Ce n'est pas la même discipline.
Ce site, lucdelbeato.fr, et celui de mon produit, gerer.ai, sont tous les deux du HTML/CSS/JS pur. Pas par paresse, par exigence. Quand on a vingt ans de web derrière soi, on finit par mesurer une chose : la complexité qu'on ajoute aujourd'hui, c'est la dette qu'on payera dans trois ans. Alors j'ajoute le moins possible.
La règle que je m'applique : le site le plus rapide, le plus durable et le plus souverain est celui qui embarque le moins de JavaScript possible. Parfois, zéro.
Pourquoi statique-sans-framework pour un site de marque
Quatre raisons, et aucune n'est idéologique. Ce sont des propriétés mesurables.
1. La vitesse, gratuite et imbattable
Quand le serveur n'a qu'à renvoyer un fichier HTML déjà écrit, le TTFB (time to first byte) est quasi instantané : pas de rendu serveur, pas d'hydratation, pas de waterfall de chunks JavaScript à télécharger puis à exécuter avant que la page soit utilisable. Le navigateur reçoit du HTML, il l'affiche. C'est tout.
Résultat concret : les Core Web Vitals sont au vert sans que j'aie à « optimiser » quoi que ce soit. Pas de Largest Contentful Paint plombé par un bundle de 300 Ko, pas de Cumulative Layout Shift causé par une hydratation tardive, pas de Total Blocking Time parce qu'il n'y a presque pas de JS à exécuter. La performance n'est pas une couche qu'on ajoute après, c'est une conséquence directe de l'architecture.
2. La durabilité : ça marchera encore dans cinq ans
Voici un test mental que je fais sur tout projet : si je reviens dessus dans cinq ans, est-ce que ça se relance ? Avec un projet framework, la réponse est souvent « non, sans une journée de souffrance ». Les versions ont bougé, le bundler est déprécié, une dépendance transitive a disparu de npm, le lockfile pointe vers des packages qui n'existent plus. C'est le dependency rot, et il est silencieux jusqu'au jour où vous en avez besoin.
Du HTML, du CSS et du JS vanilla, eux, ne pourrissent pas. Le standard du web est rétrocompatible de façon quasi religieuse : une page écrite proprement aujourd'hui s'affichera dans le navigateur de 2031. Il n'y a littéralement rien à « builder », donc rien qui puisse casser au build. C'est la définition de la boring technology, et c'est une fonctionnalité, pas un défaut.
3. Sécurité & souveraineté : aucune surface, aucun runtime
C'est l'argument qui me tient le plus à cœur. Un site sans node_modules, c'est un site sans chaîne d'approvisionnement npm à sécuriser. Pas de package compromis qui injecte un mineur de crypto ou un voleur de tokens dans votre build, souvenez-vous de event-stream, de node-ipc, des centaines de typosquats. Quand vos dépendances de production se comptent sur zéro doigt, votre surface d'attaque côté supply-chain est nulle.
Pas de runtime non plus : il n'y a pas de processus Node qui tourne, donc pas de CVE serveur à patcher en urgence, pas de fuite mémoire à surveiller. Et comme le livrable est une poignée de fichiers, je peux l'héberger n'importe où : un bucket, un CDN, ou, c'est mon cas, un petit VPS à 3 € par mois physiquement en France. La souveraineté des données commence par là : savoir exactement où vivent les octets, et ne dépendre de personne pour les servir.
4. Le coût : essentiellement zéro
Pas de fonction serverless facturée à l'invocation, pas de build minutes qui explosent, pas de plan « Pro » d'une plateforme d'hébergement pour avoir le droit à un domaine custom. Du statique se sert pour quelques euros par mois, voire gratuitement. Pour un site de marque qui doit juste exister, vite et bien, c'est l'équation parfaite.
Comment c'est vraiment fait ici
Assez de principes. Voici l'anatomie réelle de ce site, brique par brique.
Du HTML sémantique, point
Chaque page est du HTML écrit à la main : <header>, <nav>, <article>, <h2>, <blockquote>, <footer>. Le balisage décrit la structure du document, ce qui donne gratuitement l'accessibilité, le SEO et un rendu correct même si le CSS ne charge pas. Un article de blog, fondamentalement, c'est un document. Le traiter comme tel, plutôt que comme un arbre de composants, simplifie tout.
Un design system en CSS, avec des tokens
Pas de soupe de classes utilitaires, pas de CSS-in-JS, pas de Tailwind. Un seul fichier, site.css, organisé autour de custom properties CSS, mes tokens de design. Couleurs, espacements, rayons, typographies : tout est déclaré une fois en haut, et réutilisé partout. Changer l'accent mint du site, c'est modifier une seule variable.
:root{
--bg:#0b0f14; --ink:#e8eef2; --dim:#8aa0ab;
--mint:#5eead4; --amber:#fbbf24;
--space:1.25rem; --radius:14px;
--mono:"JetBrains Mono", ui-monospace, monospace;
}
.btn.mint{ background:var(--mint); color:#06251f; border-radius:var(--radius); }
Les custom properties, c'est ce qui rend le CSS natif aussi puissant qu'un design system de framework, sans le framework. Le navigateur les comprend nativement, elles cascadent, elles sont thématisables. On a tout ce qu'il faut depuis des années ; il suffisait d'arrêter d'attendre qu'un outil le réinvente.
~60 lignes de JavaScript vanilla
Le site a quelques touches interactives, et chacune tient en quelques lignes de vanilla, sans dépendance :
- Un toggle bilingue FR/EN piloté par des attributs
data-i18n(j'y reviens, c'est le morceau le plus intéressant) ; - Des révélations au scroll via
IntersectionObserver, les sections apparaissent en douceur quand elles entrent dans le viewport ; - Un menu mobile qui bascule une classe sur un clic ;
- Des chips de filtre sur la page blog, qui montrent/cachent les articles selon le tag.
L'IntersectionObserver mérite un mot : c'est l'API qui a tué le besoin de bibliothèques d'animation au scroll. On observe les éléments, et quand ils croisent le viewport on ajoute une classe CSS qui déclenche la transition. Trois lignes, zéro dépendance, soixante images par seconde.
const io = new IntersectionObserver((entries) => {
for (const e of entries) if (e.isIntersecting) {
e.target.classList.add('in'); // le CSS fait le reste
io.unobserve(e.target);
}
}, { threshold: 0.12 });
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
L'i18n maison : snapshot du DOM français, swap vers l'anglais
Le bilinguisme est le seul endroit qui demande un peu d'astuce. Le FR est la langue par défaut : il est écrit directement dans le HTML, donc visible même sans JS et indexé tel quel. Chaque nœud de texte porte un attribut data-i18n="clé", et un dictionnaire window.I18N_EN donne la traduction anglaise par clé.
Au premier chargement, le script prend un snapshot du DOM français (pour pouvoir revenir en arrière sans rechargement), puis, si l'utilisateur choisit EN, il remplace le textContent, ou l'innerHTML pour les nœuds qui contiennent des balises inline, marqués data-i18n-html. Le choix est persisté en localStorage, et la langue du navigateur (navigator.language) sert de valeur initiale. Voici le cœur, débarrassé du décor :
const FR = new Map(); // snapshot de l'original
document.querySelectorAll('[data-i18n],[data-i18n-html]').forEach(el => {
const html = el.hasAttribute('data-i18n-html');
FR.set(el, html ? el.innerHTML : el.textContent);
});
function setLang(lang){
const dict = window.I18N_EN || {};
document.querySelectorAll('[data-i18n],[data-i18n-html]').forEach(el => {
const key = el.getAttribute('data-i18n') || el.getAttribute('data-i18n-html');
const html = el.hasAttribute('data-i18n-html');
const val = lang === 'en' ? dict[key] : FR.get(el); // EN du dict, FR du snapshot
if (val == null) return;
if (html) el.innerHTML = val; else el.textContent = val;
});
document.documentElement.lang = lang;
localStorage.setItem('lang', lang);
}
const initial = localStorage.getItem('lang')
|| (navigator.language.startsWith('en') ? 'en' : 'fr');
setLang(initial);
C'est tout. Pas de react-intl, pas de fichiers .po, pas de build d'extraction. Le coût ? Je dois écrire chaque traduction à la main et garder le dictionnaire synchrone. C'est un vrai travail manuel, et c'est un compromis que j'assume pour deux langues. À dix langues, je changerais d'approche.
Les polices : Google Fonts, et l'honnêteté du « pas encore »
Je charge les polices via Google Fonts, avec preconnect. Soyons honnêtes : c'est le seul endroit où ce site dépend encore d'un tiers américain. La prochaine étape vers la souveraineté complète, c'est de self-héberger les fontes, télécharger les .woff2, les servir depuis mon propre domaine, et couper ce dernier lien externe. C'est sur la liste. Je préfère le dire plutôt que prétendre à une pureté que le site n'a pas encore atteinte.
Le déploiement : un petit script Perl/sed qui réécrit les chemins
Le seul « outillage » du projet est un script de déploiement d'une poignée de lignes. En dev sous MAMP, mes chemins sont préfixés par /lucdelbeato.fr/ ; en production, le site vit à la racine du domaine. Le script fait une passe de sed pour réécrire ces chemins, et rsync le tout vers le serveur. Pas de pipeline CI, pas d'étape de build, une réécriture de chaînes et une copie de fichiers.
# déploiement, réécrire les chemins dev → prod, puis pousser
perl -pi -e 's{/lucdelbeato\.fr/}{/}g' dist/**/*.html
rsync -az --delete dist/ deploy@vps-fr:/var/www/lucdelbeato.fr/
C'est volontairement primitif. Un script que je peux lire en entier en dix secondes ne me trahira jamais à 2 h du matin. C'est aussi ça, la boring tech : préférer l'outil que je comprends complètement à celui qui fait tout sauf ce que j'attends ce jour-là.
Soyons justes : quand un framework EST le bon choix
Je ne suis pas dogmatique, et ce serait malhonnête de l'être. Il y a des cas où un framework n'est pas un luxe mais la bonne réponse :
- Un état applicatif lourd, un dashboard, un éditeur, un outil métier où l'UI dépend en permanence de dizaines d'états qui s'influencent. Gérer ça à la main en vanilla devient vite ingérable.
- De l'UI temps réel, collaboration live, flux qui se mettent à jour en continu, rendus optimistes. Le modèle réactif d'un framework gagne clairement ici.
- De grosses équipes, la structure imposée d'un framework, ses conventions et son système de composants sont une grammaire commune qui permet à vingt personnes de ne pas se marcher dessus.
- Du routing client complexe, une vraie SPA avec des transitions de vues, du code-splitting par route, du préchargement. Réinventer ça en vanilla, c'est réécrire un framework en moins bien.
La règle, donc, n'est pas « jamais de framework ». C'est « faire correspondre l'outil au problème ». Un site de contenu, ce sont des documents : du HTML statique est l'outil exact. Une application, c'est de l'état : un framework est l'outil exact. Le péché, ce n'est pas d'utiliser React, c'est de l'utiliser pour afficher une page « à propos ».
Les compromis honnêtes du sans-framework
Tout choix a un prix, et je refuse de vous vendre une solution sans en montrer la facture :
- Pas de réutilisation de composants livrée d'office. Le header et le footer sont copiés sur chaque page. Au-delà de quelques pages, on ajoute une petite étape de templating (un include serveur, ou un générateur statique minimal), mais on reste loin d'un framework.
- L'i18n est manuelle. Comme vu plus haut : chaque traduction est écrite à la main et le dictionnaire doit rester synchrone. C'est tenable à deux langues, pénible à dix.
- On hand-roll ce qu'un framework donne gratis. Le routing, la gestion d'état, la réactivité, le binding de données : si vous en avez besoin, vous les écrivez vous-même. C'est libérateur quand vous n'en avez pas besoin, et épuisant le jour où si.
Le bon réflexe, c'est de regarder ces lignes en face avant de choisir. Pour ce site, la colonne « coûts » est minuscule et la colonne « bénéfices » est énorme. Pour votre app SaaS, ce sera peut-être l'inverse. C'est exactement le point.
Ce que je retiens
- Faites correspondre l'outil au problème. Un site de contenu ou de marque, ce sont des documents, pas une application. Choisir un framework d'app pour ça, c'est mettre un moteur de Formule 1 dans un vélo.
- La « boring tech » est une fonctionnalité. Pour ce qui doit durer et rester sûr, l'ennuyeux et l'éprouvé battent le nouveau et l'excitant. Le code qui ne change pas est le code qui ne casse pas.
- Le site le plus rapide et le plus souverain est celui qui livre le moins de JavaScript. Chaque kilo-octet de JS est un coût de performance, une surface d'attaque, et une dépendance future. Le meilleur JS est souvent celui qu'on n'écrit pas.
- La transparence est un argument de sécurité. Quand view-source dit la vérité, il n'y a nulle part où cacher un comportement. Pour un site qui parle de souveraineté, le médium est le message.
Cette philosophie n'est pas réservée à un blog perso. C'est exactement le même raisonnement que j'applique à l'infra de Gérer.ai : réduire la surface, supprimer les dépendances inutiles, savoir où vivent les octets, et préférer ce qu'on comprend entièrement à ce qui « marche par magie ». Le statique n'est pas un retour en arrière, c'est une discipline.
La même philosophie, appliquée à l'IA souveraine
Gérer.ai aussi est construit sans framework, statique, rapide, souverain. Et le produit pousse l'idée plus loin : des agents IA déployés sur votre infra, avec des modèles open-source auto-hébergés. Réduire la surface, maîtriser où vivent les octets, ne dépendre de personne.
Découvrir Gérer.ai ↗