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.
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 :
- Des temps de réponse de l'ordre de la dizaine de millisecondes, sans warm-up Node.
- Une résilience totale en cas d'arrêt du processus Node (le site reste en ligne).
- Une compatibilité parfaite avec les caches HTTP, CDN et reverse-proxies.
- Une attaque-surface drastiquement réduite : seul
/adminet/apiexposent du code applicatif.
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 :
- Préparation : lecture de la configuration globale (site, layout actif, header, footer, branding) et résolution des chemins
paths.public,paths.templates,paths.uploads. - Itération des pages CMS :
SELECTde toutes les pages publiées (statutpublished) puis pour chacune appel àrender-servicepour produire le HTML final. - Itération des articles blog : récupération de tous les articles dont le statut est
published, idem rendu viarender-service. - Itération des pages cocon : parcours des
generated_pagesissues des content groups (parents + enfants), rendues elles aussi via le moteur unifié. - Index blog paginé : génération de
/public/blog.htmlet des pages de pagination/public/blog/page/2.html, etc. - Archives catégories & tags :
/public/blog/categorie/<slug>.htmlet/public/blog/tag/<slug>.html. - Page 404 : génération de
/public/404.htmlpersonnalisable depuis les réglages. - sitemap.xml : agrégation de toutes les URL publiques + dates de mise à jour.
- robots.txt : fichier minimal autorisant les crawlers et pointant vers le sitemap.
- Log final : insertion d'une entrée
type=content_action action=site_builddans la tablelogs.
É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 contenu | Slug source | Fichier de sortie |
|---|---|---|
| Page CMS classique | contact | /public/contact.html |
| Page d'accueil | / ou home | /public/index.html |
| Article blog | mon-article | /public/blog/mon-article.html |
| Cocon parent | plombier | /public/plombier.html |
| Cocon enfant | plombier/paris | /public/plombier/paris.html |
| Index blog | — | /public/blog.html + /public/blog/page/N.html |
| Catégorie blog | actualites | /public/blog/categorie/actualites.html |
| Tag blog | seo | /public/blog/tag/seo.html |
| Page 404 | — | /public/404.html |
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 :
- Maximum 5 pages par fenêtre de 7 jours par content group (réglable par groupe).
- Ordre strict parent → enfant : tant que les pages parents d'un groupe ne sont pas générées et buildées, les enfants restent bloqués.
- Notifications email envoyées 5 minutes après chaque créneau libéré aux rôles
adminetwebmaster.
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.
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éthode | Route | Description |
|---|---|---|
| GET | /api/build/status | Lire le statut courant du build (timestamp dernier build, nombre de pages, durée). |
| GET | /api/build/sitemap | Lire la liste des URLs présentes dans le sitemap. |
| POST | /api/build | Lancer un build complet du site. |
| POST | /api/build/sitemap | Ré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 :
requireAuth: JWT ou token API valide.requireRole('owner', 'admin', 'webmaster'): les éditeurs (editor) n'ont pas accès au build.requireCapability('build'): la capabilitybuilddoit être active pour le rôle de l'appelant dans la matrice RBAC.
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.
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 :
- Promeut l'article de
scheduledverspublished. - Met à jour le champ
published_atavec l'horodatage courant. - 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éclencheur | Build complet | Sitemap seul |
|---|---|---|
| Bouton « Builder le site » dans l'admin | Oui | Non |
| POST /api/build via token API | Oui | Non |
| blog-scheduler (promotion automatique) | Oui | Non |
| Génération d'un cocon complet | Oui (en fin de batch) | Non |
| POST /api/build/sitemap | Non | Oui |
Hook extension build.requested | Selon 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.
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 :
- Toutes les pages CMS publiées (avec leur
updated_atcomme<lastmod>). - Tous les articles blog publiés.
- Toutes les pages cocon (parents + enfants).
- L'index du blog et les pages catégorie/tag.
- La page d'accueil et les pages racines (mentions légales, contact, etc.).
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).
| Chemin | Servi par | Cible |
|---|---|---|
/ | Plesk (statique) | public/index.html |
/contact.html | Plesk (statique) | public/contact.html |
/blog/mon-article.html | Plesk (statique) | public/blog/mon-article.html |
/sitemap.xml | Plesk (statique) | public/sitemap.xml |
/admin | Node (Express) | Interface React |
/api/* | Node (Express) | API REST |
/uploads/* | Plesk ou Node | Médias (servis depuis uploads/) |
/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
- Builds séquentiels : le moteur ne parallélise pas l'écriture des fichiers (volontairement, pour rester compatible avec les filesystems Plesk parfois lents). Sur un site de 100 pages, un build prend typiquement entre 1 et 3 secondes.
- Pas de cache invalidé : chaque build écrit l'intégralité des fichiers HTML, même ceux inchangés. C'est le prix de la simplicité ; pour des sites de plusieurs milliers de pages, un mode incrémental pourrait être ajouté ultérieurement.
- Atomicité : chaque fichier est écrit via un temp file + rename, ce qui évite qu'un visiteur reçoive un HTML partiel pendant le build.
- Pas d'arrêt service : aucune indisponibilité pendant le build, le site reste pleinement accessible.
Intégration avec les extensions
Le système d'extensions expose plusieurs hooks autour du cycle de build :
build.before: appelé juste avant le démarrage du build, utile pour préparer des assets externes.build.after: appelé en fin de build, reçoit les stats (nombre de pages, durée). Idéal pour notifier un service externe ou pousser le site vers un CDN.render.html(filter) : permet à une extension de transformer le HTML final de chaque page avant écriture sur disque.
Voir Système d'extensions pour la liste complète des hooks et le modèle de permissions associé.
Résumé
buildSite()dansbackend/src/services/build-service.jsest l'unique entrée du moteur de build.- Il itère pages CMS + articles blog + pages cocon et délègue le rendu HTML au
render-service. - Il génère aussi
blog.htmlpaginé, les archives catégorie/tag, la page 404, lesitemap.xmlet lerobots.txt. - Toggle global
generation_quota_enabledpour activer/désactiver les règles anti-spam cocon. - Endpoint
POST /api/buildprotégé parrequireAuth+ rôleowner|admin|webmaster+ capabilitybuild. - Déclenchement manuel ou automatique via le
blog-scheduleraprès promotion d'articles planifiés. regenerateSitemap()pour un build léger limité au sitemap.- Logs
type=content_action action=site_buildpour la traçabilité. - Le site public est servi depuis
paths.publicà la racine du domaine ; l'admin sous un slug configurable.