No articles found
Try different keywords or browse our categories
Fix: useRouter only works in Client Components Error
Learn how to fix the 'useRouter only works in Client Components' error in Next.js applications. This comprehensive guide covers client components, server components, and proper routing implementation.
The ‘useRouter only works in Client Components’ error is a common Next.js issue that occurs when trying to use the useRouter hook in Server Components or when the component hasn’t been designated as a Client Component. This error typically happens when developers attempt to use client-side routing hooks in server-rendered components, which is not allowed in Next.js 13+ with the App Router. The error prevents navigation functionality and can break the user experience.
This comprehensive guide explains what causes this error, why it happens, and provides multiple solutions to fix it in your Next.js projects with clean code examples and directory structure.
What is the useRouter only works in Client Components Error?
The “useRouter only works in Client Components” error occurs when:
- Using
useRouterhook in a Server Component without'use client'directive - Attempting to use client-side routing hooks in server-rendered components
- Importing
useRouterin components that run on the server - Using Next.js routing hooks in Server Actions or Server Components
- Mixing client and server component patterns incorrectly
Common Error Messages:
Error: useRouter only works in Client ComponentsError: useSearchParams only works in Client ComponentsError: You're importing a Client Component into a Server ComponentModule parse failed: Unexpected token
Understanding the Problem
Next.js 13+ introduced the App Router with a clear distinction between Server Components and Client Components. Server Components run on the server and can’t access browser APIs or hooks like useRouter, while Client Components run in the browser and have access to these features. The error occurs when there’s confusion between these two component types and their respective capabilities.
Typical Next.js Project Structure:
my-next-app/
├── package.json
├── next.config.js
├── app/
│ ├── layout.js
│ ├── page.js
│ ├── globals.css
│ ├── components/
│ │ ├── Header.jsx
│ │ ├── Navigation.jsx
│ │ └── Footer.jsx
│ ├── dashboard/
│ │ ├── page.js
│ │ └── client-component.js
│ └── api/
│ └── route.js
├── lib/
│ └── utils.js
└── public/
Solution 1: Add ‘use client’ Directive
The most common solution is to add the 'use client' directive at the top of your component file to designate it as a Client Component.
❌ Without ‘use client’ Directive:
// components/Navigation.jsx - ❌ Missing 'use client' directive
import { useRouter } from 'next/navigation';
export default function Navigation() {
const router = useRouter(); // ❌ This will cause an error
const handleNavigation = () => {
router.push('/dashboard'); // ❌ Error: useRouter only works in Client Components
};
return (
<nav>
<button onClick={handleNavigation}>Go to Dashboard</button>
</nav>
);
}
✅ With ‘use client’ Directive:
components/Navigation.jsx:
'use client'; // ✅ Add 'use client' directive at the top
import { useRouter } from 'next/navigation';
import { useState } from 'react';
export default function Navigation() {
const router = useRouter(); // ✅ Now this works correctly
const [isLoading, setIsLoading] = useState(false);
const handleNavigation = async () => {
setIsLoading(true);
try {
// ✅ Navigate to dashboard
router.push('/dashboard');
} catch (error) {
console.error('Navigation error:', error);
} finally {
setIsLoading(false);
}
};
const handleReplace = () => {
// ✅ Replace current route
router.replace('/dashboard');
};
const handleBack = () => {
// ✅ Go back
router.back();
};
const handleForward = () => {
// ✅ Go forward
router.forward();
};
return (
<nav className="bg-blue-500 p-4">
<div className="flex space-x-4">
<button
onClick={handleNavigation}
disabled={isLoading}
className={`px-4 py-2 rounded ${isLoading ? 'bg-gray-400' : 'bg-white text-blue-500'} hover:bg-blue-100`}
>
{isLoading ? 'Loading...' : 'Go to Dashboard'}
</button>
<button
onClick={handleReplace}
className="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
>
Replace Route
</button>
<button
onClick={handleBack}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Back
</button>
<button
onClick={handleForward}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
>
Forward
</button>
</div>
</nav>
);
}
Solution 2: Pass Router Props from Parent Client Component
Pass navigation functions as props from a Client Component to Server Components.
components/ClientWrapper.jsx:
'use client'; // ✅ Client Component wrapper
import { useRouter } from 'next/navigation';
import ServerComponent from './ServerComponent';
export default function ClientWrapper() {
const router = useRouter();
// ✅ Pass navigation functions as props
return <ServerComponent navigateToDashboard={() => router.push('/dashboard')} />;
}
components/ServerComponent.jsx:
// ✅ Server Component that receives navigation functions as props
export default function ServerComponent({ navigateToDashboard }) {
return (
<div>
<h1>Server Component</h1>
<button onClick={navigateToDashboard}>
Navigate to Dashboard
</button>
</div>
);
}
Solution 3: Use Server-Safe Alternatives
Use server-safe alternatives like the redirect function for server-side navigation.
lib/navigation.js:
// ✅ Server-safe navigation utilities
import { redirect } from 'next/navigation';
// ✅ Server-side redirect function
export function serverRedirect(path) {
redirect(path);
}
// ✅ Conditional redirect based on authentication
export function requireAuth(path = '/login') {
// ✅ Check authentication status
const isAuthenticated = checkAuthStatus(); // Your auth logic here
if (!isAuthenticated) {
redirect(path);
}
}
// ✅ Helper function to check auth status
function checkAuthStatus() {
// ✅ Implement your auth check logic
// This could check cookies, headers, etc.
return true; // Placeholder
}
app/dashboard/page.js:
// ✅ Server Component page
import { cookies } from 'next/headers';
import { serverRedirect } from '@/lib/navigation';
import DashboardClient from './client-component';
// ✅ Server Component that handles authentication
export default function DashboardPage() {
// ✅ Check authentication on the server
const authToken = cookies().get('auth-token');
if (!authToken) {
// ✅ Redirect to login if not authenticated
serverRedirect('/login');
}
return (
<div>
<h1>Dashboard</h1>
{/* ✅ Client Component for interactive features */}
<DashboardClient />
</div>
);
}
app/dashboard/client-component.jsx:
'use client'; // ✅ Client Component for interactive features
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function DashboardClient() {
const router = useRouter();
const searchParams = useSearchParams();
const [userData, setUserData] = useState(null);
// ✅ Get query parameters
const tab = searchParams.get('tab') || 'overview';
useEffect(() => {
// ✅ Fetch user data
fetchUserData();
}, []);
const fetchUserData = async () => {
try {
const response = await fetch('/api/user');
const data = await response.json();
setUserData(data);
} catch (error) {
console.error('Error fetching user data:', error);
}
};
const handleTabChange = (newTab) => {
// ✅ Update URL with new tab parameter
router.push(`/dashboard?tab=${newTab}`);
};
const handleLogout = async () => {
try {
await fetch('/api/logout', { method: 'POST' });
router.push('/login');
} catch (error) {
console.error('Logout error:', error);
}
};
return (
<div className="p-4">
<div className="flex space-x-4 mb-4">
<button
onClick={() => handleTabChange('overview')}
className={`px-4 py-2 rounded ${tab === 'overview' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
>
Overview
</button>
<button
onClick={() => handleTabChange('settings')}
className={`px-4 py-2 rounded ${tab === 'settings' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
>
Settings
</button>
<button
onClick={() => handleTabChange('analytics')}
className={`px-4 py-2 rounded ${tab === 'analytics' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
>
Analytics
</button>
</div>
<div className="mt-4">
<h2>{tab.charAt(0).toUpperCase() + tab.slice(1)} Tab</h2>
<p>Current tab: {tab}</p>
</div>
<button
onClick={handleLogout}
className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Logout
</button>
</div>
);
}
Solution 4: Create a Client-Side Navigation Hook
Create a custom hook that encapsulates navigation logic for reuse.
hooks/useNavigation.js:
'use client'; // ✅ This hook must be in a Client Component
import { useRouter } from 'next/navigation';
import { useCallback } from 'react';
export function useNavigation() {
const router = useRouter();
// ✅ Custom navigation function with loading state
const navigate = useCallback((path, options = {}) => {
const { replace = false, scroll = true } = options;
if (replace) {
router.replace(path);
} else {
router.push(path);
}
// ✅ Scroll to top if specified
if (scroll) {
window.scrollTo(0, 0);
}
}, [router]);
// ✅ Navigation with query parameters
const navigateWithQuery = useCallback((basePath, queryParams) => {
const queryString = new URLSearchParams(queryParams).toString();
const fullPath = queryString ? `${basePath}?${queryString}` : basePath;
router.push(fullPath);
}, [router]);
// ✅ Safe navigation with error handling
const safeNavigate = useCallback(async (path) => {
try {
router.push(path);
} catch (error) {
console.error('Navigation failed:', error);
// ✅ Fallback navigation
window.location.href = path;
}
}, [router]);
return {
navigate,
navigateWithQuery,
safeNavigate,
back: router.back,
forward: router.forward,
refresh: router.refresh,
};
}
components/SafeNavigation.jsx:
'use client'; // ✅ Client Component using the custom hook
import { useNavigation } from '@/hooks/useNavigation';
export default function SafeNavigation() {
const { navigate, navigateWithQuery, safeNavigate, back, forward } = useNavigation();
const handleBasicNavigation = () => {
navigate('/dashboard');
};
const handleNavigationWithQuery = () => {
navigateWithQuery('/search', { q: 'nextjs', category: 'tutorials' });
};
const handleSafeNavigation = () => {
safeNavigate('/profile');
};
return (
<div className="p-4">
<h2>Safe Navigation Examples</h2>
<div className="space-y-2">
<button
onClick={handleBasicNavigation}
className="block w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Basic Navigation
</button>
<button
onClick={handleNavigationWithQuery}
className="block w-full p-2 bg-green-500 text-white rounded hover:bg-green-600"
>
Navigation with Query Params
</button>
<button
onClick={handleSafeNavigation}
className="block w-full p-2 bg-purple-500 text-white rounded hover:bg-purple-600"
>
Safe Navigation
</button>
<button
onClick={back}
className="block w-full p-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Go Back
</button>
<button
onClick={forward}
className="block w-full p-2 bg-orange-500 text-white rounded hover:bg-orange-600"
>
Go Forward
</button>
</div>
</div>
);
}
Solution 5: Use Link Component for Static Navigation
Use Next.js Link component for static navigation instead of useRouter.
components/StaticNavigation.jsx:
// ✅ Server Component using Link for navigation
import Link from 'next/link';
export default function StaticNavigation() {
return (
<nav className="bg-gray-800 text-white p-4">
<ul className="flex space-x-6">
<li>
<Link
href="/"
className="hover:text-blue-300 transition-colors"
>
Home
</Link>
</li>
<li>
<Link
href="/about"
className="hover:text-blue-300 transition-colors"
>
About
</Link>
</li>
<li>
<Link
href="/contact"
className="hover:text-blue-300 transition-colors"
>
Contact
</Link>
</li>
<li>
<Link
href="/dashboard"
className="hover:text-blue-300 transition-colors"
>
Dashboard
</Link>
</li>
</ul>
</nav>
);
}
components/DynamicNavigation.jsx:
'use client'; // ✅ Client Component for dynamic navigation
import { usePathname } from 'next/navigation';
import Link from 'next/link';
export default function DynamicNavigation() {
const pathname = usePathname();
const navItems = [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/contact', label: 'Contact' },
{ href: '/dashboard', label: 'Dashboard' },
];
return (
<nav className="bg-indigo-600 text-white p-4">
<ul className="flex flex-wrap gap-4">
{navItems.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className={`px-4 py-2 rounded transition-colors ${
pathname === item.href
? 'bg-white text-indigo-600'
: 'hover:bg-indigo-700'
}`}
>
{item.label}
</Link>
</li>
))}
</ul>
</nav>
);
}
Solution 6: Handle Mixed Component Scenarios
Properly handle scenarios where you need both server and client functionality.
components/MixedComponentExample.jsx:
// ✅ Server Component that renders both server and client parts
import ServerData from './ServerData';
import ClientInteractions from './ClientInteractions';
export default function MixedComponentExample() {
return (
<div>
{/* ✅ Server Component - runs on server, can fetch data */}
<ServerData />
{/* ✅ Client Component - runs in browser, can use hooks */}
<ClientInteractions />
</div>
);
}
components/ServerData.jsx:
// ✅ Server Component that fetches and displays data
import { fetchData } from '@/lib/data-fetching';
export default async function ServerData() {
// ✅ Fetch data on the server
const data = await fetchData();
return (
<div className="p-4 bg-green-100">
<h3>Server Data</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
components/ClientInteractions.jsx:
'use client'; // ✅ Client Component for user interactions
import { useRouter } from 'next/navigation';
import { useState } from 'react';
export default function ClientInteractions() {
const router = useRouter();
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
const handleNavigate = () => {
router.push('/dashboard');
};
return (
<div className="p-4 bg-blue-100">
<h3>Client Interactions</h3>
<p>Count: {count}</p>
<button
onClick={handleIncrement}
className="mr-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Increment
</button>
<button
onClick={handleNavigate}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
>
Go to Dashboard
</button>
</div>
);
}
Working Code Examples
Complete Next.js App with Proper Routing:
// app/layout.js
import './globals.css';
import Navigation from '@/components/Navigation';
import StaticNavigation from '@/components/StaticNavigation';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className="min-h-screen bg-gray-100">
<header>
<StaticNavigation />
</header>
<main className="container mx-auto p-4">
{children}
</main>
<footer className="mt-8 p-4 bg-gray-800 text-white text-center">
<p>© 2026 My Next.js App</p>
</footer>
</body>
</html>
);
}
Home Page:
// app/page.js
import { getServerSession } from 'next-auth/next';
import { authOptions } from './api/auth/[...nextauth]/route';
import DynamicNavigation from '@/components/DynamicNavigation';
export default async function HomePage() {
// ✅ Server Component can fetch data
const session = await getServerSession(authOptions);
return (
<div>
<h1 className="text-3xl font-bold mb-6">Welcome to Next.js</h1>
<div className="mb-8">
<DynamicNavigation />
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4">Home Page</h2>
<p>Hello {session?.user?.name || 'Guest'}!</p>
<p>This is a server-rendered page.</p>
</div>
</div>
);
}
Dashboard Page:
// app/dashboard/page.js
import { cookies } from 'next/headers';
import DashboardClient from './client-component';
export default function DashboardPage() {
// ✅ Server Component logic
const authToken = cookies().get('auth-token');
if (!authToken) {
// ✅ Redirect to login if not authenticated
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">Access Denied</h2>
<p>Please log in to access the dashboard.</p>
</div>
</div>
);
}
return (
<div>
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<DashboardClient />
</div>
);
}
Client Component for Dashboard:
// app/dashboard/client-component.jsx
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { useState, useEffect } from 'react';
import SafeNavigation from '@/components/SafeNavigation';
export default function DashboardClient() {
const router = useRouter();
const searchParams = useSearchParams();
const [activeTab, setActiveTab] = useState(searchParams.get('tab') || 'overview');
const [stats, setStats] = useState({});
useEffect(() => {
// ✅ Update active tab when URL changes
const tabParam = searchParams.get('tab');
if (tabParam) {
setActiveTab(tabParam);
}
// ✅ Fetch dashboard stats
fetchStats();
}, [searchParams]);
const fetchStats = async () => {
try {
const response = await fetch('/api/dashboard/stats');
const data = await response.json();
setStats(data);
} catch (error) {
console.error('Error fetching stats:', error);
}
};
const handleTabChange = (tab) => {
setActiveTab(tab);
router.push(`/dashboard?tab=${tab}`, { scroll: false });
};
return (
<div className="space-y-6">
<div className="flex space-x-2 overflow-x-auto">
{['overview', 'users', 'analytics', 'settings'].map((tab) => (
<button
key={tab}
onClick={() => handleTabChange(tab)}
className={`px-4 py-2 rounded whitespace-nowrap ${
activeTab === tab
? 'bg-blue-500 text-white'
: 'bg-gray-200 hover:bg-gray-300'
}`}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</div>
<div className="bg-white p-6 rounded-lg shadow-md">
<h2 className="text-xl font-semibold mb-4 capitalize">{activeTab}</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-blue-50 p-4 rounded">
<h3 className="font-medium">Users</h3>
<p className="text-2xl font-bold">{stats.users || 0}</p>
</div>
<div className="bg-green-50 p-4 rounded">
<h3 className="font-medium">Revenue</h3>
<p className="text-2xl font-bold">${stats.revenue || 0}</p>
</div>
<div className="bg-purple-50 p-4 rounded">
<h3 className="font-medium">Orders</h3>
<p className="text-2xl font-bold">{stats.orders || 0}</p>
</div>
</div>
</div>
<SafeNavigation />
</div>
);
}
Best Practices for Next.js Routing
1. Always Add ‘use client’ for Client Hooks
'use client'; // ✅ Always add this for client hooks
import { useRouter } from 'next/navigation';
2. Separate Server and Client Logic
// ✅ Server Component for data fetching
// ✅ Client Component for interactivity
3. Use Link for Static Navigation
// ✅ Use Link for static navigation
import Link from 'next/link';
<Link href="/page">Page</Link>;
4. Use useRouter for Dynamic Navigation
'use client';
import { useRouter } from 'next/navigation';
// ✅ Use for programmatic navigation
5. Handle Error Cases
// ✅ Always handle navigation errors
try {
router.push('/path');
} catch (error) {
console.error('Navigation failed:', error);
}
Debugging Steps
Step 1: Check Component Type
# Verify if your component has 'use client' directive
grep -n "'use client'" components/YourComponent.jsx
Step 2: Review Import Statements
# Check for conflicting imports
# Ensure useRouter is imported from 'next/navigation'
Step 3: Verify Next.js Version
# Check Next.js version
npm list next
# Ensure you're using Next.js 13+ for App Router
Step 4: Check Component Hierarchy
# Verify that parent components aren't causing issues
# Ensure client components aren't being imported into server components
Step 5: Test Navigation
// Simple test component
'use client';
import { useRouter } from 'next/navigation';
export default function TestNav() {
const router = useRouter();
return <button onClick={() => router.push('/')}>Home</button>;
}
Common Mistakes to Avoid
1. Forgetting ‘use client’ Directive
// ❌ Missing 'use client'
import { useRouter } from 'next/navigation';
// This will cause an error
2. Using Client Hooks in Server Components
// ❌ Server Component (no 'use client')
import { useRouter } from 'next/navigation';
// useRouter cannot be used here
3. Importing Client Components into Server Components
// ❌ Server Component importing Client Component
import ClientComponent from './ClientComponent'; // This causes issues
4. Using useRouter in Server Actions
// ❌ Server Action trying to use client hook
'use server';
import { useRouter } from 'next/navigation'; // This won't work
Performance Considerations
1. Minimize Client Components
// ✅ Only make components client components when necessary
// ✅ Use server components for static content
2. Optimize Bundle Size
// ✅ Import only what you need
import { useRouter } from 'next/navigation';
// ✅ Not the entire next/navigation module
3. Lazy Load Client Components
// ✅ Use dynamic imports for heavy client components
import dynamic from 'next/dynamic';
const HeavyClientComponent = dynamic(() => import('./HeavyClientComponent'));
Security Considerations
1. Validate Navigation Paths
// ✅ Validate navigation paths to prevent open redirects
const safePath = validatePath(userInput);
router.push(safePath);
2. Sanitize External URLs
// ✅ Sanitize external URLs before navigation
const sanitizedUrl = sanitizeUrl(externalUrl);
window.location.href = sanitizedUrl;
3. Protect Sensitive Routes
// ✅ Use server-side validation for sensitive routes
// ✅ Don't rely solely on client-side routing
Testing Client Components
1. Unit Test Client Components
// Using React Testing Library
import { render, screen } from '@testing-library/react';
import { useRouter } from 'next/navigation';
import YourComponent from './YourComponent';
// Mock useRouter
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
}));
describe('YourComponent', () => {
test('renders navigation button', () => {
useRouter.mockReturnValue({
push: jest.fn(),
});
render(<YourComponent />);
expect(screen.getByText('Navigate')).toBeInTheDocument();
});
});
2. Test Navigation Functionality
test('handles navigation correctly', () => {
const pushMock = jest.fn();
useRouter.mockReturnValue({ push: pushMock });
render(<YourComponent />);
fireEvent.click(screen.getByText('Navigate'));
expect(pushMock).toHaveBeenCalledWith('/expected-path');
});
Alternative Solutions
1. Use Legacy Pages Router
// ❌ Not recommended, but possible alternative
// Continue using pages/ directory instead of app/
2. Server-Side Redirects
// ✅ Use server-side redirects when possible
import { redirect } from 'next/navigation';
redirect('/path');
3. Traditional Links
// ✅ Use Link component for navigation
import Link from 'next/link';
<Link href="/path">Navigate</Link>;
Migration Checklist
- Add ‘use client’ directive to components using useRouter
- Separate server and client component logic
- Replace client-side navigation with Link where appropriate
- Use server-side redirects for server logic
- Test all navigation functionality
- Verify component hierarchy is correct
- Check for mixed server/client component imports
- Update error boundaries to handle client component errors
Conclusion
The ‘useRouter only works in Client Components’ error is a fundamental aspect of Next.js 13+‘s architecture that distinguishes between Server and Client Components. By following the solutions provided in this guide—adding the ‘use client’ directive, separating server and client logic, using appropriate navigation methods, and understanding the component model—you can effectively resolve this error and build robust Next.js applications.
The key is to understand when to use Server Components versus Client Components, properly configure your components with the ‘use client’ directive when needed, and leverage Next.js’s routing capabilities appropriately. With proper component architecture, your Next.js applications will handle routing efficiently while maintaining optimal performance and user experience.
Remember to test your routing implementation thoroughly, follow Next.js best practices for component separation, implement proper error handling, and stay updated with Next.js documentation to ensure your applications maintain the best possible routing architecture.
Related Articles
Fix: cookies() can only be used in Server Components error Next.js
Quick fix for 'cookies() can only be used in Server Components' error in Next.js. Learn how to properly use cookies in Server Components.
Fix: useEffect is not allowed in Server Components - Quick Fix in Next.js
Quick fix for 'useEffect is not allowed in Server Components' error in Next.js. Learn how to properly use useEffect in Client Components.
Fix: useState is not allowed in Server Components error Next.js
Quick fix for 'useState is not allowed in Server Components' error in Next.js. Learn how to properly use useState in Client Components.