Pug JS est un moteur de templates pour Node.js et le navigateur qui transforme une syntaxe très compacte en HTML lisible, sans vous obliger à répéter les balises. Dans un backend, il devient vraiment intéressant quand il faut générer des vues server-side, des emails transactionnels ou des interfaces d’administration à partir de données dynamiques. Je vais donc montrer ce qu’il apporte, comment l’intégrer avec Express, où il fait gagner du temps et dans quels cas je le laisse de côté.
L’essentiel à retenir avant de l’adopter
- Pug compresse le HTML grâce à une syntaxe basée sur l’indentation et compile le tout en fonction JavaScript.
- Il s’intègre proprement à Express pour rendre des vues côté serveur, mais il ne sert pas à grand-chose dans une API purement JSON.
- Ses vrais atouts sont les layouts, les fragments réutilisables, les mixins et le rendu piloté par des données.
- Par défaut, l’échappement protège les sorties, mais le rendu non échappé doit rester réservé au contenu nettoyé.
- En production, je privilégie le cache et j’évite les options qui compliquent le rendu ou les espaces.
Ce que Pug apporte à un backend Node.js
Pug est avant tout un moteur de templates. J’écris une structure concise, il la transforme en HTML final au moment du rendu. Le gain n’est pas seulement esthétique : sur un projet backend, cela permet de séparer les données, la présentation et une partie de la logique de vue sans retomber dans du HTML verbeux.
Ce que j’apprécie le plus, c’est sa logique de compilation en fonction réutilisable. Une fois le template compilé, je peux le rendre plusieurs fois avec des données différentes. C’est pratique pour des pages serveur, des emails, des gabarits d’admin ou des générateurs de contenu qui doivent produire du HTML à la chaîne.
En revanche, je ne le confonds jamais avec un moteur d’API au sens strict. Si votre service ne renvoie que du JSON, Pug n’apporte rien. Il devient utile dès que le backend doit aussi fabriquer du HTML, soit pour un site rendu côté serveur, soit pour des sorties intermédiaires comme des emails ou des notifications riches.
- Pages SSR ou pages d’administration rendues côté serveur.
- Emails transactionnels avec structure stable et variables dynamiques.
- Fragments HTML réutilisables entre plusieurs routes.
- Génération de contenu à partir de données venant d’une base ou d’une API interne.
Cette distinction est importante : Pug ne remplace pas votre architecture d’API, il complète la couche qui produit du HTML. C’est précisément ce rôle qui rend la suite plus concrète.

L’utiliser en pratique avec Node.js, Express et le navigateur
Le démarrage est simple. Dans un projet Node classique, l’installation se résume à ajouter Pug comme dépendance, puis à l’utiliser soit directement, soit via Express. Avec Express, je le branche comme moteur de vues et je laisse le framework appeler le rendu au bon moment.
const express = require('express');
const app = express();
app.set('view engine', 'pug');
app.set('views', './views');
app.get('/produit/:id', (req, res) => {
res.render('produit', {
title: 'Fiche produit',
product: {
name: 'Clavier mécanique',
price: 129
}
});
});Dans un script Node plus direct, je peux rendre une chaîne de template sans passer par Express. C’est utile pour des besoins ponctuels, un pré-rendu ou un rendu d’email où je veux garder le contrôle du résultat HTML.
const pug = require('pug');
const html = pug.render('p Bonjour #{name}', {
name: 'Camille'
});Côté navigateur, la logique reste la même : on compile un template en fonction JavaScript, puis on l’exécute avec des données locales. Je m’en sers rarement pour du front lourd, mais c’est très pratique pour des interfaces simples, des prototypes ou des pages qui génèrent du HTML sans framework plus costaud.
const template = pug.compile('ul each item in items li= item');
document.querySelector('#app').innerHTML = template({
items: ['A', 'B', 'C']
});| Méthode | À quoi elle sert |
|---|---|
render |
Rendre une chaîne de template à la volée |
renderFile |
Rendre un fichier de vue côté serveur |
compileFile |
Compiler un fichier une fois pour le réutiliser plusieurs fois |
compileClient |
Produire une fonction JavaScript utilisable côté navigateur |
En pratique, je garde trois idées en tête : render pour une chaîne, renderFile pour un fichier, et compileClient quand je veux déplacer la fonction compilée vers le navigateur. Cette séparation évite de mélanger les usages et rend le code plus prévisible.
Les briques de syntaxe qui font gagner du temps
Pug devient vraiment intéressant quand on commence à composer des templates au lieu d’écrire des fichiers isolés. Les morceaux qui me servent le plus sont les includes, l’héritage de layout, les mixins et les structures conditionnelles. Ce sont ces outils qui évitent la duplication et gardent un projet lisible quand les vues se multiplient.
| Mécanisme | Rôle | Effet concret en backend |
|---|---|---|
include |
Insère un fragment de fichier | Mutualise un header, un footer ou une zone de formulaire |
extends + block
|
Construit une base commune | Garde une structure de page stable sur plusieurs routes |
mixin |
Définit un composant réutilisable | Crée des cartes, boutons ou modules de formulaire cohérents |
each / if / case
|
Gère les variations de contenu | Affiche une liste, un état vide ou un message conditionnel |
| Interpolation | Injecte les valeurs dynamiques | Personnalise le rendu sans écrire de JavaScript lourd dans la vue |
Pour moi, le vrai confort vient des templates qui se lisent de haut en bas. Une carte produit, par exemple, peut devenir un mixin réutilisable, puis être appelée partout avec quelques paramètres. C’est beaucoup plus sain que de copier-coller vingt fois le même bloc de balises.
mixin card(title, price)
article.card
h3= title
p Prix: #{price} €
+card('Clavier mécanique', 129)
+card('Souris sans fil', 59)Ce type de construction est discret, mais il change la maintenance au quotidien. Quand le design évolue, je touche une seule définition au lieu de parcourir tout le projet. C’est là que Pug devient plus qu’une syntaxe compacte.
Quand il a du sens dans une API, et quand je m’en passe
Dans une architecture backend, je garde Pug pour les cas où l’API doit produire du HTML, pas uniquement des objets JSON. C’est fréquent pour un back-office, un tableau de bord, une documentation interne, une vue d’erreur soignée ou des emails transactionnels. Dans ces contextes, le moteur de templates fait le lien entre les données métier et la présentation sans introduire un framework frontend complet.
Je m’en passe en revanche quand le service est strictement une API REST ou GraphQL consommée par une application mobile, un front séparé ou un autre backend. Ajouter Pug dans ce cas revient souvent à complexifier le projet pour un bénéfice nul. Si le backend ne rend rien, il n’a pas besoin d’un moteur de vues.
Le bon critère n’est pas “ai-je une API ?”, mais “est-ce que ce backend doit aussi fabriquer des pages ou des documents HTML ?”. Dès que la réponse est oui, Pug redevient pertinent. Sinon, je préfère garder la couche présentation ailleurs.
- Oui pour une interface d’administration ou des pages serveur.
- Oui pour des emails transactionnels ou des notifications HTML.
- Oui pour des prototypes rapides où le rendu doit rester lisible.
- Non pour un service purement JSON, surtout si une application frontend consomme déjà l’API.
Ce tri évite d’installer un outil par réflexe. La section suivante montre justement les pièges les plus courants quand on le met en place trop vite.
Les pièges qui coûtent du temps en production
Le premier piège, c’est de croire que la concision dispense de discipline. Pug rend les templates plus courts, mais il reste sensible à l’indentation. Une ligne mal alignée ne casse pas seulement la lisibilité : elle peut changer complètement la structure générée.
Le deuxième piège concerne l’échappement. Par défaut, Pug protège les attributs et les interpolations, ce qui est une bonne base de sécurité. Dès que j’utilise du rendu non échappé avec != ou des attributs non échappés, je le réserve à du contenu déjà nettoyé. Sinon, on ouvre la porte à des problèmes de type XSS.
p= user.bio
p!= trustedHtmlJe préfère considérer le second cas comme une exception, pas comme une facilité normale. Si le contenu vient d’un CMS, d’un éditeur ou d’une saisie utilisateur, je le nettoie en amont. Le template doit rester le dernier maillon, pas la première ligne de défense.
Autre point que je surveille : la logique métier qui s’invite dans les vues. Un peu de conditionnel, c’est normal. Des calculs complexes, des traitements métier ou des dépendances à la base de données, en revanche, je les laisse dans le service ou le contrôleur. Une vue doit orchestrer l’affichage, pas devenir un mini-backend.
Je fais aussi attention à l’option pretty. Elle est dépréciée et peut introduire des effets de bord liés aux espaces. Pour le rendu en production, je préfère un HTML propre, compact et prévisible, sans compter sur un formatage artificiel qui complique parfois le debug.
Une dernière habitude utile : tester au moins les templates critiques, surtout ceux qui manipulent des branches conditionnelles ou des états vides. Les bugs de vue ne sont pas toujours spectaculaires, mais ils se remarquent vite en production parce qu’ils touchent directement l’utilisateur final.
Comparer Pug à EJS et Handlebars sans se tromper
Je compare rarement les moteurs de templates sur le seul confort de lecture. Le bon choix dépend surtout du niveau de contrainte que l’équipe accepte, de la quantité de HTML à générer et de la manière dont on veut séparer la logique de la présentation.
| Moteur | Point fort | Point faible | Je le choisis quand |
|---|---|---|---|
| Pug | Syntaxe très compacte, layouts et mixins efficaces | Indentation sensible, apprentissage plus abrupt | Je veux réduire le bruit HTML et garder des vues très réutilisables |
| EJS | Proche du HTML, facile à comprendre pour une équipe habituée aux balises | Templates souvent plus verbeux | Je veux injecter du JavaScript dans du HTML sans changer les habitudes de lecture |
| Handlebars | Logique plus cadrée, séparation plus stricte | Moins souple pour certains cas avancés | Je préfère limiter la logique dans les vues et standardiser le rendu |
Si je dois trancher vite, je prends Pug quand je veux des vues compactes et une vraie réutilisation des blocs. Je prends EJS quand l’équipe pense déjà en HTML classique et que je ne veux pas lui imposer une syntaxe très différente. Je prends Handlebars quand la discipline de la couche présentation compte plus que la liberté d’expression.
Autrement dit, le meilleur moteur n’est pas celui qui “fait le plus”, mais celui qui colle au niveau de maturité du projet. C’est cette logique qui prépare la mise en production.
Les réglages que je garde avant de lancer un projet
Avant de mettre un projet Pug en production, je vérifie toujours les mêmes points. Ils ne sont pas spectaculaires, mais ils évitent les problèmes de maintenance, de sécurité et de performance qui apparaissent plus tard, quand les vues ont déjà grossi.
- Je définis
filenameet, si besoin,basedirpour que les includes et les extends restent fiables. - J’active le cache en production et je laisse les réglages de debug au plus bas possible.
- Je garde la logique métier hors des templates et je réserve les vues à la présentation.
- Je n’utilise le rendu non échappé que pour des fragments HTML déjà nettoyés.
- Je teste les templates qui gèrent des branches conditionnelles, des listes vides ou des erreurs.
- Je standardise les mixins pour éviter les variantes visuelles dispersées dans le projet.
- Avec Express, je vérifie aussi les réglages de production, notamment le cache activé et le debug désactivé.
La bonne approche, à mes yeux, consiste à traiter Pug comme une couche de rendu pragmatique, pas comme un terrain d’expérimentation. Bien utilisé, il accélère la production d’écrans serveur et de sorties HTML dynamiques ; mal cadré, il ajoute juste une syntaxe de plus à supporter.