Breadcrumbs from stati.nav

getBreadcrumbs() starts at the page's own folder. findNode() gives the whole trail.

SNP 105 #591 Filed stati eta

eta
<%
  /* /docs/api/hooks: /docs, /docs/api, /docs/api/hooks, each looked up in the navigation */
  const parts = stati.page.url.split('/').filter(Boolean);
  const crumbs = parts
    .map((_, i) => stati.nav.findNode(`/${parts.slice(0, i + 1).join('/')}`))
    .filter(Boolean);
%>
<% if (crumbs.length) { %>
  <nav aria-label="Breadcrumb">
    <ol>
      <li><a href="/">Home</a></li>
      <% crumbs.forEach((crumb, i) => { %>
        <li>
          <% if (i === crumbs.length - 1) { %>
            <span aria-current="page"><%= crumb.title %></span>
          <% } else { %>
            <a href="<%= crumb.url %>"><%= crumb.title %></a>
          <% } %>
        </li>
      <% }) %>
    </ol>
  </nav>
<% } %>

On /docs/api/hooks that's Home › Docs › API › Hooks, with each title from the page's front matter. The built-in stati.nav.getBreadcrumbs() gives only API › Hooks there: the navigation tree is flat, with every folder of pages as a node at the top, so its trail starts at the page's own folder.

The same flatness catches you in menus. A subfolder's pages aren't in its parent's children, so look the subfolder up at the top of the tree:

eta
<% const api = stati.nav.tree.find((node) => node.isCollection && node.url === '/docs/api'); %>
<% for (const page of api?.children ?? []) { %>
  <a href="<%= page.url %>"><%= page.title %></a>
<% } %>

Every folder on the way needs an index.md, or its crumb links to a page that doesn't exist.

In the Stati docs: Tree shape, findNode() and getBreadcrumbs().