跪拜 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 ↗