@nera-static/plugin-page-navigation
v2.4.1
Published
A plugin for Nera static site generator to create sibling page navigation within directories. Perfect for section-based navigation and multi-page document structures.
Maintainers
Readme
@nera-static/plugin-page-navigation
A plugin for the Nera static site generator that creates navigation between sibling pages or allows custom definitions via frontmatter. Lightweight, flexible, and easy to integrate in any layout.
✨ Features
- Automatically adds navigation based on sibling pages
- Supports custom navigation overrides via frontmatter
- Optional sorting using the
positionfrontmatter field - Configurable class for highlighting the current page
- Includes a ready-to-use Pug template with multiple layout options
- Works with Nera v4.1.0+
🚀 Installation
Install the plugin in your Nera project:
npm install @nera-static/plugin-page-navigationNera will automatically detect the plugin and apply the navigation metadata during the build process.
⚙️ Configuration
Create an optional configuration file to override the active class name:
# config/page-navigation.yaml
active_page_nav_class: 'page-nav__link--active'This class is applied to the current page's link in the generated navigation,
alongside page-nav__link.
The value shown is the default, used when no config file is present — so the file is genuinely optional. Set it only to use a different class name.
🧩 Usage
Automatic sibling navigation
By default, pages in the same directory are grouped as siblings. Each link's
text is the sibling page's title — a page without one produces an empty link,
so title is effectively required.
---
title: Page A
position: 1
---position is optional and controls the sort order:
- pages that declare a
positioncome first, ordered by it; - pages without one follow, ordered by their
href; position: 0is a real value, not "absent".
Two cases produce no navigation at all, silently, and both are deliberate:
- Pages at the site root. A page directly in
pages/has no meaningful set of siblings — it would link to every top-level page — so automatic navigation is skipped for it. A custompage_navigation(below) still works there. - A directory holding a single page, which would only link to itself.
Custom navigation via frontmatter
You can override the default navigation for a page by defining
page_navigation. One entry is enough — unlike automatic navigation, an
explicit list is never suppressed:
---
title: Page with custom nav
page_navigation:
- href: /index.html
name: Home
- href: /contact.html
name: Contact
current: true
---Custom entries are passed to the template exactly as written. Nothing is
computed for them, so if you want the active class on one of them, set
current: true yourself, as above.
Entries must be mappings. An entry that is not one — most commonly an empty
list item, which YAML parses as null — is skipped with a warning rather than
breaking the render. A page_navigation that is not a list at all (a string, a
mapping) is ignored entirely, and the page falls back to no navigation.
Navigation metadata
Every page receives meta.pageNav, always with both keys and always with
elements as an array — empty when there is nothing to render:
meta.pageNav = {
activeClass: 'page-nav__link--active',
elements: [
{
name: 'Page Title', // the sibling page's `title`
href: '/path/to/page.html',
current: true, // is this the page being rendered?
position: 0 // index in the sorted list, 0-based
},
]
}position here is the emitted 0-based index in the finished list, which is
not the same thing as the position you write in frontmatter — that one is an
arbitrary sort key. For a custom page_navigation, elements holds your
entries verbatim, so neither current nor position is present unless you
wrote them.
Pages generated by other plugins
Some plugins create pages rather than just annotating them — for example
@nera-static/plugin-tags, whose tag overview pages are built while it runs.
Plugins execute in the order start: → alphabetical → end:, so by default
this plugin runs before plugin-tags and those pages do not exist yet when it
does. They end up with no meta.pageNav at all — not an empty one, no key.
Run this plugin last. In your project's config/plugin-order.yaml:
plugin-order:
- end:
- plugin-page-navigationEvery page then gets navigation, generated ones included: each tag overview page
is a sibling of the others in /tags/, so it receives a nav across all of them
with the current tag highlighted, exactly like any other directory. This is
safe — the plugin reads only generator-produced fields (href, dirname,
title, position) and writes only meta.pageNav, which nothing but your
templates consumes, so its position in the order cannot disturb another plugin.
This requires Nera v4.2.0+, where config/plugin-order.yaml is read. On
4.1.x the file is ignored and generated pages simply have no navigation.
Guard your markup either way. Ordering closes the gap for the plugins you
have today; add another page-generating plugin later and it reopens. A missing
meta.pageNav is undefined, so reading .elements off it throws and the
whole build fails, not just that page. The shipped template already guards.
If you write your own:
if meta.pageNav && meta.pageNav.elements.length
nav.page-nav
each item in meta.pageNav.elements
a.page-nav__link(href=item.href, class=item.current ? meta.pageNav.activeClass : '') #{ item.name }🛠️ Template Publishing
Use the default template provided by the plugin:
npx nera-page-navigationThis copies the template and the mixins it depends on:
views/vendor/plugin-page-navigation/
├── page-navigation.pug
└── helper/
├── mixins.pug
└── setup.pugPublishing skips when the destination directory already exists, so re-running never discards your edits. Note that the check is on the directory, not on each file: once it exists, nothing is copied into it — a template you deleted is not restored, and the command still exits 0.
To overwrite with the packaged versions:
npx nera-page-navigation --forceUpgrading the plugin?
--forceis what actually delivers a template change. Publishing skips a directory that already exists, so a site that published before an upgrade keeps its old copies and the new markup simply never appears. Upgrading without it is safe — it just does nothing.--forceoverwrites every file in that directory and discards local edits, so diff your published copies first if you have customised them.This matters for anyone who published before v2.2.0: the template shipped up to v2.1.0 could not compile at all, and only
--forcereplaces it.
Then include it in your layout:
include ../vendor/plugin-page-navigation/page-navigationThe path is relative to the including file, so from a layout in
views/layouts/ this resolves to views/vendor/…. A bare
views/vendor/… would resolve to views/layouts/views/vendor/… and fail.
Available navigation styles
+simpleNav– Basic horizontal navigation+pipeSeparated– Pipe-separated links+linkList– List-based navigation
Uncomment the layout option that suits your needs inside the template file.
🎨 Styling
The plugin uses BEM CSS methodology:
.page-nav { }
.page-nav__item { }
.page-nav__link { }
.page-nav__link--active { }
.page-nav--list { }
.page-nav--pipe-separated { }Customize or override these classes in your CSS.
These class names are a public contract. You style them from your own stylesheet, so renaming one is a breaking change and ships as a major version.
📊 Generated Output
The plugin injects navigation metadata into each page's meta object; the
markup comes from whichever mixin the template calls. For two pages with the
second one current, the shipped mixins produce:
+simpleNav:
<nav class="page-nav">
<a class="page-nav__link" href="/index.html">Home</a>
<a class="page-nav__link page-nav__link--active" href="/about.html">About</a>
</nav>+pipeSeparated:
<nav class="page-nav page-nav--pipe-separated">
<a class="page-nav__link" href="/index.html">Home</a> |
<a class="page-nav__link page-nav__link--active" href="/about.html">About</a>
</nav>+linkList:
<ul class="page-nav page-nav--list">
<li class="page-nav__item">
<a class="page-nav__link" href="/index.html">Home</a>
</li>
<li class="page-nav__item">
<a class="page-nav__link page-nav__link--active" href="/about.html">About</a>
</li>
</ul>When meta.pageNav.elements is empty, the template renders nothing at all.
🧪 Development
npm install
npx vitest run
npm run lintnpm test runs Vitest in watch mode and does not exit — use
npx vitest run for a single pass.
Tests cover:
- Sibling detection and ordering
- Custom override behavior
- Template rendering
- Active class logic
🤝 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.
- 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; this plugin uses no
generator feature above the 4.x line, and the layout-relative include above
needs no pug
basedir. The recommendedconfig/plugin-order.yamlentry needs v4.2.0+, where that file is read; on 4.1.x it is ignored and pages generated by other plugins get no navigation - Node.js: >= 20.0.0 (matches
engines.node) - Plugin Utils: ^1.2.0
- Plugin API: Uses
getMetaData()for injecting navigation metadata
📦 License
MIT
