diagram.mmd — sequence
SPA Navigation Flow sequence diagram

SPA navigation flow describes how a single-page application responds to a URL change without performing a full browser page load — fetching only the data and code needed for the new view instead of an entirely new HTML document.

In a traditional multi-page application, clicking a link triggers the browser to issue a full HTTP GET, the server returns a complete HTML document, and the browser discards the current DOM and starts fresh. This round-trip typically takes hundreds of milliseconds and causes a visible flash. SPAs eliminate this cost by intercepting link clicks and URL changes, managing navigation entirely in JavaScript, and updating only the portion of the DOM that corresponds to the new route.

When a user clicks a link or calls router.push(), the router library (React Router, Vue Router, Angular Router) intercepts the event and calls the browser's History.pushState() API to update the URL bar without triggering a page load. The router then matches the new pathname against its route table to determine which component or module should render. If the route is code-split (see Code Splitting Architecture), the router dynamically imports the relevant JavaScript chunk. While the chunk loads, the app can display a loading indicator or skeleton screen.

Once the component code is available, the router may dispatch a data-fetching action to the application's data layer — an API call, a store selector, or a query client fetch. The data arrives asynchronously, the component renders with it, and the URL-based navigation is complete. Scroll position management and browser history stack maintenance (popstate event handling for back/forward) are handled by the router to preserve expected browser behavior. See Client Side Routing for the route-matching decision tree in detail.

The SPA model trades initial bundle size for subsequent navigation speed. After the first load, most navigations require only small JSON API responses rather than full HTML documents, making interactions feel nearly instant. This is why Lazy Loading Components and code splitting are essential companions to the SPA pattern.

Free online editor
Edit this diagram in Graphlet
Fork, modify, and export to SVG or PNG. No sign-up required.
Open in Graphlet →

Frequently asked questions

SPA navigation flow is the sequence of steps a single-page application performs when the URL changes: intercepting the navigation, updating the URL via `History.pushState`, matching the new route, loading any required code chunks, fetching data, and re-rendering the relevant portion of the UI — all without a full browser page reload.
The router library intercepts link clicks and programmatic navigation calls before they reach the browser. It calls `History.pushState()` to update the URL bar silently, matches the new path against the route table, dynamically imports any code-split chunk for the new route, and renders the matched component into the router outlet — replacing only the part of the DOM that represents the changing view.
Use SPA navigation for interactive applications where fast in-app transitions improve user experience: dashboards, social apps, productivity tools, and e-commerce carts. Multi-page (server-rendered) navigation is better for content-heavy sites where SEO, fast initial load on every URL, and progressive enhancement without JavaScript are priorities. Hybrid frameworks (Next.js, Nuxt, SvelteKit) support both models on a per-route basis.
The most common issues are: not configuring the server to serve `index.html` for all routes (causing 404s on direct links), failing to manage scroll position on navigation (page stays scrolled after route change), not code-splitting routes (causing a large initial bundle), and not handling `popstate` for browser back/forward, which can desync the router from the actual URL.
mermaid
sequenceDiagram participant User participant Browser participant Router participant CodeLoader as Code Loader participant API participant Component User->>Browser: Click link or call router.push() Browser->>Router: Intercept navigation event Router->>Browser: History.pushState(newURL) Router->>Router: Match route table alt Route chunk not loaded Router->>CodeLoader: Dynamic import(chunk) CodeLoader-->>Router: JS chunk loaded end Router->>Component: Instantiate route component Component->>API: Fetch required data API-->>Component: JSON response Component->>Browser: Render new view into DOM Browser->>User: Display updated page (no full reload) note">Note over Browser,Router: Back/forward handled via popstate event
Copied to clipboard