跪拜 Guibai
← All articles
Frontend · JavaScript · Full-Stack

How Route Guards, Login Auth, and Hidden State Actually Work in React Router v6

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

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.

Summary

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.

Takeaways
HashRouter changes only the URL fragment and triggers no server request; BrowserRouter uses the History API and needs a server fallback to `index.html`.
URL paths are treated as RESTful resources: `/products/123` is the product with ID 123, not just a page.
React Router wraps three browser primitives: `useNavigate` wraps `window.history`, `useLocation` wraps `window.location`, and `Link`/`Navigate` internally use `window.navigator`-style capabilities.
A route guard is a component that takes `children` as a slot, checks a condition, and either renders the children or redirects.
`<Navigate>` accepts a `state` prop that stores data in `history.state`; it is invisible in the URL and persists across refreshes.
The login form uses the native `FormData` API to read input values by their `name` attributes, avoiding React controlled-component boilerplate.
After login, `navigate(from, { replace: true })` replaces the login page in the history stack so the back button does not re-trigger the guard.
`localStorage` is scoped by domain, so the login flag written on the login page is readable by the guard component on protected routes.
Conclusions

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.

Concepts & terms
Route Guard
A wrapper component that checks an authentication condition (e.g., a login flag in localStorage) and either renders its children or redirects the user to a login page.
History API (pushState / replaceState)
A browser API that lets JavaScript change the URL path without triggering a full page reload. `pushState` adds a new history entry; `replaceState` overwrites the current one.
FormData API
A native browser API that extracts key-value pairs from a `<form>` element based on the `name` attributes of its inputs, without requiring React state bindings.
RESTful URL design
A convention where URL paths represent resources (e.g., `/products/123` identifies a specific product) rather than server-side actions or page files.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗