Mastering Pug Layouts: Extends and Block Inheritance in Express Apps
Learn how to build reusable page layouts in Express using Pug’s extends and block syntax. Follow a step‑by‑step guide to create a parent layout, override blocks, and verify the rendered output while handling caching and path issues.
28 Sept 2026, 13:13 UTC

Desired Outcome
After following this guide you will be able to create a single parent layout that contains common HTML skeleton (doctype, head, navigation, footer) and have any number of child pages that only supply page‑specific content. The child pages will override or append to named block sections, keeping the code DRY and making layout changes propagate automatically.
Prerequisites
- Node.js ≥ 14 and npm installed.
- An Express project with
app.set('view engine', 'pug')configured. - Basic knowledge of Pug syntax (indentation, tags, attributes).
- File system access to create
viewsdirectory.
Step‑by‑Step Procedure
1. Create the Parent Layout
Place the following in views/layout.pug. It defines the overall page structure and declares named blocks that children can override.
doctype html
html
head
title
block title
| Default Site Title
meta(charset='utf-8')
link(rel='stylesheet', href='/css/main.css')
block head
body
header
h1 My Site
nav
ul
li: a(href='/') Home
li: a(href='/about') About
main
block content
footer
p © 2026 My Company
block scripts
script(src='/js/main.js')
Key points:
block title– Page title can be overridden.block head– Allows child pages to inject CSS or meta tags.block content– Primary area for page body.block scripts– Child pages can append scripts.
2. Create a Child Template
In views/index.pug use extends to inherit the layout and override blocks.
extends layout
block title
| Home – My Site
block content
h2 Welcome to the Home Page
p This is the main content area.
block append scripts
script(src='/js/home.js')
Notice the use of block append to add a page‑specific script while preserving the default script from the layout.
3. Configure Express
In your app.js (or equivalent) ensure the view engine and directory are set correctly. Use absolute paths for extends when you want to avoid relative‑path pitfalls.
const express = require('express');
const app = express();
app.set('view engine', 'pug');
app.set('views', __dirname + '/views');
// Optional: enable view caching in production
if (process.env.NODE_ENV === 'production') {
app.set('view cache', true);
}
app.get('/', (req, res) => {
res.render('index');
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
4. Run and Verify
Start the server:
node app.js
Open http://localhost:3000/ and inspect the page source. You should see:
- The
titleset toHome – My Site. - The
h2and paragraph from the childblock content. - Both
/js/main.js(from the layout) and/js/home.js(appended by the child).
Expected Checks
1. Rendered HTML Structure
Verify that the final HTML contains the parent layout’s markup and the child’s overridden blocks. Use the browser’s “View Page Source” or a tool like curl:
curl -s http://localhost:3000/ | grep -i "Home – My Site"
2. Cache Behavior in Production
When NODE_ENV=production and app.set('view cache', true), changes to layout.pug or index.pug will not be reflected until the server restarts. Confirm by editing a block and refreshing without restart; the old content should persist. Restart the server and verify the update appears.
Recovery Options
1. Fix Path Issues
If a child template cannot locate its parent, ensure the extends path is correct. In Express, Pug resolves relative paths from the child’s directory. Using extends layout (without a leading slash) works when app.set('views', …) points to the root views folder. If you need to reference a sibling layout, use relative paths like extends ../layout but double‑check that the relative path matches the file hierarchy.
2. Reload Templates in Development
In development, set app.set('view cache', false) (default) so that every request recompiles the templates. This allows live updates without restarting the server. If you notice stale content, clear the Node.js require cache manually:
Object.keys(require.cache).forEach(key => delete require.cache[key]);
3. Clear View Cache in Production
When you deploy a new version that includes layout changes, always restart the Node.js process. If you’re using a process manager (PM2, systemd), a simple pm2 restart all or equivalent will reload the templates.
Common Pitfalls and How to Avoid Them
- Indentation Errors – Pug is whitespace‑sensitive. Ensure that
blockdeclarations are indented one level deeper than theextendsline. - Typos in Block Names – A misspelled block creates a new block instead of overriding. Use a consistent naming convention.
- Circular Extends – Do not create mutual inheritance (A extends B and B extends A). It will cause a stack overflow during compilation.
- Missing Default Content – If a child does not override a block, the parent’s default content will render. This is useful for progressive enhancement but can hide missing overrides if the parent provides empty defaults.
Conclusion
By leveraging extends and named block sections, you can maintain a clean separation between shared layout and page‑specific markup. The pattern scales well for large Express applications, reduces duplication, and makes maintenance of common elements straightforward. Remember to manage view caching appropriately and double‑check block names and indentation to avoid subtle bugs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.