LEARN · DEBUGGING GUIDE

Diagnosing React Router Infinite Redirect Loops

React Router infinite loops will cripple your SPA instantly. Stop the cycle: here’s how to track down, diagnose, and fix the root cause with real code and data.

IntermediateReact bugs4 min read

What this usually means

An infinite redirect loop in React Router almost always means the redirect logic is based on a condition that never resolves, or is toggled every render (such as a missing dependency in useEffect, uncontrolled state, or defaulting to an unauthenticated state after redirecting). This can be caused by asynchronous auth checks not updating state correctly, a typo in route paths, or circular redirects where routes point to each other. Under the hood, React keeps rerendering because the redirect action triggers a navigation, which triggers another rerender, and so on.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Open Chrome DevTools → Network tab, watch for repeating route requests (e.g., /login → /dashboard → /login...)
  • 2Temporarily remove all <Navigate> or <Redirect> components and see if the loop stops
  • 3Console.log authentication and user state in your redirect guard to see what value it actually holds
  • 4Check the rendered route tree by logging 'window.location.pathname' at every render
  • 5Scan for useEffect hooks with missing or incorrect dependency arrays responsible for auth checks
  • 6Review your protected route logic for overly broad conditions (e.g., !user instead of user === null)
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchsrc/App.js or src/routes.js (where route guards/Navigate components live)
  • searchAny AuthProvider or useAuth() custom hook files
  • searchBrowser DevTools Network and Console tabs
  • searchThe file with your PrivateRoute component (if you abstracted auth logic)
  • searchRedux/MobX/Context state files managing user or token logic
  • searchuseEffect hooks near authentication or post-login redirects
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warninguseEffect runs the redirect code every render due to missing dependency array
  • warningAuth state is always falsey, so redirect guard keeps redirecting to login
  • warningRoute typo or path mismatch causes a guarded route to never resolve
  • warningCircular redirects: Route A redirects to B, B redirects back to A
  • warningAsync auth check not updating state before first render
  • warningDefault value for auth context is 'unauthenticated', triggering redirect before state can update
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildAdd correct dependency arrays to useEffect to prevent repeated redirects
  • buildInitialize auth state as 'loading' and only redirect after actual check completes
  • buildLog user state and confirm it updates correctly after login/logout
  • buildEnsure route paths in <Navigate to="..." /> match actual registered routes
  • buildHandle asynchronous authentication logic with a loading state and conditional rendering
  • buildRefactor guard functions to check for user === null (not just falsy) before redirecting
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedReload app and confirm only one network request per route change
  • verifiedManually set user state to logged in/logged out and verify navigation behaves once
  • verifiedAdd console.log to redirect guard and confirm it fires only once per navigation
  • verifiedDisable JavaScript and validate server-side fallback (if present) doesn't redirect repeatedly
  • verifiedCheck browser history—should only log one redirect, not hundreds
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningReturning <Navigate> unconditionally from a useEffect or render function
  • warningRelying on default (non-asynchronous) user state for redirect logic
  • warningUsing undefined paths in redirect destinations
  • warningIgnoring the difference between user === null and user === false
  • warningBurying redirect logic deep in nested components instead of centralizing guards
( 07 )War story

Runaway Redirect Loop After OAuth Integration

Frontend EngineerReact 18, React Router v6, Context API, Custom useAuth hook

Timeline

  1. 13:05Deploy new OAuth login flow to dev environment
  2. 13:07QA reports: hitting /dashboard logs out and instantly redirects to /login
  3. 13:10Open DevTools, see 120+ repeated requests between /login and /dashboard
  4. 13:14Add console logs: useAuth() always returns { user: null, loading: false }
  5. 13:17Realize useEffect guarding <Navigate> lacks dependency array—fires every render
  6. 13:21Patch useEffect to run only on [user, loading] change
  7. 13:23Loop is gone; login flow now works once, then navigates as intended

We’d just added OAuth with Google sign-in to our React app, using a custom AuthProvider and updating our PrivateRoute logic. Everything worked locally, but as soon as we pushed a build to dev, QA reported an endless redirect between /dashboard and /login—the app was unusable.

Popping open Chrome DevTools, I saw network requests hammering /login and /dashboard alternately, spamming the server and pegging CPU. Console logs showed the auth context always returned null for the user, and without a dependency array, our useEffect ran on every render, triggering <Navigate> each time.

Adding [user, loading] as dependencies in useEffect and guarding redirects behind a 'loading' check stopped the madness. Now, the app only redirects once the auth state settles, and our OAuth flow works cleanly.

Root cause

useEffect for redirecting lacked a dependency array, so <Navigate> triggered on every render before auth state could update.

The fix

Added correct dependency array ([user, loading]) to useEffect, and gated redirects until loading was false.

The lesson

Always make async auth redirect logic dependent on both the user state and loading flag—never fire redirects unconditionally on render.

( 08 )How React Router Handles Redirects Under the Hood

React Router v6 uses <Navigate> components, not imperative history.push calls, for redirects. When you render <Navigate>, Router updates the location immediately, causing the component tree to re-render. If your component conditionally renders <Navigate> on a state that never resolves (like a perpetually falsey user), each render triggers another redirect, and you end up with an infinite loop.

This is aggravated when redirect checks happen every render rather than after specific state transitions. Thus, infinite loops often stem from incorrect effect dependencies or redirect guards.

( 09 )Dissecting Auth Flows and Default States

A classic pitfall: your auth context defaults to user: null before a fetch completes. If your redirect logic interprets 'user: null' as 'not authenticated', you'll redirect to /login on the initial render, even before the authentication check finishes.

The fix: introduce an explicit loading state in your context, and never redirect until loading is false. Example: if (loading) return <Spinner />; if (!user) return <Navigate to='/login' />;

( 10 )Circular Route Guards and Path Mismatches

Another source of loops: unintentional circular redirects. If /dashboard redirects to /login unless authenticated, and /login auto-redirects to /dashboard if already logged in, a stale or indeterminate auth state toggles between both routes forever.

Always ensure your guard logic is symmetrical and cannot flip-flop based on a single ambiguous state. Use explicit booleans or enums for auth status rather than relying on null/undefined.

Frequently asked questions

Why does my app only loop in production, not development?

Dev builds might hydrate state differently or have hot reloads masking timing issues. Production runs from cold start, exposing race conditions where auth state isn't ready before route guards fire.

Can useEffect cause a redirect loop in React Router?

Yes—if useEffect lacks a dependency array or has an always-triggered dependency, redirect code inside it will run after every render, creating an endless redirect situation.

How do I debug which component is causing the infinite redirect?

Temporarily comment out all <Navigate> usages. Add console.log at each route guard. Gradually re-enable logic until loop returns; the last change is your culprit.

What version-specific gotchas exist for React Router redirect loops?

v4/v5 used <Redirect>; v6 uses <Navigate>. In v6, returning <Navigate> inside a render is immediate; v4/v5 sometimes batched changes, so timing bugs can differ between versions.