Pug
Pug (formerly Jade) takes the opposite approach to EJS: instead of HTML with embedded code, you write a terse, indentation-based syntax with no angle brackets or closing tags. The structure of the document comes from whitespace, like Python. Fans love how little they have to type; the cost is a real syntax to learn and the discipline indentation demands. This page covers the syntax, interpolation, control flow, and template inheritance.
Setup
npm install pug
app.set('view engine', 'pug')
app.set('views', './views')
app.get('/', (req, res) => {
res.render('index', { title: 'Home', user: req.user }) // views/index.pug
})HTML without the brackets
The same markup, two ways
// HTML: <div class="card"> <h1 id="title">Hello</h1> <p>Welcome back</p> </div> // Pug — tag name, then nesting by indentation: .card // div is implied; .card = class="card" h1#title Hello // #title = id="title" p Welcome back
Attributes, text, and interpolation
a(href="/users/" + user.id, class="link") View profile
h1= user.name //- "=" outputs an ESCAPED expression
p Hello #{user.name}! //- #{ } interpolates (escaped) inside text
p!= trustedHtml //- "!=" and !{ } output RAW (unescaped) HTML
input(type="checkbox", checked=user.active) //- boolean attributeConditionals and loops
if user.isAdmin
a(href="/admin") Admin panel
else
p Standard account
ul
each order in user.orders
li= order.title + " — $" + order.total.toFixed(2)
else
li No orders yet //- 'each ... else' runs when the array is empty
//- A plain JS line (unbuffered code) starts with a hyphen:
- const count = user.orders.length
p You have #{count} ordersTemplate inheritance — layouts done right
views/layout.pug
doctype html
html
head
title= title
block head //- child pages can add to this block
body
include partials/nav //- shared navigation
block content //- child pages REPLACE this block
include partials/footerviews/index.pug — extends the layout
extends layout block content h1 Welcome p This fills the 'content' block of the layout. block append head link(rel="stylesheet", href="/home.css") //- adds to the head block
Mixins — reusable components
//- Define a parameterized fragment:
mixin card(title, body)
.card
h3= title
p= body
//- Use it like a function:
+card("Hello", "World")
+card(post.title, post.excerpt)The trade-off
Pro | Con |
|---|---|
Very concise — far less typing | A whole new syntax to learn |
Real inheritance + mixins | Whitespace-sensitive; misindentation breaks layout |
Forces clean structure | Pasting existing HTML requires conversion |
Great for hand-authored pages | Designers/tools expect plain HTML (EJS wins there) |