跪拜 Guibai
← All articles
React.js · Full-Stack · Performance Optimization

The One Missing Line That Breaks React Router Login Redirects

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

Broken post-login redirects are a top user-complaint generator in any app with authentication. The fix is two lines of code, but skipping them means every authenticated user starts their session annoyed and disoriented.

Summary

A route guard that redirects to `/login` without carrying the original path loses the user's intent. The fix is passing `location.pathname` through React Router's `state` prop on `<Navigate>`, then reading it back with `useLocation` on the login page to redirect post-login. Two `replace` calls keep the browser history clean so the back button doesn't dump users back onto the login form.

A subtle trap is using the global `window.location` instead of the hook `useLocation()`. It works by accident when the guard mounts on the protected route, but breaks under reuse or when components persist across navigations. The consistent pattern is `useLocation` everywhere.

The whole mechanism hinges on a single handoff: `state.from` carries the intended destination across the redirect, and the login page's `navigate(from, { replace: true })` completes the round trip.

Takeaways
Pass the current pathname via `state={{ from: location.pathname }}` on the `<Navigate to="/login">` inside the guard.
Read it back on the login page with `const from = location.state?.from || '/'` and call `navigate(from, { replace: true })` after login.
Use `replace` on both the guard's redirect and the post-login navigation so the browser back button skips the login page entirely.
Always use React Router's `useLocation()` hook inside guards, never the global `window.location`, to avoid stale reads when components are reused.
Conclusions

The bug isn't a routing mistake; it's a missing handoff. The guard knows where the user wanted to go but throws that information away before the login page can act on it.

Using `window.location` instead of `useLocation()` works by coincidence on first mount but is a time bomb: any refactor that keeps the guard mounted across route changes will read the wrong path.

The `replace: true` on both ends of the redirect is what makes the flow feel seamless. Without it, the browser history traps users in a login loop when they press back.

Concepts & terms
Route guard (protected route)
A wrapper component that checks an authentication condition before rendering its children; if the check fails, it redirects to a login page instead.
React Router state on Navigate
The `state` prop on `<Navigate>` passes data to the target route via the history entry's location state, accessible with `useLocation().state`. It persists across redirects without appearing in the URL.
replace in navigation
When `replace: true` is set on `navigate()` or `<Navigate replace />`, the current history entry is replaced rather than pushed, so the back button skips the replaced page.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗