SPA Navigation & Scroll Restore¶
What it is¶
Void intercepts link clicks and fetches the target page as HTML, swapping only the article, TOC, sidebar, and header β without a full page reload. Scroll position is saved per page and restored on return, and the browser back/forward buttons work correctly.
When to use it¶
SPA navigation is always on. It makes the docs feel instant: clicking a link in the sidebar or article swaps content in ~100ms instead of a full page load. The prefetch layer pre-fetches links on hover so the swap is often instantaneous.
How it works¶
Click interception¶
A document-level click listener checks every <a> click:
- Is it a same-site,
http(s)link withtarget="_self"(or no target)? - Is it not a
mailto:,tel:, or hash-only link? - Is it under the configured
basepath?
If all pass, navigateTo(url, true) runs instead of a full navigation.
Page swap¶
navigateTo() does:
- Saves the current scroll position to
void-scroll-pos. - Pushes a new history entry.
- Fetches the target page with
X-Void-SPA: 1header. - Parses the response and extracts
.void-article,.void-toc,.void-nav,.void-footer, and the header page-title. - Swaps them into the DOM via
applyPage(). - Re-runs 19 page-scoped initializers (
reinitPageScoped()). - Restores scroll position (top for new pages; saved position for back/forward).
- Closes any open overlays (sidebar, TOC, search).
Prefetch on hover¶
When components.prefetch.show is true (default), hovering a link triggers
a <link rel="prefetch"> for the target page. If the service worker is
active, a low-priority fetch() is also issued to warm the SW cache.
Prefetch is skipped when:
navigator.connection.saveDatais true- The link is cross-origin and
prefetch.externalis false (default) - The URL contains
/# - The URL matches any
prefetch.excludesubstring
Scroll restore¶
Scroll positions are stored in void-scroll-pos as a map of
pageURL β { y, x, at }. On back/forward, the saved position is restored.
At boot, if the site opens at the root and the last visit was a different
page, _voidRestoreScroll navigates to it with { resume: true }.
Configuration¶
theme:
void:
components:
prefetch:
show: true # Enable prefetch-on-hover (default true)
external: false # Also prefetch off-site links (default false)
exclude: [] # URL substrings to never prefetch
Under the hood¶
- File:
void.jsβinitSPANavigation(config)(line ~5117),navigateTo(url, pushState)(line ~5311),extract(html)(line ~5146),applyPage(data)(line ~5163),reinitPageScoped()(line ~5197) - Scroll restore:
void-scroll-posβpageKey β { y, x, at }map, written directly tolocalStorage(not via thevoid-prefix helpers). - Session:
void-session.lastPage/void-session.lastAtβ last visited page, used for resume-to-last-page on boot. - Prefetch:
initPrefetch(config)(line ~5467) β gated bycomponents.prefetch.show; uses<link rel="prefetch">and optional low-priorityfetch(). - Safety: cross-origin,
mailto:,tel:,downloadattr,target !== "_self", and#-only links are never intercepted.
Accessibility notes¶
- SPA swaps preserve the document title and ARIA live regions, so screen readers announce the new page.
- Focus is not moved after a swap β the user stays at the link they clicked.
- The back/forward buttons work correctly via
popstate, and scroll position is restored, so keyboard navigation is uninterrupted.