Catégorie : Méthodes, Design & Philosophie

Identité, méthode, vision de l’ingénierie

  • Comment je conçois un site fiable et maintenable

    Fiable et maintenable — deux mots qu’on entend souvent ensemble, et qu’on oppose rarement à quoi que ce soit de concret. Tout le monde veut un site fiable et maintenable. Personne ne veut un site fragile et incompréhensible. La vraie question n’est pas l’objectif — c’est les décisions qui y mènent. Cet article est ma réponse à cette question : ce que je fais concrètement, dans quel ordre, et pourquoi.

    Ce que « fiable et maintenable » signifie en pratique

    Fiable ne veut pas dire « sans bugs ». Aucun système non trivial n’est sans bugs. Fiable veut dire que les bugs sont détectés avant la production, que quand ils arrivent en production leur impact est limité, et qu’ils sont corrigés rapidement. Maintenable ne veut pas dire « code parfait ». Ça veut dire que n’importe quel membre de l’équipe peut comprendre, modifier, et déployer n’importe quelle partie du système sans avoir à solliciter le seul qui « sait comment ça marche ».

    Fiabilité — détection · les bugs sont attrapés par les tests, le typage ou le lint — avant d’atteindre les utilisateurs
    Fiabilité — confinement · quand un bug passe en production, son impact est progressif et réversible
    Maintenabilité — lisibilité · le code exprime l’intention, pas seulement le mécanisme — on comprend sans commenter
    Maintenabilité — autonomie · chaque partie du système est modifiable indépendamment — sans effets de bord cachés

    Ces quatre propriétés ne s’obtiennent pas en ajoutant de la complexité — elles s’obtiennent en la maîtrisant. Un système avec trois couches bien séparées est plus fiable et plus maintenable qu’un système avec dix couches mal définies. La sophistication technique, utilisée sans discernement, produit l’effet inverse de celui recherché.

    Décision n°1 — Choisir une stack ennuyeuse

    Le premier choix structurant est la stack. Et la meilleure stack pour un site fiable et maintenable est, presque systématiquement, une stack ennuyeuse — pas au sens péjoratif, mais au sens de « éprouvée, documentée, avec une large communauté, des patterns établis, et des développeurs disponibles sur le marché ».

    Critère Stack ennuyeuse Stack à la mode
    Maturité Patterns connus, erreurs documentées Rough edges, breaking changes fréquents
    Recrutement Large pool de compétences disponibles Compétences rares, coûteuses, volatiles
    Débogage Stack Overflow rempli, erreurs connues Peu de ressources, problèmes non documentés
    Dépendances Écosystème stable, mises à jour prévisibles Risque d’abandon, migration forcée
    Onboarding Courbe d’apprentissage bien cartographiée Documentation incomplète ou en mutation

    Ce n’est pas un argument contre l’innovation — c’est un argument pour distinguer les expérimentations des projets de production. Tester un nouveau framework sur un side project est une excellente idée. L’adopter pour le système de paiement d’un e-commerce parce qu’il a eu beaucoup d’étoiles GitHub en six mois est une décision qui se paiera en maintenance pendant des années.

    Ma règle de sélection : une technologie n’entre dans ma stack de production que si elle a au moins trois ans d’existence en production chez d’autres, une communauté active, et un plan de migration documenté pour ses breaking changes. Ce n’est pas de la prudence excessive — c’est le coût de la maintenance à long terme pris au sérieux dès le départ.

    Décision n°2 — Typer tout ce qui traverse une frontière

    TypeScript strict n’est pas une contrainte — c’est un filet de sécurité qui détecte des bugs à la compilation plutôt qu’en production. La vraie valeur du typage n’est pas dans les fonctions internes d’un module, où le contexte est immédiat — elle est dans ce qui traverse les frontières : les API, les événements, les arguments de fonctions exportées, les données en base.

    // tsconfig.json — configuration stricte
    {
      "compilerOptions": {
        "strict":               true,    // active tous les checks stricts
        "noUncheckedIndexedAccess": true,    // arr[i] peut être undefined
        "exactOptionalPropertyTypes": true, // distinction undefined / absent
        "noImplicitReturns":    true,    // toutes les branches retournent une valeur
        "noFallthroughCasesInSwitch": true
      }
    }
    
    // Typer les frontières — API response
    // ✗ Sans type — une propriété manquante casse en production
    const data = await fetch('/api/user').then(r => r.json());
    data.username.toUpperCase()  → TypeError si username est undefined
    
    // ✓ Avec validation au runtime — zod parse à la frontière
    import { z } from 'zod';
    
    const UserSchema = z.object({
      id:       z.string().uuid(),
      username: z.string().min(1),
      email:    z.string().email(),
      role:     z.enum(['admin', 'user', 'readonly']),
    });
    
    type User = z.infer<typeof UserSchema>;
    
    const raw  = await fetch('/api/user').then(r => r.json());
    const user = UserSchema.parse(raw);
    → si la réponse API est malformée, l'erreur est explicite ici — pas plus loin dans le code

    La validation au runtime (zod, valibot, arktype) combinée au typage statique est la réponse aux frontières — elle garantit que les données qui entrent dans le système correspondent exactement au contrat déclaré. Une API qui change son schéma sans prévenir ne passe plus silencieusement — elle produit une erreur explicite, au bon endroit, avec un message lisible.

    Décision n°3 — Des tests proportionnels au risque, pas à la couverture

    La couverture de code est un indicateur utile — mais un mauvais objectif. Un projet à 95% de couverture peut avoir des tests qui valident uniquement que les fonctions s’exécutent sans lever d’exception. Un projet à 70% de couverture peut avoir des tests qui couvrent tous les cas d’erreur des parcours critiques. Le bon objectif est la couverture des risques métier, pas la couverture des lignes.

    // Calibrer les tests par niveau de risque
    
    // Risque haut — logique métier critique
    → tests unitaires exhaustifs : tous les cas nominaux, toutes les erreurs, tous les edge cases
    → ex. : calcul de prix, gestion des permissions, validation des données
    
    describe('calculateOrderTotal', () => {
      it('applique la remise sur le sous-total avant les taxes', () => {
        expect(calculateOrderTotal({ subtotal: 100, discount: 0.2, taxRate: 0.2 })).toBe(96);
      });
      it('rejette une remise supérieure à 100%', () => {
        expect(() => calculateOrderTotal({ subtotal: 100, discount: 1.5, taxRate: 0.2 }))
          .toThrow('Discount cannot exceed 100%');
      });
    });
    
    // Risque moyen — contrats d'intégration
    → tests d'intégration sur les endpoints critiques
    → ex. : POST /api/orders retourne 201 avec le bon corps
    
    // Risque haut — parcours utilisateurs critiques
    → tests E2E smoke : connexion, checkout, action principale du produit
    → 10 à 20 tests — suffisant pour détecter les régressions majeures en < 3 min
    
    // Risque bas — composants UI sans logique
    → pas de tests ou snapshot tests — la valeur ne justifie pas le coût de maintenance

    La question à se poser avant chaque test : « si ce code régresse, comment m’en rendra-t-il compte ? » Si la réponse est « un utilisateur m’appelle », il manque un test. Si la réponse est « ce test unitaire cassera en deux millisecondes », c’est au bon niveau. Le test n’est pas une fin en soi — c’est un mécanisme de détection de régression.

    Décision n°4 — Séparer les couches pour les changer indépendamment

    La maintenabilité repose en grande partie sur une propriété simple : pouvoir changer une chose sans casser une autre. Cette propriété s’obtient en séparant les couches de responsabilité — pas par conformité à un pattern d’architecture, mais parce que les couches évoluent à des rythmes différents.

    // Séparation des couches — exemple concret Next.js / Node
    
    // Couche UI — affichage, pas de logique métier
    // app/orders/page.tsx
    export default async function OrdersPage() {
      const orders = await getOrders();     // appel au service — pas de SQL ici
      return <OrderList orders={orders} />;
    }
    
    // Couche service — logique métier, pas de SQL
    // services/orders.ts
    export async function getOrders(userId: string): Promise<Order[]> {
      const raw = await orderRepository.findByUser(userId);
      return raw.filter(o => o.status !== 'cancelled');  // règle métier ici
    }
    
    // Couche accès aux données — SQL, pas de logique métier
    // repositories/order.ts
    export const orderRepository = {
      async findByUser(userId: string): Promise<OrderRow[]> {
        return db.query(
          'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC',
          [userId]
        );
      },
    };

    Cette séparation n’est pas du dogmatisme architectural — c’est une décision pratique. Migrer de PostgreSQL vers une autre base de données n’affecte que la couche repository. Changer la règle métier « masquer les commandes annulées » n’affecte que le service. Revoir le design de la liste des commandes n’affecte que le composant UI. Sans séparation, chaque changement est une opération chirurgicale dans un code entremêlé.

    // ✗ Le problème de l'entrelacement — impossible à maintenir
    // app/orders/page.tsx — SQL + logique métier + UI dans un seul fichier
    export default async function OrdersPage() {
      const rows = await db.query('SELECT * FROM orders WHERE...');
      const filtered = rows.filter(o => o.status !== 'cancelled');
      const total    = filtered.reduce((acc, o) => acc + o.total, 0);
      return <div><p>Total : {total}</p>{filtered.map(...)}</div>;
      → modifier la query SQL oblige à comprendre le composant
      → tester la logique métier oblige à mocker la base de données
      → un nouveau développeur ne sait pas par où commencer
    }

    Décision n°5 — L’observabilité dès le premier déploiement

    Un site fiable est un site dont on sait à tout moment s’il fonctionne correctement — pas parce qu’un utilisateur a signaléun problème, mais parce que les métriques l’ont indiqué avant lui. L’observabilité n’est pas une fonctionnalité à ajouter plus tard — c’est une propriété qu’on construit dès le premier déploiement, parce que rétrofit est dix fois plus coûteux.

    // Les trois couches d'observabilité minimales
    
    // 1. Logs structurés — lisibles par les humains et les machines
    import pino from 'pino';
    
    const logger = pino({
      level:     process.env.LOG_LEVEL ?? 'info',
      base:      { service: 'api', version: process.env.APP_VERSION },
      timestamp: () => `,"time":"${new Date().toISOString()}"`,
    });
    
    // Log avec contexte structuré — interrogeable, filtrable
    logger.info({ userId, orderId, amount, duration: 142 }, 'order.created');
    → recherchable par userId, orderId — pas du texte plat non structuré
    
    // 2. Métriques — taux d'erreur, latence, saturation
    // voir article "pipeline CI/CD" pour l'instrumentation Prometheus complète
    
    // 3. Alertes — notification proactive, pas réactive
    Règle minimale : alerte si taux d'erreur 5xx > 1% sur 5 minutes
    Règle minimale : alerte si latence P95 > 2x la baseline sur 10 minutes
    Règle minimale : alerte si uptime < 99.9% sur l'heure passée

    La différence entre un log structuré et un `console.log` n’est pas esthétique — c’est la différence entre « chercher dans des logs plats pendant deux heures » et « filtrer par orderId et trouver l’erreur en trente secondes ». Le choix se fait au moment d’écrire le premier log — pas au moment de l’incident.

    Décision n°6 — Une documentation qui reste à jour

    La documentation technique se dégrade inévitablement — sauf si elle est intégrée dans le workflow de développement de façon à ce que la mettre à jour soit la résistance minimale. Une documentation dans un Confluence séparé, mise à jour manuellement, sera périmée dans trois mois. Une documentation dans le dépôt, à côté du code, avec une PR qui la met à jour en même temps que le code, a une chance de survivre.

    Ce qui mérite d’être documenté

    Les décisions d’architecture (ADR — Architecture Decision Records) : non pas « comment ça marche » mais « pourquoi on a choisi ça plutôt qu’autre chose ». Un docs/decisions/001-use-postgresql-not-mongodb.md qui documente le contexte, les alternatives considérées, et la décision évite de rejouer la même conversation six mois plus tard avec un nouveau développeur. Les runbooks : comment déployer, comment rollback, comment débugguer les cas connus. Les contrats d’API : OpenAPI ou tRPC — pas en documentation prose, mais en spécification machine-readable.

    Ce qui ne mérite pas d’être documenté

    Tout ce que le code exprime déjà clairement. Un commentaire qui répète ce que le code dit (// incrémente i au-dessus de i++) est du bruit — il sera périmé dès que le code changera, et personne ne le mettra à jour. Le code s’auto-documente quand les noms sont précis (calculateVATIncludedPrice plutôt que calcPrice), quand les types sont explicites, et quand les tests décrivent les comportements attendus sous forme de cas nommés. La documentation prose compense le code obscur — le bon code a peu besoin de commentaires.

    Nuances — ce que je n’applique pas systématiquement

    Le positionnement « voilà ma méthode » est incomplet sans les nuances. Ces décisions ont des coûts — et certains projets ne les justifient pas.

    Nuance

    TypeScript strict sur tous les projets

    Sur un script d’automatisation interne qui durera six mois et sera maintenu par une seule personne, strict: true peut ralentir plus qu’il n’aide. Le typage strict apporte le plus de valeur là où la base de code est longue, partagée par plusieurs développeurs, et amenée à évoluer sur plusieurs années. Sur un prototype ou un outil jetable, la pragmatique peut primer sur la rigueur — à condition de ne pas se mentir à soi-même sur la durée de vie réelle du code.

    Nuance

    La séparation des couches comme sur-ingénierie

    Sur un MVP ou un site vitrine sans logique métier complexe, imposer une architecture en couches strictes (controller / service / repository) ajoute de la cérémonie sans valeur. La règle est proportionnelle à la complexité : une route Next.js qui lit une table et affiche un résultat peut avoir le SQL directement dans le composant serveur. Quand la logique se complexifie, la séparation s’impose naturellement. L’erreur est de pré-découpler ce qui ne mérite pas encore de l’être — ou, à l’inverse, de ne jamais découpler quand la complexité l’a rendu nécessaire depuis longtemps.

    Nuance

    L’observabilité complète dès le premier jour

    Déployer Prometheus, Grafana, et Loki pour un projet à dix utilisateurs est une distraction. L’observabilité minimale — logs structurés, alertes sur les erreurs 5xx, uptime monitoring — est non négociable dès le premier déploiement. L’observabilité avancée (traces distribuées, métriques métier, dashboards personnalisés) se construit quand le trafic et la complexité le justifient. L’erreur symétrique est d’attendre que le projet soit critique pour commencer — à ce stade, le rétrofit coûte dix fois plus cher que de l’avoir fait dès le départ.

    Questions fréquentes sur la conception d’un site fiable

    Comment rendre un site web plus fiable sans sur-ingénierie ?

    La fiabilité n’est pas proportionnelle à la complexité architecturale — elle vient de quelques décisions bien appliquées : des tests qui couvrent les parcours critiques et la logique métier à risque, un typage qui attrape les erreurs à la compilation, des logs structurés qui permettent de diagnostiquer les incidents rapidement, et un pipeline CI qui bloque les régressions avant qu’elles atteignent la production. Un site simple avec ces quatre propriétés est plus fiable qu’un site complexe qui en manque une. La règle est de commencer par les propriétés qui offrent le meilleur rapport fiabilité/coût de mise en œuvre — les tests et le typage en premier, l’observabilité avancée quand la complexité le justifie.

    Quelle architecture utiliser pour un site maintenable sur le long terme ?

    L’architecture la plus maintenable est celle qui permet de changer une chose sans casser une autre — pas l’architecture la plus sophistiquée. La séparation des couches (UI / logique métier / accès aux données) est la décision la plus impactante : chaque couche évolue à son propre rythme, peut être testée indépendamment, et peut être remplacée sans affecter les autres. La stack importe moins que la clarté des frontières entre responsabilités. Un projet bien découpé avec une stack classique sera plus maintenable qu’un projet entrelacé avec une stack à la pointe — même si cette dernière semble plus impressionnante en demo.

    Faut-il utiliser TypeScript pour tous les projets web ?

    TypeScript strict est recommandé pour tout projet qui durera plus de six mois, sera maintenu par plusieurs développeurs, ou touche à de la logique métier critique. Le bénéfice est maximal sur les frontières du système — les appels API, les arguments de fonctions partagées, les données en base — là où une propriété manquante ou mal typée peut causer une erreur silencieuse en production. Sur un script d’automatisation court ou un prototype exploratoire, le compromis est différent. La règle n’est pas « TypeScript partout » — c’est « TypeScript là où la rigueur du contrat dépasse le coût de la setup ».

    Quel niveau de couverture de tests viser pour un site fiable ?

    La couverture de lignes est un indicateur insuffisant — viser 80% de couverture sans critère de qualité sur ce qui est couvert peut donner une fausse impression de sécurité. Le bon critère : couvrir exhaustivement la logique métier à risque (calculs, permissions, validation), couvrir les contrats d’intégration sur les endpoints critiques, et couvrir les parcours utilisateurs principaux avec des tests E2E smoke. Ce qui peut ne pas être couvert : les composants UI sans logique, les glue code de configuration, les adapters simples. Une suite qui couvre les bons endroits à 60% est plus utile qu’une suite qui couvre tout à 90% avec des tests qui n’affirment rien de significatif.

    Comment documenter un projet web pour qu’il reste maintenable ?

    La documentation qui survit est celle qui est co-localisée avec le code et mise à jour dans la même PR que le code qu’elle décrit. Les ADR (Architecture Decision Records) dans un dossier docs/decisions/ documentent les choix structurants et leur contexte — non pas « comment ça marche » mais « pourquoi ce choix plutôt qu’un autre ». Les runbooks (déploiement, rollback, débogage des cas connus) réduisent le temps de réaction en incident. Les contrats d’API en OpenAPI ou tRPC évitent les désaccords frontaux/backend. Ce qui ne mérite pas d’être documenté : ce que le code exprime déjà clairement. Un code avec des noms précis et des tests descriptifs se documente en grande partie lui-même.

    Quand mettre en place l’observabilité sur un projet web ?

    L’observabilité minimale — logs structurés, alerte sur les erreurs 5xx, monitoring d’uptime — doit être en place dès le premier déploiement en production. Attendre que le projet soit critique pour l’ajouter coûte dix fois plus cher en rétrofit qu’en conception initiale. L’observabilité avancée (traces distribuées, métriques métier, dashboards Grafana) se construit progressivement, quand le trafic et la complexité le justifient. La règle est simple : on ne déploie pas en production sans savoir si ça fonctionne. Les logs structurés et une alerte sur le taux d’erreur satisfont cette condition minimale avec très peu de code — c’est le plancher non négociable.

    Conclusion

    Fiable et maintenable ne sont pas des propriétés qu’on ajoute après coup — ce sont des décisions qu’on prend (ou ne prend pas) à chaque étape de la conception. La stack ennuyeuse, le typage aux frontières, les tests calibrés sur le risque, la séparation des couches, les logs structurés, la documentation qui reste à jour — chacune de ces décisions est indépendante, et chacune contribue. Aucune ne nécessite une refonte complète. Elles s’ajoutent progressivement, et leurs effets sont cumulatifs.

    La nuance honnête : aucune de ces décisions ne garantit un système parfait. Elles garantissent un système dont les problèmes sont détectables rapidement, diagnostiquables efficacement, et corrigeables sans chirurgie dans du code entrelacé. C’est la définition pratique de la fiabilité — pas l’absence de bugs, mais la capacité à les gérer sans panique.

    Un site fiable et maintenable n’est pas le résultat d’une architecture grandiose — c’est le résultat de décisions raisonnables, prises tôt, appliquées avec constance. La sophistication est tentante. La discipline, plus utile.

  • Pourquoi un site HTML minimaliste pour PYKEngine

    Le site de PYKEngine est un fichier HTML, un fichier CSS, quelques SVG. Pas de React, pas de bundler, pas de Node.js en production. Ce n’est pas une contrainte budgétaire ou une limite technique — c’est une décision d’architecture raisonnée, documentée, et défendable. L’industrie du développement web a une tendance structurelle à l’isomorphisme de stack : appliquer la même architecture à tous les problèmes, indépendamment de leur nature, parce que c’est ce qu’on connaît. Un site vitrine statique construit avec un framework full-stack n’est pas plus professionnel qu’un site HTML bien écrit. Il est juste plus complexe — et la complexité accidentelle est un risque, pas une feature.

    Ce que « minimaliste » ne signifie pas

    Minimaliste ne signifie pas artisanal, ni primitif, ni fait par défaut. C’est une réponse proportionnée à un problème précis. Un site portfolio de consulting — quelques pages statiques, des articles, un formulaire de contact — n’a structurellement pas les mêmes contraintes qu’une application SaaS avec authentification, catalogue dynamique et dashboard temps réel.

    L’erreur de raisonnement la plus courante est l’isomorphisme de stack : utiliser partout la même architecture parce que c’est l’architecture qu’on maîtrise, pas parce que c’est l’architecture que le problème appelle. Un développeur React qui construit un site vitrine en React n’optimise pas pour le problème — il optimise pour sa zone de confort. Ce n’est pas un reproche, c’est une observation sur comment les décisions d’architecture se prennent réellement dans les équipes.

    La question n’est pas « HTML ou React ? ». C’est « quelle est la complexité minimale nécessaire pour ce problème précis, et quel est le coût des couches supplémentaires ? »

    L’argument de performance — structurel, pas configuré

    Un fichier HTML servi depuis un CDN n’a rien à calculer. Le navigateur reçoit le contenu final, le parse, l’affiche. Il n’y a pas de bundle JavaScript à télécharger avant que la page soit visible, pas de runtime à initialiser, pas d’hydratation à attendre. La performance maximale est le point de départ structurel — pas un objectif à atteindre après optimisation.

    HTML statique / CDN — LCP ~300ms JS transféré : 0 Ko. TTI identique au LCP — pas d’hydratation, pas de runtime. TTFB : celui du CDN le plus proche, typiquement 20–50ms en Europe. Lighthouse 100 par défaut, sans optimisation. Durée de vie du fichier : indéfinie — un HTML valide de 2005 fonctionne en 2026 sans modification.
    Next.js App Router (SSR/RSC) — LCP ~600ms JS runtime minimum : ~85 Ko (React + Next.js runtime). TTI : ~900ms avec hydratation. TTFB : celui du serveur Node.js + temps de rendu RSC. Lighthouse 100 atteignable avec du travail. Migration majeure tous les 12–18 mois. Node.js en production = surface d’attaque supplémentaire.
    React + Vite (SPA) — LCP ~900ms+ JS bundle minimum : ~130 Ko (React + Vite runtime). TTI : ~1200ms — la page n’est pas interactive avant que le JS soit exécuté. LCP souvent dominé par du contenu placé après l’hydratation. Lighthouse 100 nécessite un effort significatif de configuration et de split.

    Ces chiffres sont indicatifs mais structurellement corrects. Un Next.js optimisé peut approcher 500ms de LCP avec du travail. Un HTML statique y est sans effort. La différence n’est pas la performance peak — c’est le coût pour l’atteindre et le maintenir.

    Ce que les benchmarks ne montrent pas : un site Next.js dégrade progressivement si les dépendances ne sont pas maintenues à jour, si les optimisations ne sont pas réappliquées après chaque migration majeure, et si les nouveaux développeurs ne connaissent pas les configurations spécifiques. Un site HTML ne dégrade pas — sa performance est structurellement stable dans le temps.

    L’argument de sécurité — surface d’attaque et supply chain

    La surface d’attaque d’un site statique est structurellement minimale. Il n’y a pas de serveur d’application à compromettre, pas de base de données à requêter, pas de runtime à exploiter, pas de dépendances à empoisonner. Les vecteurs d’attaque se réduisent à la configuration du CDN et aux headers HTTP — configurables une fois, stables dans le temps.

    # npm audit sur un projet Next.js standard — 6 mois sans maintenance
    found 4 vulnerabilities (1 low, 2 moderate, 1 high)
      run `npm audit fix` to fix them, or `npm audit fix --force`
      (breaking changes possible)
    
    # npm audit sur le site PYKEngine
    No known vulnerabilities found
      (0 packages installed)

    Ce n’est pas de la chance. C’est la conséquence directe d’avoir zéro dépendance. Les attaques supply chain npm sont documentées et récurrentes : event-stream (2018), ua-parser-js (2021), node-ipc (2022). Chaque paquet dans node_modules est un vecteur potentiel — et un projet Next.js standard en installe 700 à 800. Il n’existe pas de moyen raisonnable d’auditer manuellement cette surface.

    Un site statique n’a pas de supply chain logicielle à sécuriser. C’est une propriété structurelle, pas une configuration.

    L’argument de maintenance — coût total sur la durée

    La durée de vie d’un fichier HTML est celle du web. Un fichier HTML écrit en 2010 fonctionne dans tous les navigateurs en 2026 sans modification. Un projet create-react-app de 2018 est officiellement déprécié. Un projet Next.js 12 nécessite une migration pour passer à Next.js 15. Un projet Webpack 4 est en fin de vie.

    # Dépendances directes d'un projet Next.js standard
    next, react, react-dom, typescript, @types/react, @types/node,
    eslint, eslint-config-next, tailwindcss, postcss, autoprefixer
    → ~720 packages installés, ~280 Mo dans node_modules
    → breaking changes majeurs tous les 12–18 mois
    → migrations : Next.js 12→13→14→15 = ~3 jours de travail chacune
    
    # Dépendances du site PYKEngine
    → 0 package
    → 0 node_modules
    → 0 build step
    → 0 pipeline CI/CD de compilation
    → 0 migration prévue

    Le coût total de possession d’une stack framework inclut des postes souvent sous-estimés en amont : les migrations majeures, la gestion des breaking changes dans les dépendances transitives, les alertes de sécurité à traiter, les pipelines de build à maintenir, la documentation à mettre à jour à chaque montée de version, la montée en compétence de chaque nouveau développeur sur les spécificités du setup.

    Pour un site statique de quelques pages, ce coût n’a aucune contrepartie en valeur fonctionnelle. C’est de la complexité sans retour.

    Le signal — ce que le choix de stack communique

    Il y a un argument méta que les arguments techniques ne couvrent pas entièrement : ce que le choix de stack révèle sur le raisonnement de celui qui le fait.

    Un consultant technique dont le site vitrine est sur-ingénié envoie un signal ambigu. Si vous choisissez un framework full-stack avec SSR, hydratation, système de composants et pipeline de build pour afficher votre nom, trois pages de services et un formulaire de contact, vous signalez que vous optimisez pour la sophistication apparente plutôt que pour la valeur délivrée. C’est exactement le type de décision qu’un client paye un consultant senior pour éviter dans ses propres équipes.

    L’expertise d’un ingénieur senior ne se mesure pas à la liste des technologies qu’il est capable d’utiliser. Elle se mesure à sa capacité à choisir la solution adaptée à chaque contexte — y compris, et surtout, quand cette solution est la plus simple disponible. Choisir HTML pour un site statique n’est pas un aveu de limitation. C’est une démonstration de discernement.

    Choisir la solution la plus simple qui fonctionne est une compétence, pas une limitation. C’est souvent la décision la plus difficile à défendre dans un environnement qui valorise la complexité technique pour elle-même — et la plus révélatrice de la maturité de celui qui la prend.

    Nuances — ce que cette approche ne dit pas

    Un argument aussi tranchant mérite d’être tempéré. Quelques nuances que l’enthousiasme pour la simplicité peut faire oublier.

    La productivité éditoriale a un coût réel

    Modifier un fichier HTML directement demande de savoir éditer du HTML. Pour PYKEngine, c’est trivial — le développeur et le rédacteur sont la même personne. Dès que ce n’est plus le cas, l’absence de CMS devient un frein réel. Un site HTML statique sans système de gestion de contenu place la barrière d’édition au niveau technique, pas au niveau métier. C’est acceptable quand l’auteur est développeur. Ce n’est pas scalable quand l’équipe éditoriale est non-technique. Si ce besoin émerge, la réponse n’est pas de migrer vers un framework full-stack — c’est d’ajouter un générateur statique (Hugo, Eleventy, Astro) avec un CMS headless, en conservant les bénéfices de la génération statique.

    La duplication de code devient un problème à l’échelle

    Quand le nombre de pages grandit, l’absence de composants et de templates partagés génère de la duplication. Le header, le footer, les métadonnées SEO — reproduits dans chaque fichier HTML. Sur 5 pages, c’est gérable. Sur 50, c’est une dette de maintenance. La solution n’est pas React — c’est un générateur statique avec un système de templates (Nunjucks, Handlebars, les layouts Astro). Il est possible de conserver les bénéfices du statique (performance, sécurité, maintenance) tout en ajoutant une couche d’abstraction pour la duplication, sans framework JavaScript.

    Ce choix est contexte-dépendant — pas une règle générale

    Le site de PYKEngine est un site vitrine de consulting avec quelques pages et des articles. Ce contexte précis justifie le HTML statique. Un site e-commerce, une application SaaS, une plateforme collaborative, un tableau de bord temps réel — aucun de ces contextes ne se prête au HTML statique sans adaptation majeure. L’argument de cet article n’est pas « le HTML statique est toujours mieux ». Il est « le HTML statique est la meilleure réponse à ce problème précis, et utiliser un framework ici serait de la complexité accidentelle ». Le discernement consiste à savoir faire la distinction.

    Quand le HTML statique ne tient plus

    L’honnêteté intellectuelle exige de nommer les limites explicitement. Le HTML statique cesse d’être la bonne réponse quand :

    Contrainte Seuil de bascule Alternative adaptée
    Contenu dynamique Données personnalisées par utilisateur, authentification, temps réel Next.js / Remix / SvelteKit selon la complexité
    Volume éditorial Contenu mis à jour fréquemment par des non-développeurs Astro + CMS headless (Sanity, Contentful), ou Hugo
    Richesse d’interface Interactions complexes, état global, formulaires dynamiques multi-étapes React, Vue, Svelte selon le contexte et l’équipe
    Taille d’équipe front Plusieurs développeurs front en parallèle sur un même codebase Système de composants avec un framework — la convention prime
    Volume de pages Dizaines ou centaines de pages avec structure partagée Générateur statique (Astro, Hugo, Eleventy) — statique conservé
    Internationalisation Contenu multilingue avec routing par locale Next.js i18n ou framework avec i18n natif

    Aucune de ces contraintes ne s’applique actuellement au site de PYKEngine. Ce qui valide le choix — mais uniquement dans ce contexte.

    Questions fréquentes sur le HTML statique vs les frameworks JavaScript

    Pourquoi utiliser du HTML statique plutôt que React ou Next.js pour un site vitrine ?

    Pour un site vitrine statique — quelques pages, des articles, un formulaire de contact — le HTML statique servi depuis un CDN offre structurellement les meilleures performances (LCP ~300ms, sans optimisation), la surface d’attaque minimale (aucun runtime serveur, aucune dépendance), et le coût de maintenance le plus bas (aucune migration de framework, aucune alerte de sécurité de dépendances). React et Next.js résolvent des problèmes réels — contenu dynamique, état utilisateur, interfaces riches — qui n’existent pas sur un site vitrine. Les utiliser ici génère de la complexité accidentelle : du coût de maintenance, des dépendances à sécuriser, un pipeline de build à maintenir, sans contrepartie fonctionnelle. C’est un problème de dimensionnement de solution, pas un problème de technologie.

    Un site HTML statique peut-il atteindre un score Lighthouse de 100 ?

    Oui — et c’est son point de départ structurel, pas un objectif à atteindre avec du travail. Un fichier HTML bien écrit servi depuis un CDN n’a aucun JavaScript à exécuter, aucune hydratation à attendre, aucun bundle à télécharger. Le LCP est typiquement le délai CDN le plus proche (~20–50ms en Europe), le TBT est nul (pas de thread principal bloqué par du JS), et le CLS est nul si les images ont des dimensions définies. Avec un framework JavaScript, un score Lighthouse de 100 est atteignable — mais nécessite du travail actif (code splitting, lazy loading, élimination des scripts bloquants) et doit être réappliqué après chaque migration majeure. Le HTML statique y est sans effort.

    Quelle est la différence entre un site HTML statique et un générateur de sites statiques ?

    Un site HTML statique est un ensemble de fichiers HTML écrits directement, sans étape de compilation. Un générateur de sites statiques (Hugo, Astro, Eleventy, Jekyll) est un outil qui prend des templates et du contenu en entrée et produit des fichiers HTML en sortie — le résultat final servi aux visiteurs est du HTML statique identique. La différence est dans la productivité de développement : le générateur permet de factoriser les éléments communs (header, footer, navigation), de gérer le contenu dans des fichiers Markdown ou via un CMS headless, et de générer des dizaines ou centaines de pages automatiquement. Pour quelques pages, l’HTML direct est plus simple. Au-delà d’une dizaine de pages avec du contenu récurrent, un générateur statique apporte la même performance et sécurité avec une meilleure maintenabilité.

    Les attaques supply chain npm sont-elles un risque réel pour un site web standard ?

    Oui — et plusieurs incidents documentés l’ont prouvé. En 2018, le paquet npm event-stream (2,5 millions de téléchargements/semaine) a été compromis pour voler des portefeuilles Bitcoin. En 2021, ua-parser-js (8 millions de téléchargements/semaine) a été infecté pour installer un malware. En 2022, node-ipc a été délibérément modifié pour effacer des fichiers sur les machines russes et biélorusses. Un projet Next.js standard installe 700 à 800 packages — chacun peut être compromis directement ou via ses propres dépendances transitives. Il n’existe pas de moyen pratique d’auditer cette surface. Un site sans dépendances n’est pas exposé à ce vecteur d’attaque — c’est une propriété structurelle, pas une précaution de configuration.

    Est-ce que choisir du HTML statique pour un site professionnel envoie un mauvais signal ?

    Non — au contraire, pour un consultant technique senior. L’expertise technique ne se démontre pas par la complexité de la stack utilisée, mais par la justesse du choix de stack pour chaque contexte. Un développeur qui construit un site vitrine en React parce que c’est sa technologie principale démontre de la cohérence dans ses choix habituels. Un consultant qui choisit délibérément du HTML statique pour un site vitrine — et peut argumenter précisément pourquoi (performance structurelle, surface d’attaque nulle, coût de maintenance nul, durée de vie indéfinie) — démontre du discernement. La capacité à choisir la solution proportionnée à chaque problème, y compris une solution simple, est précisément ce qu’un client attend d’un consultant senior.

    À partir de quand doit-on passer d’un site HTML statique à un framework JavaScript ?

    Le seuil de bascule est fonctionnel, pas esthétique. Si le site nécessite du contenu personnalisé par utilisateur (authentification, tableaux de bord, panier), un framework serveur (Next.js, Remix, SvelteKit) devient nécessaire. Si le volume de pages croît au point où la duplication de code HTML devient ingérable, un générateur statique (Astro, Hugo, Eleventy) est la première étape — pas un framework JavaScript, car on conserve les bénéfices du statique. Si des interfaces riches sont nécessaires (formulaires multi-étapes, interactions complexes, état global), des îlots JavaScript (Islands Architecture avec Astro) ou un framework léger peuvent être ajoutés de façon ciblée. La règle générale : ne passer à un niveau de complexité supérieur que quand les limites du niveau actuel sont prouvées — pas anticipées.

    Conclusion

    Le site de PYKEngine est du HTML statique parce que c’est la réponse correcte au problème posé. Pas parce que les frameworks sont mauvais — ils résolvent des problèmes réels et complexes. Mais parce que la compétence clé d’un ingénieur senior est de distinguer les problèmes qui nécessitent des solutions complexes de ceux qui n’en ont pas besoin. Performance maximale par défaut. Surface d’attaque nulle. Coût de maintenance zéro. Durée de vie indéfinie.

    Le choix le plus simple est souvent le choix le plus difficile à assumer — et le plus révélateur de la maturité technique de celui qui le fait.