Skip to main content
Saved
Pattern
Difficulty Intermediate

Client-Side Routing

Handle navigation without full page reloads for seamless single-page application experiences.

By Den Odell Added

Client-Side Routing

Problem

Every click triggers a full page reload with all its consequences: the white flash, scroll jumping to top, form data disappearing, videos stopping, accordions collapsing. This is what web development looked like for decades, and it feels terrible compared to native apps where navigation is instant and everything stays put.

I’ve watched users lose their work because they accidentally clicked a link before saving. That spinning loading indicator on every navigation makes everything feel sluggish, even when content loads quickly.

Solution

Stop doing full page loads. Use the History API to change the URL without actually navigating, then swap out just the content that needs to change. Intercept link clicks, call pushState to update the URL, and render the new route’s component; when users hit back, listen for popstate and render the previous route.

This preserves everything (form state, scroll position, expanded accordions, playing media) because you’re swapping components in place rather than tearing down the entire page. Router libraries wrap this in declarative APIs: you map URL patterns to components, and the library handles the History API plumbing.

Example

Here’s client-side routing across frameworks: declarative route configuration, programmatic navigation, and scroll management.

Basic Router Setup

import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';

function App() {
  const navigate = useNavigate();

  const handleLogin = async (credentials) => {
    await login(credentials);
    navigate('/dashboard'); // Programmatic navigation
  };

  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/products">Products</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/products" element={<Products />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Scroll Position Management

// Scroll to top on route change
function ScrollToTop() {
  const { pathname } = useLocation();
  useEffect(() => window.scrollTo(0, 0), [pathname]);
  return null;
}

// Or preserve scroll per route
const scrollPositions = new Map();

function preserveScroll() {
  scrollPositions.set(location.pathname, window.scrollY);
}

function restoreScroll(pathname) {
  window.scrollTo(0, scrollPositions.get(pathname) || 0);
}

Hash-Based Routing

// For environments without server config (URLs: example.com/#/about)
window.addEventListener('hashchange', () => {
  render(location.hash.slice(1) || '/');
});

Benefits

  • Instant navigation without white flash or loading indicators. Click and you’re there.
  • State persists across navigation: form data, scroll position, expanded UI, playing videos.
  • Animated transitions become possible since the DOM stays continuous between views.
  • Server load drops because you’re fetching data, not re-rendering entire HTML pages.
  • Precise control over route guards, programmatic navigation, and transition hooks.

Tradeoffs

  • Requires JavaScript: the entire system breaks without it, so consider server-side fallbacks.
  • Server configuration needed: direct URL access must serve your app, not 404.
  • SEO complexity since crawlers may not execute JS; you might need SSR or prerendering.
  • Memory management falls on you: clean up listeners and subscriptions when components unmount.
  • Manual scroll and focus handling: browsers do this automatically for page loads, but not for client-side transitions.
  • History quirks if you mix up pushState and replaceState, creating confusing back-button behavior.

Summary

Client-side routing intercepts link clicks, updates the URL via the History API, and swaps content without page reloads. This creates fluid single-page experiences with proper browser history and shareable URLs.

Newsletter

A Monthly Email
from Den Odell

Behind-the-scenes thinking on frontend patterns, site updates, and more

No spam. Unsubscribe anytime.