跪拜 Guibai
← All articles
React.js · Frontend Framework · JavaScript

React Router v7 From Scratch: 7 Patterns Every SPA Needs

By 烬羽 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Every React SPA needs routing, and the gap between a working setup and one that loads fast, handles edge cases, and stays maintainable comes down to these seven patterns. Getting lazy loading, nested layouts, and navigation right early prevents the most common performance and UX regressions.

Summary

Frontend routing replaces the server's traditional URL-to-HTML mapping with JavaScript-driven DOM swaps, eliminating white-screen flashes between pages. This walkthrough builds a full React Router v7 system step by step, starting from the core trio of HashRouter, Routes, and Route, then layering on lazy loading with React.lazy and Suspense to cut initial bundle sizes by over 80%.

Nested routes use the Outlet component as a content slot, letting parent routes supply shared chrome like sidebars while child routes own their specific UI. Dynamic segments captured via useParams feed URL values directly into components, and useNavigate handles programmatic jumps for form submissions or auth redirects without triggering a full-page refresh. A wildcard path="*" route catches all unmatched URLs, but only when placed last in the Routes tree.

The guide closes with a layout decision heuristic: if a piece of UI would look wrong on one child page, it belongs in that child, not the parent. The same lifting principle that governs React state applies to route layouts.

Takeaways
Lazy loading with React.lazy and Suspense splits pages into separate chunks, reducing initial bundle size by over 80% when users only visit one page.
Use the Link component or useNavigate hook for all internal navigation; raw <a> tags trigger full-page refreshes that destroy SPA state.
Nested routes rely on the Outlet component as a content slot, with parent routes providing shared layout and child routes owning page-specific UI.
Dynamic route segments like :id are extracted via useParams and should be listed as useEffect dependencies when used to fetch data.
A wildcard path="*" route catches 404s but must be the last entry in Routes, since React Router matches top-to-bottom.
Decide parent-vs-child layout placement by asking whether a UI element makes sense on every child page; if not, push it down.
HashRouter listens for hashchange events and avoids HTTP requests on navigation, while BrowserRouter uses the History API for clean URLs.
Conclusions

The layout decision heuristic—'would this UI look wrong on that child page?'—is a practical rule that mirrors React's state-lifting principle but applies it to component structure, which many routing tutorials skip.

Framing lazy loading as an 80% bundle reduction rather than a generic performance tip makes the tradeoff concrete: the user pays only for the page they actually visit.

The warning against <a> tags in SPAs addresses a persistent beginner mistake that survives because it looks correct in the DOM but silently destroys application state.

Concepts & terms
HashRouter
A React Router component that uses the URL hash fragment (the part after #) for routing. Hash changes do not trigger HTTP requests to the server, so the page never does a full reload.
Outlet
A placeholder component in React Router that renders the matched child route. It acts as a slot where nested route content appears inside a parent layout component.
React.lazy
A React function that enables code-splitting by dynamically importing a component. The bundler separates that component into its own JavaScript chunk, loaded only when first rendered.
useParams
A React Router hook that returns an object of key-value pairs from the current URL's dynamic segments (e.g., :id in /user/:id becomes { id: '123' }).
useNavigate
A React Router hook that returns a function for programmatic navigation. Unlike window.location.href, it does not cause a full page refresh and preserves SPA state.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗