Migration Guide
December 15, 2024
14 min read
React Router to Next.js: What Actually Changes
Converting React Router to Next.js routing. What changes automatically, what you need to fix manually, and the patterns that don't translate directly.
One of the biggest changes when migrating from React to Next.js is moving from React Router's programmatic routing to Next.js's file-based routing. While it might seem daunting at first, Next.js's approach is actually simpler and more intuitive once you understand the patterns.
Quick Comparison
| React Router |
Next.js Equivalent |
<Route path="/about"> |
app/about/page.js |
<Route path="/blog/:id"> |
app/blog/[id]/page.js |
<Link to="/about"> |
<Link href="/about"> |
useNavigate() |
useRouter() |
useParams() |
params prop |
useLocation() |
usePathname() |
Basic Route Migration
React Router Setup
// App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
Next.js Equivalent
Simply create files in the app directory:
app/
├── page.js // → /
├── about/
│ └── page.js // → /about
└── contact/
└── page.js // → /contact
// app/page.js
export default function Home() {
return <h1>Home Page</h1>;
}
// app/about/page.js
export default function About() {
return <h1>About Page</h1>;
}
// app/contact/page.js
export default function Contact() {
return <h1>Contact Page</h1>;
}
Dynamic Routes
React Router Dynamic Routes
// React Router
<Route path="/blog/:id" element={<BlogPost />} />
<Route path="/users/:userId/posts/:postId" element={<UserPost />} />
// BlogPost.js
import { useParams } from 'react-router-dom';
function BlogPost() {
const { id } = useParams();
return <div>Post {id}</div>;
}
Next.js Dynamic Routes
// File structure
app/
├── blog/
│ └── [id]/
│ └── page.js // → /blog/:id
└── users/
└── [userId]/
└── posts/
└── [postId]/
└── page.js // → /users/:userId/posts/:postId
// app/blog/[id]/page.js
export default function BlogPost({ params }) {
return <div>Post {params.id}</div>;
}
// app/users/[userId]/posts/[postId]/page.js
export default function UserPost({ params }) {
return (
<div>
User {params.userId}, Post {params.postId}
</div>
);
}
Catch-All Routes
React Router
<Route path="/docs/*" element={<Docs />} />
Next.js
// app/docs/[...slug]/page.js
export default function Docs({ params }) {
// /docs/a/b/c → params.slug = ['a', 'b', 'c']
return <div>Docs: {params.slug.join('/')}</div>;
}
Navigation and Links
React Router
import { Link, useNavigate } from 'react-router-dom';
function Navigation() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/about');
};
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<button onClick={handleClick}>Go to About</button>
</nav>
);
}
Next.js
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
function Navigation() {
const router = useRouter();
const handleClick = () => {
router.push('/about');
};
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<button onClick={handleClick}>Go to About</button>
</nav>
);
}
Important: In Next.js 13+, use next/navigation (not next/router) for the App Router.
Nested Routes and Layouts
React Router Nested Routes
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="settings" element={<Settings />} />
<Route path="profile" element={<Profile />} />
</Route>
// DashboardLayout.js
import { Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<Sidebar />
<Outlet /> {/* Child routes render here */}
</div>
);
}
Next.js Layouts
// File structure
app/
└── dashboard/
├── layout.js // Shared layout
├── page.js // → /dashboard
├── settings/
│ └── page.js // → /dashboard/settings
└── profile/
└── page.js // → /dashboard/profile
// app/dashboard/layout.js
export default function DashboardLayout({ children }) {
return (
<div>
<Sidebar />
{children} {/* Child pages render here */}
</div>
);
}
// app/dashboard/page.js
export default function DashboardHome() {
return <h1>Dashboard Home</h1>;
}
Protected Routes
React Router
function ProtectedRoute({ children }) {
const { user } = useAuth();
const navigate = useNavigate();
if (!user) {
navigate('/login');
return null;
}
return children;
}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
Next.js Middleware
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const token = request.cookies.get('token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: '/dashboard/:path*',
};
Query Parameters
React Router
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
return <div>Search: {query}</div>;
}
Next.js
'use client';
import { useSearchParams } from 'next/navigation';
function SearchPage() {
const searchParams = useSearchParams();
const query = searchParams.get('q');
return <div>Search: {query}</div>;
}
Programmatic Navigation
| React Router |
Next.js |
navigate('/about') |
router.push('/about') |
navigate('/about', { replace: true }) |
router.replace('/about') |
navigate(-1) |
router.back() |
navigate(1) |
router.forward() |
location.reload() |
router.refresh() |
Automate Your Routing Migration
ReactToNext automatically converts React Router patterns to Next.js file-based routing, saving you hours of manual work.
Start Converting Now →
Migration Checklist
- Solution: Convert
<Route> components to file structure
- Solution: Replace
:param with [param] folders
- Solution: Update
<Link to> to <Link href>
- Solution: Change
useNavigate() to useRouter()
- Solution: Update
useParams() to params prop
- Solution: Convert
<Outlet> to layout.js
- Solution: Move protected routes to middleware
- Solution: Update imports from
react-router-dom to next/navigation
Conclusion
While React Router and Next.js routing work differently, Next.js's file-based approach is more intuitive and requires less boilerplate. The migration process is straightforward once you understand the patterns, and the benefits—automatic code splitting, prefetching, and better performance—make it well worth the effort.