Build & Publication

Le moteur de build statique du Labo du Yeti transforme la base de données SQLite (pages, articles, groupes cocon, médias, configuration globale) en un site HTML 100% statique servi par le serveur web frontal (Plesk, Nginx ou Apache). Cette page décrit le pipeline complet, depuis l'appel buildSite() jusqu'à la génération du sitemap.xml et du robots.txt, en passant par le quota global, le déclenchement par le scheduler blog et les contraintes de déploiement sous Plesk.

En une phrase : buildSite() écrit tous les fichiers HTML dans paths.public, qui correspond au document root du domaine. Le panneau d'administration React vit, lui, sous un slug configurable (par défaut /admin).

Philosophie : statique d'abord

Le CMS est conçu autour d'une règle simple : aucun visiteur public n'interroge Node ni SQLite. Toutes les pages destinées au monde extérieur sont écrites sur disque sous forme de fichiers .html servis directement par le frontal HTTP. Cette approche garantit :

La contrepartie : tout changement de contenu doit déclencher un build, partiel ou complet. Le service est conçu pour qu'un build complet reste rapide même sur des sites de plusieurs centaines de pages.

La fonction buildSite()

L'entrée principale du moteur de build est la fonction buildSite() exposée par backend/src/services/build-service.js. Elle est invoquée à la fois par l'API REST (POST /api/build) et par le scheduler blog après promotion d'articles planifiés.

Pipeline complet

Un appel à buildSite() exécute, dans l'ordre, les étapes suivantes :

  1. Préparation : lecture de la configuration globale (site, layout actif, header, footer, branding) et résolution des chemins paths.public, paths.templates, paths.uploads.
  2. Itération des pages CMS : SELECT de toutes les pages publiées (statut published) puis pour chacune appel à render-service pour produire le HTML final.
  3. Itération des articles blog : récupération de tous les articles dont le statut est published, idem rendu via render-service.
  4. Itération des pages cocon : parcours des generated_pages issues des content groups (parents + enfants), rendues elles aussi via le moteur unifié.
  5. Index blog paginé : génération de /public/blog.html et des pages de pagination /public/blog/page/2.html, etc.
  6. Archives catégories & tags : /public/blog/categorie/<slug>.html et /public/blog/tag/<slug>.html.
  7. Page 404 : génération de /public/404.html personnalisable depuis les réglages.
  8. sitemap.xml : agrégation de toutes les URL publiques + dates de mise à jour.
  9. robots.txt : fichier minimal autorisant les crawlers et pointant vers le sitemap.
  10. Log final : insertion d'une entrée type=content_action action=site_build dans la table logs.

Écriture sur disque

Chaque page est écrite via une routine d'écriture atomique : génération dans un fichier temporaire puis rename pour éviter qu'un visiteur reçoive un fichier partiellement écrit. Le chemin de sortie suit cette logique :

Type de contenuSlug sourceFichier de sortie
Page CMS classiquecontact/public/contact.html
Page d'accueil/ ou home/public/index.html
Article blogmon-article/public/blog/mon-article.html
Cocon parentplombier/public/plombier.html
Cocon enfantplombier/paris/public/plombier/paris.html
Index blog/public/blog.html + /public/blog/page/N.html
Catégorie blogactualites/public/blog/categorie/actualites.html
Tag blogseo/public/blog/tag/seo.html
Page 404/public/404.html
Slugs composites cocon : les pages enfants utilisent un slug à deux segments (parent/enfant). Le build crée le sous-dossier correspondant et y écrit le fichier HTML. Cela permet de garder des URLs propres du type monsite.fr/plombier/paris.html sans configuration de rewriting côté serveur.

Délégation au render-service

buildSite() ne contient aucune logique de rendu HTML. Il délègue intégralement à backend/src/services/render-service.js, ce qui garantit qu'une page rendue en preview dans l'admin est rigoureusement identique à la page écrite sur disque. Voir Moteur HTML (pipeline) pour les détails du rendu : application du layout global, injection du header/footer, expansion des overrides du builder, fusion des médias, injection du runtime des formulaires, etc.

Toggle generation_quota_enabled

Le système expose un toggle global generation_quota_enabled dans la configuration. Quand il est actif (valeur par défaut), il déclenche les règles anti-spam suivantes pendant la génération de pages cocon :

Le quota peut être désactivé globalement (par exemple lors d'une migration ou d'une importation initiale) en passant generation_quota_enabled à false. Dans ce cas, le build ne fait aucune vérification de fréquence et toutes les pages disponibles sont écrites.

Bonne pratique : garder le quota activé en production pour éviter les pics de publication suspects aux yeux de Google. Le désactiver est réservé aux opérations administratives ponctuelles (réimport, audit, migration entre instances).

Endpoint POST /api/build

Le build peut être déclenché manuellement depuis l'interface admin via le panneau Configuration → Build du site, ou programmatiquement via l'API REST.

MéthodeRouteDescription
GET/api/build/statusLire le statut courant du build (timestamp dernier build, nombre de pages, durée).
GET/api/build/sitemapLire la liste des URLs présentes dans le sitemap.
POST/api/buildLancer un build complet du site.
POST/api/build/sitemapRégénérer uniquement le sitemap.xml sans rebuilder les pages.

Permissions requises

Les routes de mutation (POST) sont protégées par la combinaison suivante :

Les routes de lecture (GET) restent ouvertes à tous les utilisateurs authentifiés disposant de la capability build. Voir Authentification & RBAC pour la matrice complète.

Tokens API : un token API généré dans Réglages → Tokens API peut lui aussi déclencher un build, à condition que l'utilisateur propriétaire du token ait le rôle et la capability nécessaires. C'est le mécanisme utilisé par les pipelines d'intégration externes (GitHub Actions, cron Plesk, etc.).

Déclenchements automatiques

Outre le déclenchement manuel via l'API, le build est invoqué automatiquement par le blog-scheduler. Ce service interne surveille les articles dont le statut est scheduled et la date scheduled_at dépassée. Lorsque c'est le cas, il :

  1. Promeut l'article de scheduled vers published.
  2. Met à jour le champ published_at avec l'horodatage courant.
  3. Déclenche automatiquement buildSite() pour publier l'article et régénérer l'index blog, les pages catégorie et tag impactées, ainsi que le sitemap.

Cela garantit qu'un article planifié pour 9h00 du matin apparaît effectivement en ligne quelques secondes après cet horaire, sans intervention humaine.

Autres triggers

DéclencheurBuild completSitemap seul
Bouton « Builder le site » dans l'adminOuiNon
POST /api/build via token APIOuiNon
blog-scheduler (promotion automatique)OuiNon
Génération d'un cocon completOui (en fin de batch)Non
POST /api/build/sitemapNonOui
Hook extension build.requestedSelon implémentation

regenerateSitemap : build léger

Pour les opérations où seules les URLs publiques changent (par exemple après une suppression de page sans modification du contenu des autres), le service expose une fonction dédiée regenerateSitemap() qui ne rebuilde que le fichier sitemap.xml. Cette opération est nettement plus rapide (quelques dizaines de millisecondes contre potentiellement plusieurs secondes pour un build complet), et est exposée via POST /api/build/sitemap.

À utiliser avec discernement : regenerateSitemap() ne supprime pas les fichiers HTML orphelins sur disque. Si vous venez de supprimer une page, lancez un build complet pour nettoyer le filesystem et éviter qu'une URL morte reste accessible directement.

Format du sitemap.xml

Le sitemap suit la norme sitemaps.org (XML 0.9). Il inclut :

Les URLs sont construites à partir de global.site.url, qu'il faut donc renseigner correctement dans les Réglages → Général. Une URL manquante ou erronée produira un sitemap invalide aux yeux de Google Search Console.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://monsite.fr/</loc>
    <lastmod>2026-05-26</lastmod>
  </url>
  <url>
    <loc>https://monsite.fr/contact.html</loc>
    <lastmod>2026-05-20</lastmod>
  </url>
  ...
</urlset>

Format du robots.txt

Le robots.txt généré est minimal et conçu pour autoriser tous les crawlers tout en leur indiquant l'emplacement du sitemap :

User-agent: *
Allow: /
Disallow: /admin
Disallow: /api

Sitemap: https://monsite.fr/sitemap.xml

Les chemins /admin et /api sont explicitement exclus pour éviter que des moteurs de recherche n'indexent par erreur l'interface admin ou l'API JSON.

Page 404 personnalisée

Le fichier /public/404.html est généré à partir du template configuré dans Réglages → Page 404. Le contenu peut être édité depuis l'admin (titre, message, lien de retour). Le frontal HTTP (Plesk/Nginx/Apache) doit être configuré pour servir ce fichier en cas de status code 404.

Servir le site : paths.public

Tout le résultat du build vit dans paths.public, qui correspond physiquement au dossier public/ à la racine du projet. Ce dossier est exclu du dépôt Git via .gitignore (public/*) car son contenu est entièrement régénéré à chaque build et propre à chaque instance client.

Configuration Plesk recommandée

Sur Plesk, on configure le Document Root du domaine sur public/ et l'application Node attachée au sous-chemin /admin (ou tout autre slug configuré dans Réglages → Général → Accès admin).

CheminServi parCible
/Plesk (statique)public/index.html
/contact.htmlPlesk (statique)public/contact.html
/blog/mon-article.htmlPlesk (statique)public/blog/mon-article.html
/sitemap.xmlPlesk (statique)public/sitemap.xml
/adminNode (Express)Interface React
/api/*Node (Express)API REST
/uploads/*Plesk ou NodeMédias (servis depuis uploads/)
Slug admin configurable : le chemin /admin n'est qu'une valeur par défaut. Pour réduire la surface d'attaque, on peut le renommer (ex : /console, /manage-2f3a) depuis Réglages → Général → Accès admin. Le slug choisi est utilisé à la fois par le serveur Node et le routeur React.

Logs & audit

Chaque build laisse une trace dans la table logs avec la signature suivante :

{
  "type": "content_action",
  "action": "site_build",
  "user_id": "u-xxxx",
  "message": "Build complet du site",
  "meta": {
    "pages_count": 42,
    "articles_count": 18,
    "cocon_pages_count": 73,
    "duration_ms": 1240,
    "sitemap_urls": 134
  }
}

Les logs sont consultables dans Outils → Logs, filtrables par type et action. Ils permettent d'auditer qui a lancé un build et de mesurer son impact (durée, nombre de pages). Voir Base de données : table logs pour le schéma complet.

Performance & bonnes pratiques

Intégration avec les extensions

Le système d'extensions expose plusieurs hooks autour du cycle de build :

Voir Système d'extensions pour la liste complète des hooks et le modèle de permissions associé.

Résumé