Using Pug Template Inheritance to Keep HTML Layouts DRY in Node.js
Learn how Pug’s extends and block keywords let you reuse a base layout, avoid duplicated markup, and safely evolve HTML templates in Node.js apps.
23 Jun 2026, 12:57 UTC

The problem: duplicated markup
When building a Node.js server that renders HTML with Pug, each route often repeats the same doctype, html, head and footer markup. Changing a shared element—like adding a new meta tag or updating the site footer—means editing every template file, which is error‑prone and makes refactors tedious.
Why inheritance helps
Pug’s extends and block keywords let you define a base layout once and let child templates fill in only the parts that differ. If a child does not override a block, the base layout’s default content is used, giving you a fallback without duplication. Because the resolution happens at compile time, the generated HTML is identical to hand‑written markup and adds no runtime overhead.
Worked example: base layout and page
layout.pug
doctype html
html
head
title= pageTitle || 'Default Title'
block head
link(rel='stylesheet', href='/styles.css')
body
header
h1 My Site
block content
footer
small © 2026
index.pug
extends layout.pug
block head
meta(name='description' content='Home page')
style.
body { background:#fafafa; }
block content
section
h2 Welcome
p This is the home page.
When you run the Pug CLI:
pug index.pug -o ./dist
the file ./dist/index.html will contain the base HTML structure surrounding the child’s title, meta tag, inline style and main section. If you remove the block head or block content sections from index.pug, the layout’s defaults (the title fallback and empty content) are rendered instead.
Trade‑offs and gotchas
- Whitespace sensitivity. Pug relies on indentation to detect block boundaries. A mis‑aligned space can cause a block to be ignored silently, producing missing sections in the output.
- Version differences. In Pug < 2.0 the
extendspath was resolved relative to the caller’s directory differently. Projects locked to an older release may need to adjust paths or upgrade. - Compile‑time only. Because inheritance is resolved when the template is compiled, you cannot change which block is filled based on runtime data without using mixins or conditionals inside the child template.
These limitations are mitigated by enabling a linter rule that checks indentation (e.g., pug-lint) and by pinning a Pug version in your package.json.
Putting it into practice
- Create a
layouts/folder and addbase.pugwith the shared HTML and named blocks. - In each page template,
extends ../layouts/base.pugand override only the blocks you need. - Add a build step:
pug **/*.pug -o ./public --prettyto generate HTML for static serving or use thepugmiddleware in Express. - Run
pug-lint **/*.pugin CI to catch indentation problems early.
With this setup you keep a single source of truth for the site’s skeleton, reduce duplicated markup, and gain a clear, version‑controlled way to evolve layouts as your application grows.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.