Builds the sidebar tree once and binds every navigation lookup to it.
Creates a Navigation over a list of content entries. It builds the sidebar
tree a single time and binds the four lookups a page needs to that one tree, so
the rail, the breadcrumbs, and the pager can never disagree about what the site
contains.
ts
import { createNavigation } from "@teasim/astro";const docs = createNavigation(entries, { base: "/docs" });docs.sidebar; // the tree, as getSidebar builds itdocs.prevNext("guides/installing"); // { prev, next }docs.breadcrumbs("guides/installing"); // [{ label, href, current }, …]docs.sections("guides/installing"); // top level, with the current one markeddocs.find("guides/installing"); // one node, or null// prevNext reads the entry's own frontmatter, so a page hand-wires nothing.docs.prevNext("intro"); // honours `next: false` in intro's frontmatter// An explicit override still wins.docs.prevNext("intro", { next: "guides/installing" });// Nothing here reads astro:content, touches the filesystem, or needs a build.createNavigation([{ slug: "a", title: "Alpha" }]).sidebar[0].href; // "/a"
Usually composeEntries(collection).entries. Order is not significant — the tree sorts by sidebar.order, then label
options
SidebarOptions
No
base, trailingSlash, includeDrafts, transform
SidebarOptions
Option
Type
Default
Notes
base
string
"/"
Prefix for every href; a path or an absolute URL. A path with no leading slash is still rooted
trailingSlash
boolean
false
Append / to every href except the root
includeDrafts
boolean
false
Keep entries marked draft
transform
(nodes: SidebarNode[]) => SidebarNode[]
—
Reshape the finished tree
Returns
Returns a Navigation — the tree plus four lookups bound to it.
Member
Type
Notes
sidebar
SidebarNode[]
The finished tree, already sorted and pruned
find(slug)
SidebarNode | null
null for a heading, an unknown slug, or an empty one
breadcrumbs(slug)
Breadcrumb[]
Ancestry, current: true on the last crumb; [] for an unknown slug
prevNext(slug, overrides?)
PrevNext
Both ends SidebarLink | null; never wraps
sections(slug)
SidebarSection[]
The top level, with the one holding slug marked current
Throws
Nothing. Bad content does the most sensible thing and carries on — two
entries that normalise to one slug become one node, last wins. That is how a
duplicate slug becomes one silently missing page, so run validateContent on
the same entries when you want the problems named instead — composeEntries
does exactly that, in dev only.
Generate createNavigation(entries, options) for a page. The five underlying
functions stay exported for a test driving one answer, or a page that wants
one without the rest.
Do not thread { prev: entry.data.prev, next: entry.data.next } into
prevNext — it reads the entry’s frontmatter itself. A mapper that forgets
makes those two schema fields silently inert.
Reach for getNavigation in a site that renders many pages: it takes the
same arguments and answers with the tree already built for that entries array,
keyed on the array itself. createNavigation rebuilds on every call, which is
what you want for a one-off, a test, or any options carrying a transform —
no key describes a function, so those are uncacheable and build every time.
Map entries with composeEntries, or toContentEntry one at a time — not by
hand: a hand mapper that drops externalLink, prev, next, or
previousSlug makes those fields inert and validateContent cannot report
what it never receives.
Both ends of prevNext are SidebarLink, whose slug and href are never
null. Do not generate a null check for them.
breadcrumbs keeps headings and prevNext skips them, so a trail is not a
slice of the reading order. Do not derive one from the other.
Pass base: import.meta.env.BASE_URL in an Astro project so every href
survives a deploy under a subpath.