React Router v7 From Scratch: Lazy Loading, Auth Guards, and Nested Routes
React Router v7 solidifies the component-based routing model that most React codebases rely on. Getting the auth guard pattern right—passing state through Navigate and using replace to keep the login page out of history—prevents the redirect bugs that plague nearly every app with protected routes.
BrowserRouter replaces the hash fragment with clean History API URLs, while Routes and Route turn routing into a declarative component tree. Dynamic segments like `/user/:id` feed parameters into `useParams`, and nested routes render child content through an `<Outlet />` placeholder so parent layouts stay persistent across navigation.
Code-splitting arrives through `React.lazy` and `<Suspense>`, which download page chunks only on first visit. A ProtectRoute wrapper checks `localStorage` and redirects unauthenticated visitors to `/login`, carrying the original path in `location.state` so `useNavigate` can return them exactly where they were headed after a successful login. A wildcard `path="*"` route at the end catches everything else and auto-redirects to the home page after three seconds.
The full architecture strings these together: a global Navigation bar sits outside `<Routes>`, while lazy-loaded pages, nested product routes, a redirect for legacy paths, and the auth guard all coexist inside the same `<Suspense>` boundary.
Storing auth state in localStorage and checking it synchronously inside a route guard is simple but leaves the door open to spoofing; a real app would verify a token or session server-side.
The `children` pattern used in ProtectRoute is the same composition mechanism that powers Modal and layout components, making it a transferable React skill rather than a routing-specific trick.
Passing `state` through `<Navigate>` and reading it back with `useLocation` creates a fragile coupling: if a user navigates directly to `/login` without state, the fallback to `"/"` is a silent default that could mask broken deep-link flows.