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

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.