[{"data":1,"prerenderedAt":130},["ShallowReactive",2],{"qa-\u002Freact\u002Frouting\u002Fprotected-routes":3},{"page":4,"siblings":114,"blog":127},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":105,"related":106,"seo":107,"seoDescription":108,"stem":109,"subtopic":6,"topic":110,"topicSlug":111,"updated":112,"__hash__":113},"qa\u002Freact\u002Frouting\u002Fprotected-routes.md","Protected Routes",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,4,"\u002Freact\u002Frouting\u002Fprotected-routes",[23,28,32,36,40,44,48,53,57,61,65,69,73,77,81,85,89,93,97,101],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-protected-route","easy","What is a protected route and why is it needed in a React SPA?","A **protected route** is a route that only renders its component when the user meets\na certain condition — typically being authenticated. Without it, any visitor who knows\na URL can directly navigate to sensitive pages (dashboards, admin panels, account\nsettings) because the browser fetches the static JS bundle and renders the page\nentirely client-side.\n\nThe solution is an **auth guard** component that checks the auth state before\nrendering child routes. If the check fails it **redirects** to the login page instead\nof rendering the protected content.\n\n```jsx\n\u002F\u002F Minimal guard — renders children or redirects\nfunction RequireAuth({ children }) {\n  const { user } = useAuth(); \u002F\u002F check auth state\n  if (!user) {\n    return \u003CNavigate to=\"\u002Flogin\" replace \u002F>; \u002F\u002F kick to login\n  }\n  return children; \u002F\u002F authenticated — render the page\n}\n```\n\n**Rule of thumb:** A protected route is just a conditional render — show the page or\nredirect; the *real* security still lives on the server.\n",{"id":29,"difficulty":25,"q":30,"a":31},"require-auth-wrapper","How do you implement a RequireAuth wrapper component using React Router v6's Navigate?","In React Router v6 the `\u003CNavigate>` component performs a **declarative redirect**.\nA `RequireAuth` wrapper checks auth state and either renders `\u003COutlet \u002F>` (for\nlayout-route usage) or `children` (for direct wrapping).\n\n```jsx\nimport { Navigate, Outlet, useLocation } from 'react-router-dom';\nimport { useAuth } from '..\u002Fcontext\u002FAuthContext';\n\n\u002F\u002F Layout-route variant — works with nested \u003CRoute> trees\nexport function RequireAuth() {\n  const { user } = useAuth();\n  const location = useLocation(); \u002F\u002F capture current path\n\n  if (!user) {\n    \u002F\u002F Pass current location so login can redirect back after success\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n\n  return \u003COutlet \u002F>; \u002F\u002F render matched child route\n}\n```\n\nIn the router config, nest protected routes under `RequireAuth`:\n\n```jsx\n\u003CRoute element={\u003CRequireAuth \u002F>}>\n  \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n  \u003CRoute path=\"\u002Fsettings\" element={\u003CSettings \u002F>} \u002F>\n\u003C\u002FRoute>\n```\n\n**Rule of thumb:** Use the **layout-route** (`\u003COutlet \u002F>`) pattern rather than\nwrapping every `\u003CRoute element>` individually — one guard covers all children.\n",{"id":33,"difficulty":14,"q":34,"a":35},"preserve-intended-destination","How do you preserve the intended destination so users land on the right page after login?","When unauthenticated users hit a protected URL you pass the current **location** in\n`state` on the redirect. After a successful login you read `location.state.from` and\ncall `navigate()` to send them there instead of a hard-coded fallback.\n\n```jsx\n\u002F\u002F 1. Guard passes current location in redirect state\nfunction RequireAuth() {\n  const { user } = useAuth();\n  const location = useLocation();\n  if (!user) {\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n  return \u003COutlet \u002F>;\n}\n\n\u002F\u002F 2. Login page reads state.from after successful login\nfunction LoginPage() {\n  const { login } = useAuth();\n  const navigate = useNavigate();\n  const location = useLocation();\n  const from = location.state?.from?.pathname ?? '\u002Fdashboard'; \u002F\u002F fallback\n\n  async function handleSubmit(e) {\n    e.preventDefault();\n    await login(formData); \u002F\u002F authenticate\n    navigate(from, { replace: true }); \u002F\u002F go to original destination\n  }\n  \u002F\u002F ...render form\n}\n```\n\n**Rule of thumb:** Always supply a **fallback path** (`?? '\u002Fdashboard'`) — if the\nuser bookmarks `\u002Flogin` directly, `state` is undefined.\n",{"id":37,"difficulty":14,"q":38,"a":39},"rbac-route-protection","How do you implement role-based access control (RBAC) in React Router v6 routes?","Extend the `RequireAuth` pattern with an `allowedRoles` prop. The guard checks both\n**authentication** and **authorization** before rendering.\n\n```jsx\nimport { Navigate, Outlet, useLocation } from 'react-router-dom';\nimport { useAuth } from '..\u002Fcontext\u002FAuthContext';\n\n\u002F\u002F allowedRoles: string[] — e.g. ['admin', 'editor']\nfunction RequireRole({ allowedRoles }) {\n  const { user } = useAuth();\n  const location = useLocation();\n\n  if (!user) {\n    \u002F\u002F Not logged in → redirect to login\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n\n  if (!allowedRoles.includes(user.role)) {\n    \u002F\u002F Logged in but wrong role → show 403\n    return \u003CNavigate to=\"\u002F403\" replace \u002F>;\n  }\n\n  return \u003COutlet \u002F>;\n}\n\n\u002F\u002F Router config\n\u003CRoute element={\u003CRequireRole allowedRoles={['admin']} \u002F>}>\n  \u003CRoute path=\"\u002Fadmin\" element={\u003CAdminPanel \u002F>} \u002F>\n\u003C\u002FRoute>\n```\n\n**Rule of thumb:** Always separate the two checks — unauthenticated users should\nsee the **login page**, not a 403; authorized users with the wrong role should see\n**403**, not the login page.\n",{"id":41,"difficulty":14,"q":42,"a":43},"protecting-nested-routes","How do you protect an entire branch of nested routes with a single layout route guard?","React Router v6's **layout route** pattern (a `\u003CRoute>` with no `path` but with an\n`element`) lets you wrap a whole subtree. Combine this with `RequireAuth` to cover\nall descendants at once.\n\n```jsx\n\u003CRoutes>\n  {\u002F* Public routes *\u002F}\n  \u003CRoute path=\"\u002F\" element={\u003CHome \u002F>} \u002F>\n  \u003CRoute path=\"\u002Flogin\" element={\u003CLoginPage \u002F>} \u002F>\n\n  {\u002F* Protected subtree — single guard for all children *\u002F}\n  \u003CRoute element={\u003CRequireAuth \u002F>}>\n    \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n    \u003CRoute path=\"\u002Fprofile\" element={\u003CProfile \u002F>} \u002F>\n\n    {\u002F* Doubly-nested admin section with role check *\u002F}\n    \u003CRoute element={\u003CRequireRole allowedRoles={['admin']} \u002F>}>\n      \u003CRoute path=\"\u002Fadmin\" element={\u003CAdminPanel \u002F>} \u002F>\n      \u003CRoute path=\"\u002Fadmin\u002Fusers\" element={\u003CUserManager \u002F>} \u002F>\n    \u003C\u002FRoute>\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n```\n\n**Rule of thumb:** Think of layout routes as **middleware layers** — stack them to\ncompose auth + role checks without repeating logic on each leaf route.\n",{"id":45,"difficulty":14,"q":46,"a":47},"lazy-loading-protected-routes","How do you lazy-load protected route components with React.lazy and Suspense?","Use `React.lazy` to **code-split** heavy protected pages so their JS chunk is only\ndownloaded after the guard confirms auth. Wrap the lazy import in `\u003CSuspense>` inside\n(or outside) the `RequireAuth` element.\n\n```jsx\nimport React, { Suspense } from 'react';\nimport { Route, Routes } from 'react-router-dom';\nimport { RequireAuth } from '.\u002Fguards\u002FRequireAuth';\n\n\u002F\u002F Lazy imports — bundle splits here\nconst Dashboard = React.lazy(() => import('.\u002Fpages\u002FDashboard'));\nconst AdminPanel = React.lazy(() => import('.\u002Fpages\u002FAdminPanel'));\n\nfunction AppRoutes() {\n  return (\n    \u002F\u002F Suspense can live here to cover all lazy routes at once\n    \u003CSuspense fallback={\u003Cdiv>Loading…\u003C\u002Fdiv>}>\n      \u003CRoutes>\n        \u003CRoute element={\u003CRequireAuth \u002F>}>\n          \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n          \u003CRoute path=\"\u002Fadmin\" element={\u003CAdminPanel \u002F>} \u002F>\n        \u003C\u002FRoute>\n      \u003C\u002FRoutes>\n    \u003C\u002FSuspense>\n  );\n}\n```\n\n**Rule of thumb:** Place `\u003CSuspense>` **outside** `\u003CRoutes>` to handle all lazy\nroutes uniformly; only drop it inside a specific `\u003CRoute>` if you need per-page\nloading UI.\n",{"id":49,"difficulty":50,"q":51,"a":52},"token-storage-security","hard","Compare localStorage, cookies, and in-memory storage for auth tokens and their implications for routing guards.","The storage choice affects how the auth guard reads the token on each render and\nwhat attack surface you expose.\n\n| Storage | XSS risk | CSRF risk | Survives refresh | Notes |\n|---|---|---|---|---|\n| `localStorage` | High — JS-readable | None | Yes | Avoid for sensitive tokens |\n| `httpOnly` cookie | None — server-set | Medium | Yes | Best for session tokens |\n| Memory (React state) | Low | None | No | Requires silent-refresh |\n\n```jsx\n\u002F\u002F In-memory pattern — token lives only in AuthContext state\nconst AuthContext = createContext(null);\n\nexport function AuthProvider({ children }) {\n  const [user, setUser] = useState(null); \u002F\u002F cleared on tab close\n\n  \u002F\u002F On mount, attempt a silent refresh via httpOnly refresh-token cookie\n  useEffect(() => {\n    api.post('\u002Fauth\u002Frefresh')\n      .then(({ data }) => setUser(data.user)) \u002F\u002F restore session\n      .catch(() => setUser(null)); \u002F\u002F no valid cookie → stay logged out\n  }, []);\n\n  return \u003CAuthContext.Provider value={{ user, setUser }}>{children}\u003C\u002FAuthContext.Provider>;\n}\n```\n\n**Rule of thumb:** Store the **access token in memory**, set the **refresh token as\nan httpOnly cookie** — you get XSS-safety for the short-lived token and automatic\nsession persistence without `localStorage`.\n",{"id":54,"difficulty":14,"q":55,"a":56},"optimistic-auth-loading-state","What is the difference between optimistic auth and a loading-state guard, and which should you use?","On initial page load the auth context hasn't yet rehydrated (e.g., the silent-refresh\ncall is in-flight). Two strategies exist:\n\n**Optimistic auth** assumes the user *is* logged in until proven otherwise — renders\nthe protected page immediately, then potentially redirects. Can cause a **flash** of\nprotected content.\n\n**Loading-state guard** holds rendering until auth is confirmed. Prevents flashing\nbut shows a spinner on every cold load.\n\n```jsx\nfunction RequireAuth() {\n  const { user, loading } = useAuth();\n  const location = useLocation();\n\n  if (loading) {\n    \u002F\u002F Wait for silent-refresh before deciding\n    return \u003Cdiv className=\"spinner\" aria-label=\"Checking auth…\" \u002F>;\n  }\n\n  if (!user) {\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n\n  return \u003COutlet \u002F>;\n}\n```\n\n**Rule of thumb:** Always use the **loading-state guard** — a brief spinner is a\nfar better UX than flashing private data at unauthenticated users.\n",{"id":58,"difficulty":50,"q":59,"a":60},"token-expiry-interceptor","How do you handle token expiry mid-session without forcing the user to re-login?","Set up an **Axios\u002FFetch interceptor** that detects a 401 response, silently requests\na new access token using the refresh-token cookie, retries the original request, and\nonly redirects to login when the refresh itself fails.\n\n```jsx\n\u002F\u002F api.js — Axios instance with interceptor\nimport axios from 'axios';\n\nconst api = axios.create({ baseURL: '\u002Fapi', withCredentials: true });\n\nlet refreshPromise = null; \u002F\u002F prevent concurrent refresh calls\n\napi.interceptors.response.use(\n  res => res,\n  async error => {\n    const original = error.config;\n    if (error.response?.status === 401 && !original._retry) {\n      original._retry = true;\n      \u002F\u002F Deduplicate: reuse an in-flight refresh\n      refreshPromise = refreshPromise ?? api.post('\u002Fauth\u002Frefresh').finally(() => {\n        refreshPromise = null;\n      });\n      await refreshPromise;        \u002F\u002F wait for new token\n      return api(original);        \u002F\u002F retry original request\n    }\n    \u002F\u002F Refresh failed → redirect to login\n    window.location.href = '\u002Flogin';\n    return Promise.reject(error);\n  }\n);\n```\n\n**Rule of thumb:** Use a **single shared refresh promise** to prevent multiple\nparallel 401 responses from each triggering their own refresh race.\n",{"id":62,"difficulty":14,"q":63,"a":64},"auth-context-pattern","Describe the AuthContext + useAuth hook pattern and why it's preferred for protecting routes.","**AuthContext** stores auth state (user, loading, login, logout) in React context so\nany component in the tree — including route guards — can read it without prop-drilling.\nThe `useAuth` hook wraps `useContext` and throws early if used outside the provider.\n\n```jsx\n\u002F\u002F context\u002FAuthContext.jsx\nimport { createContext, useContext, useState, useEffect } from 'react';\n\nconst AuthContext = createContext(null);\n\nexport function AuthProvider({ children }) {\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    \u002F\u002F Restore session on mount\n    api.get('\u002Fauth\u002Fme')\n      .then(({ data }) => setUser(data))\n      .catch(() => setUser(null))\n      .finally(() => setLoading(false)); \u002F\u002F stop spinner\n  }, []);\n\n  const login = async (creds) => {\n    const { data } = await api.post('\u002Fauth\u002Flogin', creds);\n    setUser(data.user); \u002F\u002F update global state\n  };\n\n  const logout = async () => {\n    await api.post('\u002Fauth\u002Flogout');\n    setUser(null);\n  };\n\n  return (\n    \u003CAuthContext.Provider value={{ user, loading, login, logout }}>\n      {children}\n    \u003C\u002FAuthContext.Provider>\n  );\n}\n\n\u002F\u002F Enforces provider presence\nexport function useAuth() {\n  const ctx = useContext(AuthContext);\n  if (!ctx) throw new Error('useAuth must be used inside AuthProvider');\n  return ctx;\n}\n```\n\n**Rule of thumb:** Throw in `useAuth` when the context is null — it turns a silent\nwrong-render into an obvious developer error.\n",{"id":66,"difficulty":14,"q":67,"a":68},"route-level-vs-component-level","When should you use route-level guards versus component-level access checks?","**Route-level guards** (`RequireAuth` layout routes) handle coarse-grained access —\nthey prevent entire pages from rendering. **Component-level guards** handle\nfine-grained UI elements within a page (e.g., an \"Edit\" button visible only to\nadmins).\n\n```jsx\n\u002F\u002F Route-level guard — whole page blocked for non-admins\n\u003CRoute element={\u003CRequireRole allowedRoles={['admin']} \u002F>}>\n  \u003CRoute path=\"\u002Fadmin\" element={\u003CAdminDashboard \u002F>} \u002F>\n\u003C\u002FRoute>\n\n\u002F\u002F Component-level guard — same page, conditional UI\nfunction ArticlePage() {\n  const { user } = useAuth();\n  return (\n    \u003Carticle>\n      \u003Ch1>{article.title}\u003C\u002Fh1>\n      \u003Cp>{article.body}\u003C\u002Fp>\n      {\u002F* Only editors see the Edit button *\u002F}\n      {user?.role === 'editor' && (\n        \u003Cbutton onClick={handleEdit}>Edit\u003C\u002Fbutton>\n      )}\n    \u003C\u002Farticle>\n  );\n}\n```\n\n**Rule of thumb:** Use **route-level** guards for page access, **component-level**\nguards for UI elements — never rely on hiding UI elements as the sole security\nmeasure.\n",{"id":70,"difficulty":14,"q":71,"a":72},"testing-protected-routes","How do you test protected routes by mocking the auth context?","Render a custom wrapper that supplies a mock `AuthContext` value. This lets you test\nboth the authenticated and unauthenticated branches without a real auth server.\n\n```jsx\n\u002F\u002F test\u002Futils.jsx\nimport { render } from '@testing-library\u002Freact';\nimport { MemoryRouter } from 'react-router-dom';\nimport { AuthContext } from '..\u002Fcontext\u002FAuthContext';\n\nexport function renderWithAuth(ui, { user = null, route = '\u002F' } = {}) {\n  return render(\n    \u002F\u002F MemoryRouter lets us set the initial URL\n    \u003CMemoryRouter initialEntries={[route]}>\n      \u003CAuthContext.Provider value={{ user, loading: false }}>\n        {ui}\n      \u003C\u002FAuthContext.Provider>\n    \u003C\u002FMemoryRouter>\n  );\n}\n\n\u002F\u002F dashboard.test.jsx\nimport { screen } from '@testing-library\u002Freact';\nimport { Routes, Route } from 'react-router-dom';\nimport { RequireAuth } from '..\u002Fguards\u002FRequireAuth';\nimport { Dashboard } from '..\u002Fpages\u002FDashboard';\n\ntest('redirects to login when unauthenticated', () => {\n  renderWithAuth(\n    \u003CRoutes>\n      \u003CRoute path=\"\u002Flogin\" element={\u003Cp>Login page\u003C\u002Fp>} \u002F>\n      \u003CRoute element={\u003CRequireAuth \u002F>}>\n        \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n      \u003C\u002FRoute>\n    \u003C\u002FRoutes>,\n    { user: null, route: '\u002Fdashboard' } \u002F\u002F no user\n  );\n  expect(screen.getByText('Login page')).toBeInTheDocument();\n});\n```\n\n**Rule of thumb:** Use `MemoryRouter` (not `BrowserRouter`) in tests — you control\nthe initial URL without touching `window.location`.\n",{"id":74,"difficulty":50,"q":75,"a":76},"server-vs-client-protection","What are the trade-offs between client-side route protection and server-side protection?","**Client-side protection** (React Router guards) is UI-only — it prevents rendering\nthe component but the API endpoints are still reachable by anyone with network tools.\n**Server-side protection** validates the token on every API call and is the true\nsecurity boundary.\n\n```jsx\n\u002F\u002F Client-side guard — UX only, not a security boundary\nfunction RequireAuth() {\n  const { user } = useAuth();\n  if (!user) return \u003CNavigate to=\"\u002Flogin\" replace \u002F>;\n  return \u003COutlet \u002F>;\n}\n\n\u002F\u002F Server-side guard (Express example) — real security\nfunction authMiddleware(req, res, next) {\n  const token = req.cookies.accessToken;\n  try {\n    req.user = jwt.verify(token, process.env.JWT_SECRET); \u002F\u002F throws if invalid\n    next();\n  } catch {\n    res.status(401).json({ error: 'Unauthorized' }); \u002F\u002F API rejects bad token\n  }\n}\n\n\u002F\u002F Every protected API route uses the middleware\nrouter.get('\u002Fapi\u002Fdashboard-data', authMiddleware, getDashboardData);\n```\n\n**Rule of thumb:** Client-side guards protect **UX**; server-side guards protect\n**data** — you need both, and the server is the only one that matters for security.\n",{"id":78,"difficulty":25,"q":79,"a":80},"navigate-replace-vs-push","Why should you use replace={true} on the Navigate redirect in an auth guard?","Without `replace`, the redirect adds the protected URL to the **browser history\nstack**. After login the user presses Back, lands on the protected page URL in\nhistory, gets redirected again, and is stuck in a loop. Using `replace` **overwrites**\nthe history entry so the Back button goes to wherever they came from before the\nprotected URL.\n\n```jsx\nfunction RequireAuth() {\n  const { user } = useAuth();\n  const location = useLocation();\n\n  if (!user) {\n    return (\n      \u003CNavigate\n        to=\"\u002Flogin\"\n        state={{ from: location }}\n        replace   \u002F\u002F ← replaces instead of pushing — prevents back-button loop\n      \u002F>\n    );\n  }\n  return \u003COutlet \u002F>;\n}\n```\n\n**Rule of thumb:** Always pass `replace` on auth redirects to avoid **back-button\nloops** — it's a one-word fix with a significant UX impact.\n",{"id":82,"difficulty":25,"q":83,"a":84},"public-only-routes","How do you create a \"public-only\" route that redirects authenticated users away from the login page?","The inverse of `RequireAuth` — a **GuestOnly** guard redirects authenticated users\nwho try to visit `\u002Flogin` or `\u002Fregister` to the dashboard instead of showing them\na form they don't need.\n\n```jsx\nfunction GuestOnly() {\n  const { user, loading } = useAuth();\n  const location = useLocation();\n\n  if (loading) return \u003Cdiv>Loading…\u003C\u002Fdiv>;\n\n  if (user) {\n    \u002F\u002F Authenticated user tried to reach \u002Flogin — send them home\n    const destination = location.state?.from?.pathname ?? '\u002Fdashboard';\n    return \u003CNavigate to={destination} replace \u002F>;\n  }\n\n  return \u003COutlet \u002F>; \u002F\u002F not logged in — show login\u002Fregister\n}\n\n\u002F\u002F Router config\n\u003CRoute element={\u003CGuestOnly \u002F>}>\n  \u003CRoute path=\"\u002Flogin\" element={\u003CLoginPage \u002F>} \u002F>\n  \u003CRoute path=\"\u002Fregister\" element={\u003CRegisterPage \u002F>} \u002F>\n\u003C\u002FRoute>\n```\n\n**Rule of thumb:** Pair every `RequireAuth` guard with a `GuestOnly` guard — without\nit, logged-in users see the login form and get confused.\n",{"id":86,"difficulty":14,"q":87,"a":88},"persisting-auth-across-refresh","How do you persist authentication state across a full page refresh in a React SPA?","On a hard refresh React state is wiped. To restore auth you have two options:\n(1) read a token from `localStorage` synchronously on mount, or (2) fire a\n**silent-refresh** API call on mount using an httpOnly refresh-token cookie.\nOption 2 is more secure.\n\n```jsx\nexport function AuthProvider({ children }) {\n  const [user, setUser] = useState(null);\n  const [loading, setLoading] = useState(true); \u002F\u002F true until we know\n\n  useEffect(() => {\n    \u002F\u002F Silent refresh — relies on httpOnly cookie sent automatically\n    api.post('\u002Fauth\u002Frefresh')\n      .then(({ data }) => {\n        setUser(data.user);      \u002F\u002F restore user object\n      })\n      .catch(() => {\n        setUser(null);           \u002F\u002F no valid cookie → stay logged out\n      })\n      .finally(() => {\n        setLoading(false);       \u002F\u002F guards can now make a decision\n      });\n  }, []); \u002F\u002F run once on mount\n\n  return (\n    \u003CAuthContext.Provider value={{ user, loading }}>\n      {children}\n    \u003C\u002FAuthContext.Provider>\n  );\n}\n```\n\n**Rule of thumb:** Keep `loading: true` until the refresh attempt resolves — every\nguard in the app blocks on this flag, preventing a premature redirect to `\u002Flogin`.\n",{"id":90,"difficulty":50,"q":91,"a":92},"multiple-guards-composition","How do you compose multiple guards (auth + subscription + feature flag) on a single route?","Nest layout routes — each wraps the next, and all must pass before the leaf renders.\nThis keeps each guard **single-responsibility** and independently testable.\n\n```jsx\n\u002F\u002F Each guard does one job and delegates via \u003COutlet \u002F>\nfunction RequireSubscription() {\n  const { user } = useAuth();\n  if (!user?.subscription?.active) {\n    return \u003CNavigate to=\"\u002Fupgrade\" replace \u002F>;\n  }\n  return \u003COutlet \u002F>;\n}\n\nfunction RequireFeatureFlag({ flag }) {\n  const { flags } = useFeatureFlags();\n  if (!flags[flag]) {\n    return \u003CNavigate to=\"\u002Fcoming-soon\" replace \u002F>;\n  }\n  return \u003COutlet \u002F>;\n}\n\n\u002F\u002F Router — guards stack innermost-last\n\u003CRoute element={\u003CRequireAuth \u002F>}>             {\u002F* 1st: auth *\u002F}\n  \u003CRoute element={\u003CRequireSubscription \u002F>}>   {\u002F* 2nd: paid plan *\u002F}\n    \u003CRoute element={\u003CRequireFeatureFlag flag=\"ai-tools\" \u002F>}> {\u002F* 3rd: flag *\u002F}\n      \u003CRoute path=\"\u002Fai-tools\" element={\u003CAiTools \u002F>} \u002F>\n    \u003C\u002FRoute>\n  \u003C\u002FRoute>\n\u003C\u002FRoute>\n```\n\n**Rule of thumb:** Compose guards by **nesting layout routes** rather than combining\nlogic in one mega-guard — each layer stays readable and individually unit-testable.\n",{"id":94,"difficulty":14,"q":95,"a":96},"redirect-loop-debugging","What causes an infinite redirect loop in protected routes and how do you fix it?","A redirect loop happens when the guard sends the user to `\u002Flogin`, but `\u002Flogin` is\nalso behind the guard (or the auth state never resolves to `true`), so the guard\nredirects again immediately.\n\nCommon causes and fixes:\n\n```jsx\n\u002F\u002F BUG: \u002Flogin is inside RequireAuth — redirects loop forever\n\u003CRoute element={\u003CRequireAuth \u002F>}>\n  \u003CRoute path=\"\u002Flogin\" element={\u003CLoginPage \u002F>} \u002F> {\u002F* ← wrong! *\u002F}\n  \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F>\n\u003C\u002FRoute>\n\n\u002F\u002F FIX: \u002Flogin must be outside the guard\n\u003CRoutes>\n  \u003CRoute path=\"\u002Flogin\" element={\u003CLoginPage \u002F>} \u002F>  {\u002F* public *\u002F}\n\n  \u003CRoute element={\u003CRequireAuth \u002F>}>\n    \u003CRoute path=\"\u002Fdashboard\" element={\u003CDashboard \u002F>} \u002F> {\u002F* protected *\u002F}\n  \u003C\u002FRoute>\n\u003C\u002FRoutes>\n\n\u002F\u002F BUG 2: guard doesn't wait for loading — user is null on first render\n\u002F\u002F even when a valid refresh cookie exists → immediate redirect to login\nfunction RequireAuth() {\n  const { user, loading } = useAuth();\n  if (loading) return null; \u002F\u002F ← wait before deciding\n  if (!user) return \u003CNavigate to=\"\u002Flogin\" replace \u002F>;\n  return \u003COutlet \u002F>;\n}\n```\n\n**Rule of thumb:** If you see a redirect loop, check two things: is the redirect\n**target outside the guard**, and is the guard **waiting for the loading flag**?\n",{"id":98,"difficulty":25,"q":99,"a":100},"useNavigate-vs-navigate-component","When should you use the Navigate component versus the useNavigate hook in auth flows?","`\u003CNavigate>` is a **declarative** redirect that happens during render — ideal for\nguards that decide synchronously (user is or isn't logged in right now).\n`useNavigate()` returns an **imperative** `navigate()` function — ideal for\nredirecting *after* an async operation (login form submit, token refresh).\n\n```jsx\n\u002F\u002F Declarative — fires during render, perfect for guards\nfunction RequireAuth() {\n  const { user } = useAuth();\n  if (!user) return \u003CNavigate to=\"\u002Flogin\" replace \u002F>; \u002F\u002F renders a redirect\n  return \u003COutlet \u002F>;\n}\n\n\u002F\u002F Imperative — fires after async logic, perfect for post-login redirect\nfunction LoginPage() {\n  const { login } = useAuth();\n  const navigate = useNavigate();\n  const location = useLocation();\n  const from = location.state?.from?.pathname ?? '\u002Fdashboard';\n\n  async function handleSubmit(credentials) {\n    await login(credentials);       \u002F\u002F async: wait for server response\n    navigate(from, { replace: true }); \u002F\u002F then redirect\n  }\n}\n```\n\n**Rule of thumb:** Use `\u003CNavigate>` in **guards** (render-time decision), use\n`useNavigate` in **event handlers** (post-async decision).\n",{"id":102,"difficulty":14,"q":103,"a":104},"404-vs-403-handling","How should a protected-route guard distinguish between a 404 (route not found) and a 403 (forbidden)?","A **404** means the URL doesn't match any route. A **403** means the URL matched but\nthe user lacks permission. Conflating them leaks information (you reveal the route\nexists to unauthorized users). In practice, both are acceptable — many apps return\na 404 for unauthorized routes to avoid enumeration.\n\n```jsx\nfunction RequireRole({ allowedRoles }) {\n  const { user } = useAuth();\n  const location = useLocation();\n\n  if (!user) {\n    \u002F\u002F Not logged in → login (don't reveal the page exists)\n    return \u003CNavigate to=\"\u002Flogin\" state={{ from: location }} replace \u002F>;\n  }\n\n  if (!allowedRoles.includes(user.role)) {\n    \u002F\u002F Logged in, wrong role — choose 403 or 404 based on policy\n    return \u003CNavigate to=\"\u002F403\" replace \u002F>; \u002F\u002F explicit \"forbidden\" page\n    \u002F\u002F OR: return \u003CNavigate to=\"\u002F404\" replace \u002F>; \u002F\u002F stealth — hides route existence\n  }\n\n  return \u003COutlet \u002F>;\n}\n\n\u002F\u002F Catch-all 404 must be the last route in the tree\n\u003CRoute path=\"*\" element={\u003CNotFoundPage \u002F>} \u002F>\n```\n\n**Rule of thumb:** Send unauthenticated users to **login**, send authenticated but\nunauthorized users to **403** (or 404 for sensitive admin routes) — keep the\ndistinction deliberate and documented.\n",20,null,{"description":11},"React Router v6 protected routes interview questions — RequireAuth wrapper, Navigate redirect, role-based access, auth context, token storage, and redirect-after-login patterns.","react\u002Frouting\u002Fprotected-routes","Routing","routing","2026-06-24","WBfNu4OmQJrFDE-JcwSQzTUWre5iGF2lnnAP77GmAOE",[115,119,122,126],{"subtopic":116,"path":117,"order":118},"Routing Basics","\u002Freact\u002Frouting\u002Frouting-basics",1,{"subtopic":120,"path":121,"order":12},"Dynamic and Nested Routes","\u002Freact\u002Frouting\u002Fdynamic-nested-routes",{"subtopic":123,"path":124,"order":125},"Navigation Hooks","\u002Freact\u002Frouting\u002Fnavigation-hooks",3,{"subtopic":6,"path":21,"order":20},{"path":128,"title":129},"\u002Fblog\u002Freact-protected-routes-guide","React Router v6 Protected Routes — Complete Interview Guide",1782244101054]