Build a blog with Nera
In this tutorial you'll scaffold a Nera site, write two posts, and turn the home page into a blog index that lists them newest-first. Every file is shown in full, so you can follow along step by step.
1. Scaffold the site
npx @nera-static/nera new my-blog
cd my-blog
nera new creates the site and installs its dependencies. You'll touch three
places: pages/ for your content (it starts with pages/index.md, plus
pages/imprint.md and pages/privacy.md — placeholders to replace before you
publish), theme/views/ for the Pug templates (layouts/layout.pug is the HTML
shell, pages/default.pug renders a page into it), and config/ for settings.
Want a finished blog to edit instead? The blog starter sets one up with a theme, sample posts, tags, an archive, a feed and a sitemap:
npx @nera-static/nera new my-blog --starter blog
Its files won't match the steps below: this tutorial builds a simpler blog by hand, to show how the pieces fit together.
2. Write posts
Posts are Markdown files in pages/posts/. Create pages/posts/hello-world.md:
---
layout: pages/default.pug
title: Hello world
description: My first post.
createdAt: 2026-10-01
---
# Hello world
Welcome to my new Nera blog.
And a second one, pages/posts/second-post.md:
---
layout: pages/default.pug
title: My second post
description: What I learned this week.
createdAt: 2026-10-08
---
# My second post
A week in, and the blog lists this post first.
A page renders only if it names a layout. The title and description show
up in the list, and createdAt is the post's date. Always set it: without it,
Nera falls back to the file's creation time, which on a fresh checkout — on a CI
build server, say — is the same for every file, so the order would be arbitrary.
3. List the posts on the home page
@nera-static/plugin-simple-page-list collects the pages of a folder into a
sorted list. Install it and publish its template:
npm install @nera-static/plugin-simple-page-list
npx nera-simple-page-list
Nera picks up the plugin from your package.json on its own. The second command
copies the template to
theme/views/vendor/plugin-simple-page-list/simple-page-list.pug.
Tell the plugin which folder to list and how to sort it. Create
config/simple-page-list.yaml:
page_path: /posts
more_link_text: Read more
sortBy: date
sortOrder: descending
page_path gives you one flat list in app.pageList, which is all a blog with
one section needs. sortBy: date reads a post's date from the frontmatter and
falls back to createdAt, so your posts sort by the dates you just set.
Now a page template that renders the page's own content and the list below it.
Create theme/views/pages/blog.pug:
extends ../layouts/layout
block content
main#main
!= content
include /vendor/plugin-simple-page-list/simple-page-list
The two contents are unrelated. block content fills the slot of that name
in layouts/layout.pug. != content outputs the page's Markdown, which Nera
has already rendered to HTML; != leaves it unescaped, so it arrives as markup.
The leading slash makes the include path start at theme/views/, wherever the
template sits.
Finally, make the home page use it. Replace pages/index.md with:
---
layout: pages/blog.pug
title: My blog
description: Notes and posts, newest first.
---
# My blog
Welcome! Here is everything I have written so far.
This is the template you published — yours to edit:
//- Handles both shapes `getAppData` can produce:
//- flat — `app.pageList` is an array (config uses `page_path`)
//- grouped — `app.pageList` is keyed by path (config uses `page_paths`)
//- The flat branch emits exactly the markup this template has always emitted,
//- so published copies keep rendering identically.
mixin pageListItems(pages)
each page in pages
article.page-list__item
header.page-list__header
h3.page-list__item-title
a.page-list__link(href=page.href) #{page.title}
if page.description
p.page-list__description #{page.description}
footer.page-list__footer
a.page-list__more-link(href=page.href) #{page.moreLinkText}
-
var pageList = app.pageList
var isFlat = Array.isArray(pageList)
var groups = []
if (isFlat) {
groups = pageList.length ? [{ key: null, pages: pageList }] : []
} else if (pageList && typeof pageList === 'object') {
groups = Object.keys(pageList)
.filter(function (key) {
return Array.isArray(pageList[key]) && pageList[key].length > 0
})
.map(function (key) {
return { key: key, pages: pageList[key] }
})
}
if groups.length > 0
section.page-list
h2.page-list__title Recent Pages
if isFlat
+pageListItems(groups[0].pages)
else
each group in groups
div.page-list__group
h3.page-list__group-title #{group.key}
+pageListItems(group.pages)
Each post becomes an article with its title, description and a "Read more"
link, under the heading "Recent Pages". To change the heading or the markup, edit
this file — it is your copy now. Running npx nera-simple-page-list again skips
the folder because it already exists; npx nera-simple-page-list --force
overwrites it with the shipped version and discards your edits.
Prefer to write the list yourself? See Write your own local plugin.
4. Grow it
- Tags: give posts
tags:and get a page per tag with Tag your content. - Previous/next links: link each post to its neighbours in
pages/posts/with Add prev/next links between pages. - Sitemap: set your site's origin as in
Add canonical links for SEO, then add
@nera-static/plugin-sitemapfrom the plugin catalog. - Images and code:
@nera-static/plugin-images(responsive images) and@nera-static/plugin-code-blocks(syntax highlighting) are in the plugin catalog too.
5. Preview and build
npm run dev
Open http://localhost:3000: the home page lists "My second post" above "Hello
world", and each title links to its post. The preview rebuilds when you save a
file.
When you're ready to publish, build the site once:
npm run build
The finished blog is in public/ — upload that folder to any static host.