@nera-static/plugin-page-pagination
v2.3.1
Published
A plugin for Nera static site generator to create previous/next page pagination within directory structures. Perfect for blog posts, documentation sections, and sequential content navigation.
Maintainers
Readme
@nera-static/plugin-page-pagination
A plugin for the Nera static site generator that creates previous/next navigation between sibling pages. Perfect for documentation sites, tutorials, or any sequential content that needs pagination-style navigation.
✨ Features
- Automatically generates previous/next links based on sibling pages
- Supports custom sorting via
pagination_order(or custom property) - Falls back to creation date when no order is specified
- Configurable sorting property via
config/page-pagination.yaml - Deterministic ordering — the result never depends on the order files happen to be read from disk
- Includes a ready-to-use Pug template
- Lightweight and easy to integrate
- Works on Nera v4.1.0+ (see Compatibility)
🚀 Installation
Install the plugin in your Nera project:
npm install @nera-static/plugin-page-paginationNera will automatically detect the plugin and inject pagination metadata during the build.
⚙️ Configuration
Customize the order property by creating config/page-pagination.yaml:
order_property: custom_orderThis tells the plugin to use custom_order instead of pagination_order.
Example with custom property
---
title: Chapter 1
custom_order: 100
------
title: Chapter 2
custom_order: 200
---🧩 Usage
Automatic sibling pagination
Pages in the same directory are linked in order:
- Pages with a
pagination_order, sorted by that value - Then pages without one, sorted by creation date
- Ties are broken by
href, so the result never depends on the order files happen to be read from disk
Mixing the two is safe: every page that defines pagination_order comes before
every page that does not. pagination_order: 0 is a valid value and sorts
first — it is not treated as "unset".
pagination_order is normally a number, and numeric values — including numeric
strings such as '10' — are compared as numbers, so 2 sorts before 10. A
non-numeric value (pagination_order: intro) is compared as text instead, which
is deterministic but sorts 10 before 2. Do not mix the two styles in one
directory unless you have checked the result.
Pages without a layout are excluded
Nera writes a page to public/ only if its frontmatter has a layout, so a
page without one is never rendered. Such pages are left out of the pagination
chain entirely — they neither appear as a previous/next target nor receive
pagination of their own. This matters if you use
@nera-static/plugin-stacks,
which recommends omitting layout on stack pages: those stacks will not show up
as phantom links in a sibling page's navigation.
---
title: Getting Started
pagination_order: 1
------
title: Advanced Topics
pagination_order: 2
---Result:
- "Getting Started" → Next: "Advanced Topics"
- "Advanced Topics" → Previous: "Getting Started"
Directory-based grouping
Only pages within the same directory are linked:
pages/
├── docs/
│ ├── intro.md
│ └── start.md
└── blog/
└── post.mdTemplate integration
meta.pagePagination is added to each page:
{
previous: { href: "/docs/intro.html", name: "Introduction" },
next: { href: "/docs/advanced.html", name: "Advanced Topics" }
}name is the sibling's title from its frontmatter. A page without a title
produces a link with no text, so give every paginated page one.
A missing neighbour is false, not an absent key. The first page in a
directory gets { previous: false, next: { … } } and the last gets
{ previous: { … }, next: false }. Test for truthiness — a check like
meta.pagePagination.previous !== undefined is always true.
Use in Pug:
if meta.pagePagination.previous
a.previous(href=meta.pagePagination.previous.href)
| ← #{meta.pagePagination.previous.name}
if meta.pagePagination.next
a.next(href=meta.pagePagination.next.href)
| #{meta.pagePagination.next.name} →Pages generated by other plugins
meta.pagePagination is written only onto the pages that exist when this plugin
runs. Plugins are applied in the order start: → alphabetical → end:, so a
plugin that creates pages and sorts after plugin-page-pagination —
@nera-static/plugin-tags, which builds its tag overview pages, is the common
case — produces pages on which the key is absent entirely.
To paginate those pages too, run this plugin last via
config/plugin-order.yaml:
plugin-order:
- end:
- plugin-page-paginationEach tag overview page then gets a previous/next chain across the others in
/tags/. Whether you want that is your call; without the entry those pages
simply render no pagination.
If you write your own markup instead of using the shipped template, guard the
key — it is absent, not empty, so meta.pagePagination.previous on a
generated page throws and takes the whole build down. The shipped template does
this for you:
- var pagePagination = (typeof meta !== 'undefined' && meta && meta.pagePagination) || {}
if pagePagination.previous
a.previous(href=pagePagination.previous.href) #{ pagePagination.previous.name }🛠️ Template Publishing
Use the default template provided by the plugin:
npx nera-page-paginationThis copies the template to:
views/vendor/plugin-page-pagination/page-pagination.pugPublishing skips when the destination directory
views/vendor/plugin-page-pagination/ already exists — the check is on the
directory, not on each file. Re-running therefore never discards your edits, but
it also copies nothing at all: if you delete a published template and re-run the
command, the file is not restored and the command still exits 0.
--forceis what delivers a template update. When a release changes the shipped template, upgrading the package does not change the copy underviews/vendor/. Your site keeps the old template silently — the upgrade is clean and the new behaviour simply never appears. Re-publish to pick it up:npx nera-page-pagination --forceThis discards any local edits to the published template, so diff first if you have customised it.
Include it in your layout:
include ../vendor/plugin-page-pagination/page-paginationThe path is relative to the including file, so from a layout in
views/layouts/ this resolves to views/vendor/…. Adjust the number of ../
segments to match your layout's depth; a bare vendor/… would resolve to
views/layouts/vendor/… and fail.
On Nera 4.3.0 and later you can use the location-independent root-absolute form
instead, which is resolved relative to views/ and so works from any depth:
include /vendor/plugin-page-pagination/page-paginationNote there is no views/ segment in it — include /views/vendor/… looks for
views/views/vendor/… and fails.
🎨 Styling
The plugin uses BEM CSS methodology:
.page-pagination { }
.page-pagination__link { }
.page-pagination__link--previous { }
.page-pagination__link--next { }Customize these classes in your CSS.
These class names are a public contract. You hold your own copy of the template under
views/vendor/plugin-page-pagination/and style it from your own CSS, so renaming a class here is a breaking change and only ships in a major release.
📊 Generated Output
The plugin injects pagination metadata into meta.pagePagination; the shipped
template renders it as a <nav> block. Rendered by Nera from the templates in
this package:
<!-- first page in the directory: next only -->
<nav class="page-pagination"><a class="page-pagination__link page-pagination__link--next" href="/docs/page2.html">Page 2</a></nav>
<!-- middle page: both links -->
<nav class="page-pagination"><a class="page-pagination__link page-pagination__link--previous" href="/docs/page1.html">Page 1</a><a class="page-pagination__link page-pagination__link--next" href="/docs/page3.html">Page 3</a></nav>
<!-- last page: previous only -->
<nav class="page-pagination"><a class="page-pagination__link page-pagination__link--previous" href="/docs/page2.html">Page 2</a></nav>
<!-- page with no siblings: the nav element is still emitted, empty -->
<nav class="page-pagination"></nav>The empty <nav> in the last case is worth knowing about if you style the block
with borders, padding or margins — use .page-pagination:empty to hide it.
If you write your own markup instead, meta.pagePagination is the only data the
plugin provides; see Template integration.
🧪 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:
- Sibling page detection and grouping
- Sorting by order property and fallback
- Previous/next link generation
- Edge cases (first page, last page, layout-less siblings)
- Ordering determinism across every input permutation
- Template rendering, including pages where
meta.pagePaginationis absent
🤝 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+ — this is the baseline 4.x floor, not a feature
requirement; the plugin uses no generator feature above it. The optional
config/plugin-order.yamlentry described under Pages generated by other plugins needs v4.2.0+, where plugin ordering was added; on 4.1.x the file is ignored and plugin-generated pages get no pagination. The optional root-absolute include form needs v4.3.0+; the relative form documented above works on every version. - Node.js: >= 20.0.0
- plugin-utils: ^1.2.0
- Plugin API: Uses
getMetaData()for pagination metadata
📦 License
MIT
