What is client-side routing in an SPA — and what the browser stops doing for you

You click a link, the address bar changes to /about, a new page is on screen. Looks like business as usual.
But the server never even heard about that navigation. No request went out. The same page simply redrew itself and changed the text in the address bar.
That's how single-page applications — SPAs — work. Fast and smooth. But there's a catch: on every navigation the browser used to do a pile of small chores for you. Now nobody does them unless you take care of it.
A normal navigation vs. one in an SPA
A regular site. Click a link → the browser sends a request to the server → gets new HTML → throws away the old page and renders the new one.
An SPA. MDN defines it as an app that loads one HTML document and then updates its content with JavaScript. The link click is intercepted by code. The code changes the address, takes the right piece of UI, and swaps it in for the old one.
That interception and swap is called client-side routing: the decision "what to show at this address" is made not by the server but by JavaScript in the browser. If you work with React, routing usually comes from a separate library — React Router — or from a framework like Next.js with its own link component.
How it works under the hood
It all rests on the browser's History API. The key method is history.pushState().
history.pushState({}, "", "/about");
It adds a history entry and changes the address. MDN stresses that the browser won't attempt to load that URL. Only the text changed. Showing the right screen is your code's job.
Two details that surprise almost everyone:
- The second argument does nothing. It was once meant to be the page title. MDN now calls it
unused: the parameter exists for historical reasons, and passing an empty string is safe. - The Back button is a separate signal. When someone presses Back or Forward, the browser fires a
popstateevent. CallingpushStateitself does not fire it. The router listens forpopstateand redraws the screen for the old address.
Four things the browser no longer does for you
This is why it was worth reading to here. On a normal navigation all of this happened automatically. In an SPA, the router is responsible for each one — and sometimes nobody is.
- Scrolling to the top. A normal new page opens at the start. In an SPA you read an article to the end, click "Next" — and the new one opens at the bottom. Good routers fix this: React Router has a
ScrollRestorationcomponent, and Next.js links scroll to the top by default. - The tab title.
pushStatedoesn't change it — that useless second argument again. If code doesn't updatedocument.title, every page gets the same name in the tab and in browser history. - The screen-reader announcement. On a normal load, a screen reader reads the new page's title, and a blind user knows the navigation happened. In an SPA — silence. Next.js has a built-in route announcer for this: it looks for the name in
document.title, then in theh1, then falls back to the URL path. A hand-rolled router won't have this until you add it. - The server knows nothing about the address. Inside the app,
/aboutworks. But open that address directly or hit Refresh, and the request goes to the server — which has no/aboutfile. That's the classic bug we covered separately: why a page shows 404 after refresh.
How to check your app in five minutes
AI happily generates SPAs and almost never checks these four points on its own. Walk through them by hand:
- Scroll down a long page and follow a link. Did the new page open at the top?
- Visit three different pages and watch the tab title. Does it change?
- Press Back twice. Does the app return to the previous screens or kick you off the site?
- Copy the address of an inner page and open it in a new tab. Did it load, or 404?
If anything is broken, that's no reason to ditch the SPA. It's a to-do list for the router. Whether you need an SPA at all is easier to decide with React vs. plain HTML. And by the way, the address can hold more than the page — it can hold screen state too: filters, sort order, the open tab. How that's done is in the guide how to keep filters in the URL.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.
Does client-side routing hurt SEO?
MDN lists SEO as one of the SPA trade-offs: search engines and link-preview bots have to run JavaScript to see the content. For a dashboard behind a login it doesn't matter; for a blog or a store, server rendering is safer.
How is it different from hash addresses (#/about)?
Hash routing puts the path after #, and that part of the address is never sent to the server. So refreshing doesn't break — but the addresses look ugly. React Router has a separate mode for this.
Do I need to write routing myself?
No — use a ready-made router: React Router, Next.js routing, or whatever your framework ships with. Your job is to check the four points above, not to reinvent pushState.





