How Route Guards, Login Auth, and Hidden State Actually Work in React Router v6
Every frontend app that gates content behind a login needs this exact pattern. Getting the redirect-back flow wrong traps users in a login loop; using `replace` instead of `push` and passing state through `Navigate` rather than query params are the two details that prevent that.
Protected routes in React Router v6 use a `children`-based guard component that reads a login flag from localStorage. When the flag is missing, the guard redirects to `/login` via `<Navigate>` and passes the original URL through the `state` property, which stays invisible in the address bar and survives page refreshes because it lives in the browser's history stack. The login form uses the native `FormData` API to extract input values without React controlled components, and on success it navigates back to the original destination using `replace: true` to purge the login page from the history stack, preventing a back-button loop.
BrowserRouter and HashRouter are contrasted: HashRouter uses the URL fragment and needs zero server configuration, while BrowserRouter uses the History API for clean URLs but requires a server fallback rule that serves `index.html` for all paths. The article also frames URL paths as RESTful resources and maps the three browser primitives—navigator, location, and history—to their React Router hook equivalents.
Using `replace: true` after login is not just a UX nicety; without it, the back button creates an infinite redirect loop that locks the user on the login page.
The `state` object on `<Navigate>` is a cleaner alternative to query parameters for passing redirect destinations because it keeps internal routing data out of the URL and survives a page refresh.
`FormData` is an underused native API that eliminates the need for controlled inputs in submit-only forms, reducing state management overhead for simple cases like login.
Separating the guard logic into a `children`-based wrapper component keeps authentication concerns decoupled from page components, making the pattern reusable across any route.