@nera-static/plugin-stacks
v2.3.1
Published
A plugin for Nera static site generator to create reusable content stacks with template-based rendering. Perfect for content components, widgets, and modular design systems.
Maintainers
Readme
@nera-static/plugin-stacks
A plugin for the Nera static site generator that allows you to define reusable content blocks ("stacks") in Markdown. These can be embedded in any page or layout and optionally rendered with a custom template.
✨ Features
- Define reusable Markdown blocks in the
pages/directory - Access all stacks via
app.stacksin any view - Optional Pug templates for customized stack rendering
- Frontmatter meta fields fully available in templates
- Publishable default template for quick integration
- BEM CSS methodology for styling consistency
- Lightweight and zero-runtime overhead
- Full compatibility with Nera v4.1.0+
🚀 Installation
Install the plugin in your Nera project:
npm install @nera-static/plugin-stacksNo further setup required — Nera will auto-detect the plugin.
⚙️ Configuration
No configuration file needed. Stacks are defined directly via frontmatter in Markdown files.
🧩 Usage
What makes a page a stack
A stack is any Markdown file in pages/ whose frontmatter sets type: stack
and provides either a slug or a title. The marker is an exact match — a page
without it is invisible to this plugin, which is the usual reason app.stacks
comes back empty.
Two consequences worth knowing, because neither is obvious:
- Give stack pages no
layout. Nera renders a page topublic/only if its frontmatter has one, so omittinglayoutkeeps the stack a reusable fragment rather than also publishing it as its own page. Add alayoutonly if you genuinely want both. - Stack pages are still ordinary pages. They stay in the page list every
other plugin sees, so they can be counted by
plugin-statistics, indexed byplugin-search, or listed by a navigation plugin. Exclude them there if you do not want fragments leaking into menus or search results.
Frontmatter example
---
title: Reusable Stack
slug: basic_stack
type: stack
---
Some content that will be reused in various templates.Stack with custom layout
---
title: Stack with layout
description: A reusable section
slug: stack_with_template
type: stack
stack_layout: views/stacks/stack-layout.pug
---
### Hello Stack
This will be rendered with a layout.Stack keys
Each stack is exposed as app.stacks[<slug>]. The slug comes from frontmatter
when present; otherwise it is derived from the title by lowercasing it and
replacing spaces with underscores — title: My Great Stack becomes
app.stacks.my_great_stack.
This is a data-contract key you reference from your own layouts, not an HTML
anchor, so the underscore convention is deliberate and will not change. Set an
explicit slug if you want something else.
Four details that are easy to trip over:
- Only spaces are replaced. Punctuation and accents survive, lowercased, so
title: Hero: Big! Blockbecomes the keyhero:_big!_block. That is a valid object key but not a valid identifier, so it can only be read asapp.stacks['hero:_big!_block']— dot access will not compile. Set an explicitslugfor any title containing punctuation. - An explicit
slugis used exactly as written — no lowercasing, no space replacement.slug: my slugreally does produce the keymy slug. - Slugs are one global namespace. Two stacks that resolve to the same slug
collapse into one: the later page in build order wins, and the plugin warns.
Set an explicit
slugto keep both. - A non-string title. Frontmatter is YAML, so
title: 2024is a number and an unquotedtitle: 2024-05-01is a date. Numbers and booleans are used as keys (2024,true). A date, list or mapping cannot produce a sensible key, so the stack is skipped with a warning — quote the value, or set an explicitslug, to include it.
If a stack_layout is missing or fails to compile, the plugin warns with the
offending path and falls back to the stack's unrendered content rather than
failing the build.
Example layout template
section.stack
header.stack__header
h2.stack__title #{ stack.meta.title }
p.stack__description #{ stack.meta.description }
article.stack__content
| !{ stack.content }🛠️ Template Publishing
Publish the default template:
npx nera-stacksThis copies the layout to:
views/vendor/plugin-stacks/stack-template.pugPublishing skips the whole directory, not individual files. If
views/vendor/plugin-stacks/already exists, the command copies nothing and still exits successfully — even if you deleted the file inside it. This also means upgrading the plugin never updates your published template. To pull in a newer version, re-run with--force:npx nera-stacks --force
--forceoverwrites every file in that directory and discards local edits, so diff your copy first if you have customised it.
Reference it in stack frontmatter:
stack_layout: views/vendor/plugin-stacks/stack-template.pug🎨 Styling
Default template uses BEM CSS classes:
.stack { }
.stack__header { }
.stack__title { }
.stack__description { }
.stack__content { }These class names are a public contract. You style them from your own CSS, so renaming one here is a breaking change and ships as a major version.
Note that .stack__description is rendered unconditionally, so a stack with no
description in its frontmatter emits an empty <p class="stack__description">.
Either always set a description, style the empty case, or edit your published
copy of the template to guard the line.
📊 Generated Output
The plugin adds app.stacks, keyed by slug. Each entry holds the stack's
rendered content and its full frontmatter as meta:
app.stacks = {
basic_stack: {
// the stack_layout output when one resolved, otherwise the page's own
// markdown-rendered HTML
content: '<section class="stack">…</section>',
// every frontmatter key survives, including your own custom ones
meta: { type: 'stack', title: 'Reusable Stack', slug: 'basic_stack', … }
}
}Rendering a stack through the shipped template produces:
<section class="stack">
<header class="stack__header">
<h2 class="stack__title">T</h2>
<p class="stack__description">D</p>
</header>
<article class="stack__content">
<p>hi</p>
</article>
</section>Without a stack_layout, content is simply the page's rendered Markdown and
carries no .stack markup at all.
🧪 Development
npm install
npx vitest run
npm run lintnpm test starts Vitest in watch mode and does not exit; use npx vitest run
for a single pass.
Tests use Vitest and cover:
- Stack discovery and rendering
- Layout integration
- Template publishing and overwrite handling
🤝 Contributing
Issues and pull requests are welcome. See the Nera contributing guide for plugin development, the hook contract, and local setup.
For this repo specifically:
npx vitest runandnpm run lintmust pass (npm testis watch mode).- Bump the version and update
CHANGELOG.mdin the same commit as the change. - Template markup and BEM class names are a public contract — users style them from their own CSS, so changing one is a major bump.
- The
app.stackskey convention (lowercase, spaces to underscores) is equally a public contract: users referenceapp.stacks[slug]from their layouts. - Releases publish from CI on a pushed
v*tag. Never runnpm publish.
🧑💻 Author
Michael Becker
https://github.com/seebaermichi
🔗 Links
🧩 Compatibility
- Nera: v4.1.0+ — a baseline rather than a requirement; the plugin reads only page frontmatter and uses no generator feature above the 4.x line.
- Node.js: >= 20.0.0
- Plugin Utils:
^1.2.0— used by thenpx nera-stackspublish command (where--forcelanded), not by the plugin at build time. - Plugin API: Uses
getAppData()to expose stack data
📦 License
MIT
