Pug Mixin Swallowing Nested Content: Diagnosing Missing `block` Keyword
Pug mixins render but nested content disappears when the mixin lacks a bare <code>block</code>. Diagnose with a table, ordered checks, and fixes for common whitespace and block placement bugs.
28 Aug 2026, 05:15 UTC

The symptom: mixin renders, nested content disappears
You call a mixin and indent markup underneath it. The mixin’s own HTML appears in the output, but everything you nested under the call is gone. No error, no warning – the content is silently discarded. Tests that only check for the mixin’s own markup will pass, so this can survive code review.
The fix is usually one word: block. In Pug 3.x, content indented under a mixin call is only emitted where the mixin declares a bare block statement. Without it, the passed block is thrown away.
Cause and diagnostic table
| Observation | Likely cause | Where to look |
|---|---|---|
| Nested content missing entirely | Mixin body has no block keyword | Mixin definition |
| Content missing only in some states | block sits inside a conditional or loop that doesn’t execute | Mixin definition branches |
| Content appears twice (or in the wrong spot) | block declared more than once — content renders at each occurrence | Mixin definition |
Named block like block content inside mixin doesn’t receive caller content | Confusion with template‑inheritance blocks; only the anonymous block receives mixin call content | Mixin definition vs. layout |
| Content missing and mixin also misbehaves | Caller‑side indentation: content not nested under the +mixin line, or tabs/spaces mixed | Call site |
Ordered checks
- Check the mixin definition for a bare
block. It must be the anonymous keyword on its own line, notblock something. - Check the call site indentation. The content must be indented one level deeper than the
+mixinNameline. Pug is whitespace‑significant; mixed tabs and spaces can look correct in an editor while compiling to something else. Configure your editor to show whitespace characters. - Check where
blocksits. If it’s inside aniforeach, confirm that branch actually runs for your data. Remember that multipleblockstatements each emit the content. - Rule out name collisions. If the same file or an extended layout uses named blocks (
block content,block head), those belong to template inheritance and have nothing to do with mixin content. - Inspect the compiled output. Render a minimal case and search the HTML string for your marker text, or compile with debug info to see where the block lands.
A minimal reproduction
Run this anywhere Node runs (no special permissions needed). It demonstrates the failure and the fix:
const pug = require('pug');
// Broken: no `block` in the mixin
const broken = `
mixin card(title)
.card
h2= title
+card('Hello')
p This paragraph vanishes.
`;
// Fixed: `block` marks where caller content goes
const fixed = `
mixin card(title)
.card
h2= title
.card-body
block
+card('Hello')
p This paragraph renders.
`;
console.log(pug.render(broken));
console.log(pug.render(fixed));
In the first render, the <p> is absent from the HTML string. In the second, it appears inside .card-body, exactly where block was placed. That position control is the point of the feature: the mixin author decides where caller content lands.
Fixes tied to each finding
- No
blockin the mixin: add it where the caller’s content should appear. If the mixin should never accept content, that’s fine – but then callers nesting content under it are writing dead code, so remove it at the call site. blockin a dead branch: move it outside the conditional, or restructure so every path that should show content reaches ablock.- Duplicate output: keep exactly one
blockunless you genuinely want the content repeated. - Named‑block confusion: replace
block contentwith a bareblockinside the mixin. Reserve named blocks forextends/layoutinheritance. - Caller indentation: re‑indent the nested content one level under the
+mixincall and standardize on spaces or tabs file‑wide.
Verifying the fix
Render the template with pug.render or pug.compile and assert the nested content appears in the output string at the expected position – not just that the mixin’s own markup is present. If you want to trace compilation, pass { compileDebug: true } (the default) so errors map to template lines, or inspect the function returned by pug.compile to see where the block is emitted. Confirm your installed version with npm ls pug; this behavior reflects stable Pug 3.x semantics, and Jade‑era code may differ in edge cases.
When to escalate
If a two‑file minimal case – one mixin with a bare block, one call with indented content – still drops the content on a supported Pug 3.x release, you may have a genuine engine bug. Reduce it to the smallest template that reproduces the issue, note your exact Pug and Node versions, and report it on the Pug issue tracker. Until then, the overwhelmingly likely explanation is a missing or misplaced block, or whitespace that isn’t what it looks like.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.