Using Pug Template Inheritance to Keep Layouts DRY
Learn how to use Pug’s extends and block features to reuse layouts, see a worked example, and avoid common indentation pitfalls.
01 Sept 2026, 22:38 UTC

The problem: duplicated markup in every view
When building a server-side rendered Node.js site with Pug, you often find yourself repeating the same header, footer, and boilerplate HTML in each view file. Changing the site-wide navigation requires editing dozens of templates, which is error-prone and violates the DRY (Don't Repeat Yourself) principle. Even a small change, like adding a CSS class to the body element, turns into a find-and-replace exercise across your entire views directory.
Thesis: Pug’s extends and block features let you define a single layout and override only the parts that differ per page
By extracting the common structure into a base layout and using named blocks for page-specific content, you keep the markup centralized while still allowing each view to customize what it needs without duplication. Instead of copying the full document skeleton into every file, you describe the page once and fill in the blanks where they matter.
How inheritance works in Pug
A base layout file does not use extends. Inside it, you declare sections using block directives. Blocks can contain default content; if a child does not redefine a block, the parent's content is rendered, providing a fallback UI. This is particularly useful for complex sites where a layout might extend another layout to create a nested hierarchy.
Pug compiles the inheritance hierarchy at render time. This means changes to base templates are reflected in the output without needing to manually recompile every child file, especially when using the pug-cli watch mode. Because the resolution happens dynamically, you can adjust a shared sidebar or analytics snippet in the layout and every page that inherits from it picks up the change on the next request.
Example: Implementing a Base Layout
To implement this, create a base layout named layout.pug and a child view page.pug. Note that Pug is whitespace-sensitive; mixing spaces and tabs or using inconsistent indentation will break block detection and cause compilation errors. Additionally, in Pug v3+, the syntax for self-closing tags changed, so ensure your templates follow modern standards.
layout.pug (The Base)
doctype html
html
head
title block title - My Awesome App
body
header
nav
ul
li: a(href="/") Home
li: a(href="/about") About
main
block content
p Default content goes here if the child doesn't provide any.
footer
p © 2026 My Project
page.pug (The Child)
extends layout block title About Page block content h1 Welcome to the About Page p This content is unique to the about page.
Notice that the child only declares the blocks it needs to change. It does not repeat the doctype, the navigation, or the footer. When Pug renders page.pug, it walks up to layout.pug, substitutes the overridden blocks, and emits a single HTML document.
Diagnostic: Verification
To verify the inheritance works, use the pug-cli to render the HTML. Run the following command in your terminal (ensure you have the CLI installed globally or use npx):
pug -P page.pug
Check the resulting file. The output should contain the header and footer from the layout, while the main tag contains the specific "About" content. To test the fallback, delete the block content from page.pug and re-render; the default paragraph from the layout should now appear in the HTML. If the override does not appear, confirm that the indentation in page.pug uses the same whitespace character as the layout and that the block name matches exactly. You can also run pug -P page.pug --pretty to generate indented HTML that is easier to inspect.
Trade-offs and Limitations
The primary limitation of Pug inheritance is its strict reliance on indentation. A single extra space can prevent a child from correctly identifying a block, leading to the parent content being rendered instead of the override. Furthermore, while multiple levels of inheritance are supported, deep nesting can make it difficult to trace where a specific piece of HTML originated. When a layout extends another layout, debugging a missing block often means walking up the chain manually.
Actionable Closing
Start by refactoring your most-repeated template into a single layout.pug. Use specific block names for titles, head scripts, and main content to ensure your views remain clean and flexible as your application grows. If you already have a working Express or Koa setup, render one page through the new layout first, confirm the output in the browser, and then migrate the remaining views incrementally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.