React Router v7: Route Tables Become Component Trees
Component-based routing collapses route config, parameter parsing, and layout nesting into a single declarative tree. Teams avoid writing and maintaining the same boilerplate — URL splitting, active link states, nested layout wrappers — across every page, and code splitting becomes a one-line change that doesn't touch the route definitions.
A hand-written HashRouter uses a flat key-value object for routes, but real applications quickly hit limits: nested layouts, URL parameter parsing, and code splitting all require custom code. React Router v7 addresses these by turning the route table into a component tree where `<Route>` nesting mirrors UI nesting. Dynamic segments like `:id` are extracted automatically via the `useParams` hook, and `<Outlet>` marks where child routes render inside a parent layout, eliminating repeated wrapper components. Navigation moves from raw `<a>` tags to `<Link>` and `useNavigate`, which update the URL through the History API without full page reloads. Lazy loading slots in through React's `lazy` and `Suspense`, splitting pages into separate chunks that download only when a route matches. The shift is from imperative routing — listen, parse, look up, call — to a declarative structure where the configuration itself documents the mapping from path to component.
The jump from imperative to declarative routing isn't about new capabilities — hand-written routers can do nesting, params, and lazy loading — but about removing repetitive plumbing that accumulates across pages.
React Router's design keeps routing concerns layered: the declaration layer describes what maps to what, the navigation layer handles how transitions fire, and the data layer feeds URL state into components, so each layer can change independently.
Code splitting via `lazy` is transparent to the route definitions, which means a project can start with eager-loaded pages and add splitting later without rewriting any route configuration.