React Router v7 in Practice: Lazy Loading, Nested Routes, and the SPA Routing Playbook
React Router v7 is the current routing standard for new React projects, and the patterns here — lazy boundaries, nested layouts, hash vs. history mode tradeoffs — are the same decisions every SPA team faces at project start. Getting them right from the first route definition avoids costly refactors later.
A full demo application walks through the entire React Router v7 API surface using React 19 and Vite. The project configures HashRouter for zero-config deployment, then layers in lazy-loaded page chunks behind Suspense boundaries to keep initial bundle sizes small. Nested routes under /products show how parent layouts persist while child routes swap content through Outlet, and dynamic segments like /user/:id feed parameters into components via useParams.
Navigation uses Link components instead of raw anchor tags to avoid full-page reloads, while useNavigate handles programmatic redirects — a 404 page counts down three seconds before sending users home. A wildcard path="*" route catches all unmatched URLs, and the Navigate component with replace demonstrates clean URL migration from old paths to new ones.
The piece closes by mapping every React Router abstraction back to the native hashchange event, making the case that the framework is just a component-shaped wrapper around browser primitives.
Mapping React Router's API back to the raw hashchange event reveals how thin the abstraction really is — the framework adds component ergonomics but no novel browser capability.
The HashRouter vs. BrowserRouter decision is often framed as a deployment concern, but it also shapes the entire URL design of an application before a single server is configured.
Lazy loading is treated as an optimization, but in a Router context it doubles as a code-organization pattern: each route boundary becomes a natural split point for teams working on separate pages.