Routing
In a single page application the server stops handing out pages, so something has to answer the question every navigation asks: given this URL, what should the user see? That something is the router, and it is one of the most load-bearing pieces of frontend architecture. Treat it as a system rather than a convenience, and a lot of subtle bugs, broken refreshes, dead links, leaked state, simply stop happening.
This guide is the architecture-level view of routing, building on the practical routing basics from Foundations and the layered picture in SPA architecture.
What you'll learn
What a router does
A router has one core job: keep the view in sync with the URL, in both directions. Change the URL and the right view appears; trigger an in-app navigation and the URL updates to match.
The URL as application state
The cleanest way to think about it is that the URL is a piece of application state, often the most important piece, because it is shareable, bookmarkable, and survives a refresh. The router's responsibility is to treat the address as the source of truth for which view is active, so that the same URL always reproduces the same screen. Everything else routing does flows from that contract.
Route matching and parameters
To turn a URL into a view, the router compares the current path against a table of route patterns and picks the one that fits. Patterns are how a finite list of routes covers an infinite set of URLs.
Static, dynamic, and wildcard routes
var routes = {
'/': HomeView,
'/users/:id': UserView, // dynamic segment, :id is a parameter
'*': NotFoundView // wildcard fallback
};
Static routes like / match exactly, dynamic segments like :id capture a value and pass it to the view as a parameter, and a wildcard catches anything unmatched so you can show a real not-found view. Order matters: match the most specific patterns before the general ones, and define a fallback so an unknown URL renders a deliberate page rather than nothing.
Nested and layout routes
Real interfaces are not flat. A dashboard wraps a sidebar around a section, which itself wraps a detail panel, and routing should mirror that nesting rather than fight it.
Composing UI with nested routes
Nested routes let a parent route render a shared layout with an outlet, a placeholder, into which the matched child route renders. A path like /settings/profile resolves to the settings layout containing the profile view, so common chrome is defined once and child views slot in. Composing the UI from nested matches keeps layouts from being duplicated across every route and mirrors the structure users actually navigate.
History API versus hash routing
There are two ways a client router can represent the route in the URL, and the choice has consequences beyond aesthetics. One needs server cooperation; the other does not.
Clean paths and the server fallback
The History API gives clean paths like /users/42 through pushState, which look right and are best for SEO, but a direct visit to that path hits the server, so the server must be configured to return the app shell for app routes. Hash routing keeps the route after a # and needs no server config, since the server never sees the fragment, at the cost of uglier URLs and weaker indexing. Prefer clean paths with a server fallback, the approach the SPA indexing guide also recommends, and reserve hash routing for when you cannot configure the server.
Navigation lifecycle and guards
A navigation is not instantaneous; it is a small lifecycle with points where you may want to intervene. Guards are the hooks into that lifecycle.
Guards and data loading on navigation
Before a route activates, a guard can allow it, block it, or redirect, an authentication guard sending an unauthenticated user to a login route is the canonical case. The same lifecycle is where you load the data a view needs, so the view renders with its data ready rather than flashing empty. Because users navigate fast, handle cancellation too: if a new navigation starts before the last one finishes, abandon the stale one so its result never overwrites the current view.
Code-splitting routes
As an app grows, shipping every view's code in one bundle makes the first load heavier than it needs to be. Routing is the natural seam along which to split that code.
Loading route code on demand
Code-splitting by route means each view's code is loaded only when its route is first visited, so the initial download carries just the entry view and the rest arrives on demand. This keeps the app shell small, which directly helps the performance signals the JavaScript SEO cluster cares about. Pair it with a loading state during the fetch, and prefetch a route's code when a link is likely to be clicked, so the split is invisible to users. With routing understood as a system, the next guides turn to what those views actually manage, starting with application state.
Frequently Asked Questions
What is client-side routing?
Client-side routing is when a router in the browser intercepts navigation and maps the current URL to a view, without a full page reload. The address becomes a piece of application state, and the router keeps the view in sync with it.
What is the difference between hash and history routing?
Hash routing keeps the route after a # in the URL and needs no server configuration, because the server only ever sees the part before the hash. History API routing uses clean paths like /users/42 but requires the server to return the app for those paths, or a direct visit will 404.
What are route guards?
Route guards are hooks that run before or after a navigation so you can allow, block, redirect, or load data for it. A common example is an authentication guard that redirects an unauthenticated user to a login route before the protected view renders.
Why does my SPA route 404 on refresh?
With History API paths, refreshing or visiting a route directly sends the request to the server, not the client router. If the server is not configured to return the app shell for those paths, it cannot find a file there and responds with a 404. Add a fallback that serves the shell for app routes.
Read next: back to the Frontend Architecture hub, or revisit client-side architecture for where routing fits.
Want to wire a router by hand?
The complete Backbone guide builds routing on the History API step by step.
Explore the Backbone Guide →