Building pages
A page is any .html, .md, or engine-native (.njk/.liquid) file under your markup in
directory. Its output path mirrors its source path: src/markup/about.md โ dist/about.html.
Front matter
Start a page with a YAML front-matter block. layout picks the wrapping template; everything else
is available under page.*:
---
layout: default
title: About
description: Who we are and why.
order: 2
---
# About us
We build things with Poops.
Common fields: title, description, layout, date, order, published, nav, navTitle.
Any custom field you add is yours to use in templates and it flows into the search index.
Poops also computes read-only fields on page: content (the rendered body), url, wordcount,
excerpt, and filePath. excerpt is the first prose paragraph as plain text (headings and
comments skipped, capped at 160 chars) โ use it as the fallback for a missing description, e.g. in
the meta tag below or the og/jsonld filters. filePath is the source file's path relative to
your project root (posix separators), for building "Edit on GitHub" links โ see
Building a documentation site.
Layouts
Put base templates in _layouts/ (a directory ignored for output, but on your includePaths). A
Nunjucks layout defines a content block:
<!DOCTYPE html>
<html lang="{{ page.lang or site.lang or 'en' }}">
<head>
<meta charset="UTF-8">
<title>{{ page.title or site.title }}</title>
<meta name="description" content="{{ page.description or page.excerpt or site.description }}">
<link rel="stylesheet" href="{{ relativePathPrefix }}css/styles.min.css">
</head>
<body>
{% include "site-header.html" %}
<main>{% block content %}{% endblock %}</main>
{% include "site-footer.html" %}
</body>
</html>
The page body is rendered, then dropped into {% block content %}.
Tip
Always prefix asset and link URLs with relativePathPrefix. It resolves to the correct number
of ../ for the page's depth, so a page at dist/blog/post.html still finds css/styles.css.
Partials & includes
Reusable snippets live in _partials/ (also on includePaths). Include them by file name:
{% include "site-header.html" %}
In Liquid, use render:
{% render "site-header.liquid" %}
Global and page data
Three sources of data reach your templates:
siteโ set once in the markup config (site.title,site.url, โฆ).datafiles โ JSON/YAML loaded as globals named after the file._data/links.jsonbecomeslinks, so{{ links.github }}works everywhere.pageโ the current page's front matter.
{
"markup": {
"in": "src/markup",
"out": "dist",
"options": {
"site": { "title": "My Site" },
"data": ["_data/links.json", "_data/authors.yaml"]
}
}
}
Note
File names are normalized: spaces, dashes and dots become underscores. the awesome-links.json
is available as {{ the_awesome_links }}.
Markdown
Markdown files are rendered to HTML and then run through the template engine, so template expressions work inside Markdown too. Fenced code blocks are syntax-highlighted at build time (highlight.js) โ you ship a CSS theme, not a highlighter.
```js
const greet = (name) => `Hello, ${name}!`;
```
Info
Registered highlight languages include js, ts, css, scss, html, json, bash,
python, ruby, php, go, rust, yaml, sql, diff and more. Omit the language to let
highlight.js auto-detect.
Useful filters
Poops adds template filters usable in both engines โ slugify, markdown, toc, date,
jsonify, svg, highlight, groupby, the array helpers concat (returns a new array with the
value appended) and push (appends in place), and the image helpers srcset, exif, images:
<h1>{{ page.title }}</h1>
<time>{{ page.date | date("MMMM D, YYYY") }}</time>
{{ "src/icons/logo.svg" | svg }}
For markdown source, run markdown before toc so code-fence content doesn't get misread as headings:
{{ page.content | markdown | toc }}
Next: Images & galleries.