Skip to content

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:

  1. "NextRouter was not mounted": This occurs when importing from 'next/router' in App Router components
  2. Type error with router.push: When using 'next/navigation', passing an object argument to router.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:

FeaturePages Router (pages/)App Router (app/)
useRouter importnext/routernext/navigation
pathname accessrouter.pathnameusePathname() hook
Query parametersrouter.queryuseSearchParams() hook
router.pushAccepts objectsRequires URL strings only
Server componentsNot supportedFully 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:

jsx
// 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:

jsx
// 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:

jsx
const params = new URLSearchParams({ search: searchInput });
router.push(`/search?${params.toString()}`);

3. Accessing Path and Query Parameters

Use dedicated hooks for pathname and queries:

jsx
'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:

jsx
// 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:

jsx
'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:

  1. Replace next/router imports with next/navigation in app/
  2. Convert all router.push() object arguments to URL strings
  3. Replace router.pathname with usePathname() hook
  4. Replace router.query with useSearchParams() hook
  5. Add 'use client' directive to components using navigation hooks
  6. Update tests with proper router mocks

Important Behavior Changes

  1. The App Router's useRouter is isomorphic, meaning it works identically in SSR and CSR environments
  2. Query parameters are now serialized as strings automatically
  3. router.replace() now clears browser history differently

Testing Strategies Comparison

tsx
// 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');
});
jsx
// __mocks__/next/navigation.js
module.exports = {
  useRouter: () => ({
    push: jest.fn(),
    replace: jest.fn(),
    refresh: jest.fn()
  }),
  usePathname: () => '/',
  useSearchParams: () => new URLSearchParams()
};
  1. Next.js Navigation Documentation
  2. Upgrade Guide: Pages to App Router
  3. next/navigation API Reference

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.