How to Fix React Router v6 'useNavigate() may be used only in the context of a <Router>'
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: A component invoking
useNavigate()is rendering outside the<BrowserRouter>(or equivalent) provider, so React Router's context isundefinedat call time. - How to fix it: Ensure
<BrowserRouter>(or<MemoryRouter>/<HashRouter>) wraps every component that consumes any React Router hook — typically atmain.jsx/index.jsroot level. - Fast path: Use our Client-Side Sandbox above to paste your component tree and auto-refactor the wrapping structure without sending your code to a third-party server.
The Incident (What Does the Error Mean?)
Raw runtime error:
Error: useNavigate() may be used only in the context of a <Router> component.
at invariant (react-router/dist/index.js)
at useNavigate (react-router/dist/index.js)
at YourComponent (YourComponent.jsx:12)
React Router v6 hooks (useNavigate, useLocation, useParams, etc.) read from a React Context object that is only populated when a <Router> sits above the calling component in the tree. The moment that context is undefined, the internal invariant() guard throws synchronously — before any JSX renders — taking down the entire subtree with it. In production, this surfaces as a white screen with no fallback unless you have an <ErrorBoundary> in place.
The Attack Vector / Blast Radius
This is not a security vulnerability, but the blast radius is a full application crash. Common triggers that bite senior engineers in production:
- Lazy-loaded micro-frontends — a remotely loaded module mounts a component that calls
useNavigate()but the host shell's<BrowserRouter>is not in scope for the remote's React instance (especially with Module Federation and duplicate React Router installs). - Testing environments — unit tests rendering a component in isolation without wrapping it in
<MemoryRouter>. Works in dev (becauseAppis wrapped), explodes in CI. - Portal-rendered components —
ReactDOM.createPortal()targets a DOM node outside the React tree root, severing the context chain. - Utility/helper files calling hooks at module scope — calling
useNavigate()outside a functional component body (e.g., inside a plainnavigateexport) violates the Rules of Hooks and drops the context. - Duplicate
react-router-domversions — two different semver installs innode_modules(common in monorepos) mean the<Router>from v1 populates a different context object than theuseNavigatefrom v2 reads.npm ls react-router-domis your first diagnostic command.
How to Fix It
Basic Fix — Wrap at the Root
The most common cause: <App /> is not wrapped in <BrowserRouter> in your entry point.
// main.jsx (Vite) or index.js (CRA)
import React from 'react';
import ReactDOM from 'react-dom/client';
+ import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
- <App />
+ <BrowserRouter>
+ <App />
+ </BrowserRouter>
</React.StrictMode>
);
Enterprise Best Practice — Centralized Router Ownership + Test Utility
In large codebases, never let individual feature teams decide where the Router lives. Enforce a single RouterProvider at the shell level and expose a test wrapper.
// src/router/index.tsx — single source of truth
- // Router was scattered: some features used BrowserRouter locally
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
+ import { routes } from './routes';
+
+ export const router = createBrowserRouter(routes);
+
+ export function AppRouter() {
+ return <RouterProvider router={router} />;
+ }
// src/test-utils/renderWithRouter.tsx — use in ALL unit tests
- // Tests called render(<MyComponent />) with no Router wrapper
+ import { render } from '@testing-library/react';
+ import { MemoryRouter } from 'react-router-dom';
+
+ export function renderWithRouter(ui: React.ReactElement, { initialEntries = ['/'] } = {}) {
+ return render(
+ <MemoryRouter initialEntries={initialEntries}>
+ {ui}
+ </MemoryRouter>
+ );
+ }
// For Module Federation — deduplicate react-router-dom in webpack config
// webpack.config.js (host)
shared: {
+ 'react-router-dom': {
+ singleton: true,
+ requiredVersion: deps['react-router-dom'],
+ },
}
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your component trees, route configs, and internal path structures. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing component or
main.jsxinto the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
Stop this class of error from reaching production with these gates:
1. ESLint — eslint-plugin-react-hooks
Enforces Rules of Hooks statically. Catches useNavigate() called outside a component body at lint time.
npm install --save-dev eslint-plugin-react-hooks
// .eslintrc.json
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
2. Enforce renderWithRouter in Test Suite via Custom ESLint Rule or Jest setupFilesAfterFramework
Write a simple ESLint rule (or use eslint-plugin-testing-library) that errors if a component using useNavigate is rendered without a Router wrapper in test files.
3. Monorepo — Deduplicate Check in CI
# .github/workflows/ci.yml
- name: Check for duplicate react-router-dom
run: |
COUNT=$(npm ls react-router-dom 2>/dev/null | grep -c 'react-router-dom@')
if [ "$COUNT" -gt 1 ]; then
echo "FATAL: Multiple react-router-dom versions detected. Fix hoisting."
exit 1
fi
4. Playwright / Cypress Smoke Test
A single E2E test that boots the app and asserts no console.error containing useNavigate fires on load. Catches misconfigured lazy routes before they hit users.
// cypress/e2e/smoke.cy.js
it('boots without Router context errors', () => {
cy.on('window:console', (msg) => {
if (msg.type === 'error') {
expect(msg.message).not.to.include('useNavigate');
}
});
cy.visit('/');
});