Prev / next navigation

Every content page knows its neighbours. Within a collection, the pages sit in the collection’s sort order (ordered guide chapters, dated blog posts) and each one is handed links to the page before and after it. No configuration: the data is always there for a layout template to use.

The links arrive alongside frontmatter and taxonomies, on page.nav:

#let page(page, body) = {
  body
  let nav = page.nav
  if nav.prev != none {
    link(nav.prev.url)[← #nav.prev.title]
  }
  if nav.next != none {
    link(nav.next.url)[#nav.next.title →]
  }
}

Each of nav.prev and nav.next is either none (at the ends of a collection) or a dictionary with the neighbour’s url and title. The order follows the collection’s own sort and reverse settings, so a sort="date" reverse=#true blog links newest-to-oldest, while a sort="order" guide links chapter to chapter.

Navigation never crosses a collection boundary, and drafts or future-dated pages that were filtered out are skipped: the sibling links only ever point at pages that were actually built.

This site uses it: the pager under every guide chapter and blog post is exactly this page.nav.

The whole site, in order

The same build view is available as page.sections: every content collection, in its sort order, as (id, pages) where each page is (url, title):

#let page(page, body) = {
  body
  for section in page.sections {
    if section.id == "guide" {
      for entry in section.pages {
        link(entry.url)[#entry.title]
      }
    }
  }
}

Generated listing pages (taxonomy and pagination indexes) are left out: only authored content appears. This is how the sidebar on the left is built: it is not a hand-kept list, so it can never drift from the pages or from the prev/next pager, which both read from the same ordered set. Reorder a collection’s sort, or a page’s order, and the sidebar and the pager move together.