useRouter in Next.js App Directory
Problem Statement
When using Next.js, developers migrating to the App Router (introduced in Next 13) often encounter two related errors related to useRouter
:
- "NextRouter was not mounted": This occurs when importing from
'next/router'
in App Router components - Type error with router.push: When using
'next/navigation'
, passing an object argument torouter.push()
fails with:bash"Argument of type '{ pathname: string; query: { search: string; }; }' is not assignable to parameter of type 'string'"
These errors stem from fundamental differences between the pages directory routing system and the new App Router architecture introduced in Next.js 13.
Core Differences: Pages vs App Router
Next.js 13+ introduces a new routing paradigm with significant changes:
Feature | Pages Router (pages/ ) | App Router (app/ ) |
---|---|---|
useRouter import | next/router | next/navigation |
pathname access | router.pathname | usePathname() hook |
Query parameters | router.query | useSearchParams() hook |
router.push | Accepts objects | Requires URL strings only |
Server components | Not supported | Fully supported |
Common Mistake
Imports from next/router
only work in pages/
components
Imports from next/navigation
only work in app/
components
Solutions
1. Correct Import Syntax
First ensure proper import paths for your directory structure:
// Inside app directory:
'use client'; // Required for client components
// Correct import for App Router
import { useRouter } from 'next/navigation';
// Inside pages directory:
// Traditional import for Pages Router
import { useRouter } from 'next/router';
2. Migrating router.push
Instead of object arguments, manually build URL strings:
// Before (works in pages directory only)
router.push({
pathname: '/search',
query: { search: searchInput }
});
// After (app directory)
router.push(`/search?search=${encodeURIComponent(searchInput)}`);
Best Practice
Use URLSearchParams
for proper URL encoding:
const params = new URLSearchParams({ search: searchInput });
router.push(`/search?${params.toString()}`);
3. Accessing Path and Query Parameters
Use dedicated hooks for pathname and queries:
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
function MyComponent() {
const pathname = usePathname(); // e.g. "/dashboard/users"
const searchParams = useSearchParams();
const searchTerm = searchParams.get('search');
return (
<div>
Current path: {pathname}
{searchTerm && <p>Searching for: {searchTerm}</p>}
</div>
);
}
4. Testing Components Using Router
Mock the router context in unit tests:
// test-utils/mock-router.js
export const mockRouter = {
push: jest.fn(),
replace: jest.fn(),
// Add other router methods as needed
};
// Component.test.jsx [JavaScript]
import { render, screen } from '@testing-library/react';
import { AppRouterContext } from 'next/dist/shared/lib/app-router-context';
import Component from './Component';
test('navigates on click', () => {
const router = mockRouter;
render(
<AppRouterContext.Provider value={router}>
<Component />
</AppRouterContext.Provider>
);
fireEvent.click(screen.getByText('Search'));
expect(router.push).toHaveBeenCalledWith('/search?q=test');
});
5. Special Navigation Techniques
For enhanced navigation control:
'use client';
import { useRouter } from 'next/navigation';
function LogoutButton() {
const router = useRouter();
const handleLogout = () => {
logoutUser();
// Refresh current route without page reload
router.refresh();
router.replace('/login');
};
return (
<button onClick={handleLogout}>Sign out</button>
);
}
Migration Roadmap
Follow this process when migrating from pages to app directory:
- Replace
next/router
imports withnext/navigation
inapp/
- Convert all
router.push()
object arguments to URL strings - Replace
router.pathname
withusePathname()
hook - Replace
router.query
withuseSearchParams()
hook - Add
'use client'
directive to components using navigation hooks - Update tests with proper router mocks
Important Behavior Changes
- The App Router's
useRouter
isisomorphic
, meaning it works identically in SSR and CSR environments - Query parameters are now serialized as strings automatically
router.replace()
now clears browser history differently
Testing Strategies Comparison
// Component.test.ts
import mockRouter from 'next-router-mock';
test('navigates on click', () => {
const { getByText } = render(<Component />,
{ wrapper: ({ children }) => (
<AppRouterContext.Provider value={mockRouter}>
{children}
</AppRouterContext.Provider>
)}
);
fireEvent.click(getByText('Go'));
expect(mockRouter.asPath).toEqual('/new-page');
});
// __mocks__/next/navigation.js
module.exports = {
useRouter: () => ({
push: jest.fn(),
replace: jest.fn(),
refresh: jest.fn()
}),
usePathname: () => '/',
useSearchParams: () => new URLSearchParams()
};
Recommended Resources
By understanding these distinctions and following migration best practices, you can effectively navigate Next.js routing in the App Router architecture while avoiding common "router not mounted" errors.