Initializing Enclave...

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 is undefined at call time.
  • How to fix it: Ensure <BrowserRouter> (or <MemoryRouter> / <HashRouter>) wraps every component that consumes any React Router hook — typically at main.jsx / index.js root 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:

  1. 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).
  2. Testing environments — unit tests rendering a component in isolation without wrapping it in <MemoryRouter>. Works in dev (because App is wrapped), explodes in CI.
  3. Portal-rendered componentsReactDOM.createPortal() targets a DOM node outside the React tree root, severing the context chain.
  4. Utility/helper files calling hooks at module scope — calling useNavigate() outside a functional component body (e.g., inside a plain navigate export) violates the Rules of Hooks and drops the context.
  5. Duplicate react-router-dom versions — two different semver installs in node_modules (common in monorepos) mean the <Router> from v1 populates a different context object than the useNavigate from v2 reads. npm ls react-router-dom is 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.jsx into 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('/');
});

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →