Learn how to troubleshoot and fix the common Next.js error "Uncaught Error: invariant expected app router to be mounted" with our comprehensive guide.
The error "invariant expected app router to be mounted" is a common issue in Next.js applications when using the useRouter
hook from next/navigation
incorrectly. This error occurs when you try to access the Next.js router outside of a Next.js routing environment, such as in data fetching functions or during component tests. This introduction will explain the error, its common causes, and how to resolve it.
The error "invariant expected app router to be mounted" commonly occurs in Next.js applications when using the useRouter
hook from next/navigation
outside the context of a Next.js routing environment. Here's a breakdown of the issue and how to resolve it:
Understanding the Error
The useRouter
hook provides access to the Next.js router object, which manages navigation and route information within your application. This hook is designed to work within the lifecycle of Next.js components that are rendered as part of your application's routing structure.
When you encounter the "invariant expected app router to be mounted" error, it means you're attempting to use useRouter
in a component or context where the Next.js router hasn't been properly initialized.
Common Causes and Solutions
Using useRouter
Outside of Components:
Problem: You might be trying to call useRouter
directly within a data fetching function (getServerSideProps
, getStaticProps
, or getStaticPaths
) or outside the scope of a React component.
Solution: These functions run on the server during build time or on each request, where the client-side router isn't available. Instead of directly using useRouter
, pass route parameters or query strings as props to your components.
// pages/product/[id].js
export async function getStaticProps({ params }) {
const { id } = params;
// ... fetch product data using 'id'
return { props: { productData } };
}
function ProductPage({ productData }) {
// Access product details from props
}
Testing Components with useRouter
:
Problem: When unit testing components that rely on useRouter
, your testing environment might not have the Next.js router set up.
Solution: Mock the useRouter
hook within your tests to provide a controlled environment.
// __tests__/MyComponent.test.js
import { render } from '@testing-library/react';
import MyComponent from '../components/MyComponent';
jest.mock('next/navigation', () => ({
useRouter: () => ({
push: jest.fn(),
// ... other router methods you use
}),
}));
test('MyComponent navigates correctly', () => {
// ... your test logic
});
Storybook Integration:
Problem: Similar to testing, Storybook might not have the Next.js router context.
Solution: Configure Storybook to work with Next.js using addons like @storybook/addon-actions
or @storybook/addon-links
to simulate routing behavior.
// .storybook/main.js
module.exports = {
addons: [
// ... other addons
'@storybook/addon-actions',
'@storybook/addon-links',
],
};
Redux Persist or Similar Libraries:
Problem: Libraries like Redux Persist might try to access the router during their initialization, even before the Next.js app is fully mounted.
Solution: Wrap your Redux store initialization or the component that uses useRouter
with a check to ensure the router is ready.
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
function MyComponent() {
const router = useRouter();
const [isRouterReady, setIsRouterReady] = useState(false);
useEffect(() => {
setIsRouterReady(true);
}, []);
if (!isRouterReady) {
return null; // Or a loading indicator
}
// ... rest of your component logic
}
Debugging Tips
useRouter
is a descendant of your Next.js application's root component (usually within the pages
directory or a custom app
directory).useRouter
is being called prematurely.By carefully considering where and how you're using useRouter
, you can effectively resolve this error and ensure your Next.js application functions correctly.
This JavaScript code demonstrates how to handle the useRouter
hook from Next.js in different scenarios, including server-side rendering, testing, Storybook integration, and usage with Redux Persist. It provides solutions for common issues like trying to access the router outside of components and offers workarounds for testing and mocking. The code showcases how to pass route data as props, mock the useRouter
hook with Jest, simulate routing in Storybook, and delay rendering until the router is ready when using Redux Persist.
// Example of the error and solutions
// 1. Using useRouter outside of components (INCORRECT)
// export async function getServerSideProps() {
// const router = useRouter(); // ERROR: useRouter is not available here
// return { props: {} };
// }
// Solution: Pass route data as props
export async function getServerSideProps({ params }) {
const { id } = params;
// ... fetch data using 'id'
return { props: { productData } };
}
// 2. Testing components with useRouter (MOCKING)
import { render } from '@testing-library/react';
import MyComponent from '../components/MyComponent';
import { useRouter } from 'next/navigation'; // Correct import
jest.mock('next/navigation'); // Mock the entire module
test('MyComponent navigates correctly', () => {
useRouter.mockReturnValue({
push: jest.fn(),
});
render(<MyComponent />);
// ... test navigation logic
});
// 3. Storybook integration (using addon-actions for example)
// In your component:
import { useRouter } from 'next/navigation';
const MyComponent = () => {
const router = useRouter();
return (
<button onClick={() => router.push('/about')}>Go to About</button>
);
};
// In your Storybook story:
import { action } from '@storybook/addon-actions';
export default {
title: 'Components/MyComponent',
component: MyComponent,
};
const Template = (args) => <MyComponent {...args} />;
export const Default = Template.bind({});
Default.args = {
onClick: action('Button clicked'), // Simulate routing with action
};
// 4. Redux Persist and similar libraries
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import store from '../store'; // Your Redux store
function MyApp({ Component, pageProps }) {
const router = useRouter();
const [isRouterReady, setIsRouterReady] = useState(false);
useEffect(() => {
setIsRouterReady(true);
}, []);
// Only render PersistGate when the router is ready
return (
<Provider store={store}>
{isRouterReady ? (
<PersistGate persistor={persistStore(store)}>
<Component {...pageProps} />
</PersistGate>
) : null}
</Provider>
);
}
Explanation:
useRouter
in getServerSideProps
and instead pass data as props.useRouter
in a test environment using jest.mock
.addon-actions
to simulate routing behavior without a real router.useRouter
with Redux Persist by delaying the rendering of components that rely on the router until it's fully initialized.Remember to adapt these examples to your specific use case and component structure.
Importance of Context: The core concept is that useRouter
needs to be used within the client-side lifecycle of your Next.js application. It's not available during server-side operations.
Data Fetching Alternatives:
[id].js
) and access the id
from params
in getStaticProps
or getServerSideProps
.router.query
within your component.Mocking Best Practices: When mocking useRouter
in tests, focus on mocking only the specific methods you're using in the component under test. This makes your tests more focused and less brittle.
Storybook and Routing: While addons like addon-actions
can simulate routing interactions, for more complex scenarios, consider setting up Storybook with a Next.js environment for a more realistic experience.
Redux Persist Timing: The key with Redux Persist is to ensure that it doesn't attempt to access the router before it's available. The isRouterReady
pattern is one approach, but you might need to explore other lifecycle methods or library-specific options depending on your setup.
Error Prevention: A good understanding of the Next.js lifecycle (server-side vs. client-side) and the purpose of useRouter
is crucial for preventing this error in the first place.
App Router in Next.js 13: The introduction of the app
directory in Next.js 13 might introduce subtle differences in how routing works. Always refer to the latest Next.js documentation for the most up-to-date information.
This table summarizes the "invariant expected app router to be mounted" error in Next.js, which occurs when using the useRouter
hook outside a Next.js routing environment:
Cause | Problem | Solution |
---|---|---|
Using useRouter outside components |
Calling useRouter in data fetching functions or outside React components. |
Pass route data as props to components instead. |
Testing components with useRouter |
Testing environment lacks the Next.js router setup. | Mock the useRouter hook in your tests. |
Storybook integration | Storybook might not have the Next.js router context. | Configure Storybook with addons like @storybook/addon-actions or @storybook/addon-links . |
Redux Persist or similar libraries | Libraries accessing the router before the Next.js app is fully mounted. | Wrap your store initialization or component using useRouter with a check to ensure the router is ready. |
Debugging Tips:
useRouter
is used within the Next.js app's routing structure.useRouter
calls.By understanding the root cause of this error—attempting to use a client-side navigation hook in a server-side context—developers can apply the appropriate solution. Whether it's passing data as props, mocking the hook in test environments, utilizing Storybook addons, or managing the timing of library integrations, the solutions revolve around ensuring that useRouter
is accessed within the client-side lifecycle of a Next.js application. By adhering to these practices and understanding the interplay between server-side and client-side rendering in Next.js, developers can create robust and error-free applications.
Uncaught Error: invariant expected app router to be mounted
when ... | Link to the code that reproduces this issue https://github.com/WMLondo/template-app.git To Reproduce Check the README file with all the needed to the project works. Go to /auth/login and check the ...NextRouter
was not mounted | Next.js | NextRouter
was not mounted. Why This Error Occurred. A component used useRouter outside a Next.js application, or was rendered outside a Next.js ...