FUNSTACK Router is built on the Navigation API, a modern browser API that provides a unified way to handle navigation. This guide explains the key differences from the older History API and the benefits this brings to your application.
The History API has been the foundation of single-page application routing for years, but it comes with significant limitations that make building routers more complex than necessary.
Reactive, not proactive: The popstate event fires after navigation has already occurred. This makes it difficult to intercept navigation, perform async operations like data fetching, or cancel navigation based on conditions.
// History API: popstate fires AFTER navigation
window.addEventListener("popstate", (event) => {
// Too late! The URL has already changed.
// We can only react to what happened.
console.log("Navigated to:", location.pathname);
});
// For programmatic navigation, we must manually call pushState
// AND then update the UI ourselves
history.pushState(null, "", "/new-page");
updateUI(); // Manually trigger UI updateFragmented event model: Different navigation types require different handling. Browser back/forward triggers popstate, but pushState and replaceState don't trigger any events. This leads to scattered navigation logic.
Custom Link components required: Since clicking an <a> tag causes a full page reload, routers must provide custom <Link> components that intercept clicks and call pushState instead.
The Navigation API fundamentally changes how navigation works by providing a single navigate event that fires before navigation commits. This allows routers to intercept and handle navigation declaratively.
// Navigation API: navigate event fires BEFORE navigation
navigation.addEventListener("navigate", (event) => {
// Navigation hasn't happened yet - we can intercept it!
const url = new URL(event.destination.url);
// Check the navigation type
console.log(event.navigationType); // "push", "replace", "reload", or "traverse"
// Intercept and handle with async operations
event.intercept({
async handler() {
// Fetch data, render components, etc.
const data = await fetchPageData(url.pathname);
renderPage(data);
// Navigation completes when handler resolves
}
});
});Key benefits of this approach:
navigate eventintercept() method lets you run async handlers before navigation completesevent.navigationType tells you exactly what kind of navigation occurred<a> Elements for SPA LinksOne of the most practical benefits of the Navigation API is that standard <a> elements work for SPA navigation without any special handling. The router intercepts navigation events from native links automatically.
// With FUNSTACK Router, native <a> elements just work!
function Navigation() {
return (
<nav>
{/* These are standard HTML anchor tags */}
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/users/123">User Profile</a>
</nav>
);
}
// No special <Link> component needed for basic navigation
// The router intercepts the navigate event and handles it as SPA navigationThis means you can use standard HTML in your components, and navigation "just works". The router intercepts same-origin navigation events and handles them without a full page reload.
This approach has several advantages over custom Link components:
<a> tag they already knowWhile FUNSTACK Router handles navigation for you, you can interact directly with the Navigation API when needed. This is useful for features like analytics tracking.
import { useEffect } from "react";
function App() {
useEffect(() => {
const controller = new AbortController();
// Listen for successful navigation completion
navigation.addEventListener(
"navigatesuccess",
() => {
// Track page view for analytics
analytics.trackPageView(location.pathname);
},
{ signal: controller.signal }
);
return () => controller.abort();
}, []);
return <Router routes={routes} />;
}The navigatesuccess event fires after navigation completes successfully, making it ideal for post-navigation actions. You can also use navigation.transition to track in-flight navigations or implement loading indicators.
Note: be careful when adding event listeners for navigate events since it may interfere with the router's own handling. Consider using onNavigate prop on the <Router> component for most use cases.
The Navigation API provides several advanced features that you can leverage directly when needed:
id and key, plus a dispose event that fires when the entry is removed from historyinfo — Pass non-persisted context data during navigation via navigation.navigate(url, { info })For more details, see the MDN Navigation API documentation.
<a> elements work — No special Link component required for SPA navigation; the router intercepts standard anchor tags automaticallynavigatesuccess for scroll restoration, analytics, or other post-navigation actions