Skip to content
techpotions
react · nextjs · migration · cra-to-nextjs · ssrOctober 9, 20265 min read

Migrate React to Next.js: Step-by-Step

Learn the real-world steps to migrate your React app to Next.js without losing momentum—covering routing, API moves, client components, and when to call in help.

Cover illustration for “Migrate React to Next.js: Step-by-Step”

When you migrate React to Next.js, you replace a client-side SPA with a framework that delivers hybrid rendering, file-based routing, and built‑in performance optimization. This guide walks you through moving an existing React app—whether from Create React App, Vite, or a custom setup—into Next.js 14+ using the App Router. We’ll cover the real‑world steps the official docs often gloss over, so you can avoid the common pitfalls.

Why migrate your React app to Next.js?

Next.js solves the two biggest pain points of plain React SPAs: search engine indexing and initial load speed. August Infotech highlights better SEO visibility, faster page loads, and Lighthouse improvements as primary drivers for migration. When users and crawlers hit a React SPA, they often get an empty shell until JavaScript hydrates. Next.js renders HTML on the server, so the content is immediately visible and crawlable. File-based routing eliminates the need for react-router-dom and organically groups API routes next to pages. Plus, you get image optimization, static generation, and incremental adoption of React Server Components—all without ejecting or changing your bundler.

How to migrate React to Next.js – step by step

These steps walk you through a real migration, not a greenfield project. You’ll keep your existing codebase and gradually restructure it.

1. Install Next.js and update dependencies

You don’t need to start from scratch. In your existing React project, install Next.js as a dependency:

Shell
npm install next@latest react@latest react-dom@latest

If you’re coming from Create React App, the official Next.js migration guide suggests this minimal install first, then building the config.

2. Create the Next.js configuration

Add a next.config.js at the root:

JavaScript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
};
module.exports = nextConfig;

This mirrors the simplest needed config. Later you can add image domains, rewrites, etc.

3. Set up the root layout

Create the app directory (if not present) and add app/layout.js:

JSX
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

This replaces public/index.html and wraps every page. The App Router requires it (Step 3 of the CRA migration).

4. Migrate routing – goodbye React Router

Next.js uses a file-system routing model. Remove react-router-dom and replace your <BrowserRouter> with the page structure inside app/. For example:

  • app/page.js → your home page
  • app/about/page.js → /about
  • app/products/[id]/page.js → dynamic route

As Anna Saad’s guide notes, you’ll no longer use <Route> components. Simply export default React components from page.js files. Any shared layouts go in layout.js. This alone cleans up a huge amount of route configuration.

5. Move API endpoints into route handlers

If your React app had a separate Express server or calls to external APIs, you can consolidate them inside app/api/. Create files like app/api/users/route.js and export functions for each HTTP method:

JavaScript
export async function GET() {
  const res = await fetch('https://backend.example.com/users');
  const data = await res.json();
  return Response.json(data);
}

These live alongside your pages, removing the need for a separate server. The internal API routes can even fetch directly from databases when deployed on a serverful environment.

6. Handle client-only code and state

One of the most common migration stumbles: you’ll encounter errors like window is not defined because Next.js tries to render components on the server. Wrap any component that uses browser APIs, event listeners, or hooks like useState with the 'use client' directive at the top of its file. This tells Next.js to ship it as a client component, just like a traditional React component. The App Router migration docs emphasize that you can adopt Server Components gradually, so you don’t need to refactor everything at once.

State management libraries (Redux, Zustand, Context) work as before when used inside client components. Just ensure the provider wrapping is also client-side.

7. Adopt server-side data fetching

Where you previously used useEffect to fetch data on mount, consider moving that to an async server component or using Next.js’s fetch within Server Components. It automatically deduplicates requests and can render the page before JavaScript reaches the browser. This is optional but gives you the full performance benefit.

JSX
// app/products/page.js – Server Component
export default async function Products() {
  const products = await fetch('https://api.example.com/products').then(res => res.json());
  return <ProductList items={products} />;
}

You can still use client-side fetching for real-time dashboards; Next.js doesn’t force a single pattern.

8. Test and flip the switch

Run npm run dev (add "dev": "next dev" to your package.json scripts). Fix any broken imports, missing 'use client' markers, or assets that moved from src/ to public/. Update your build and start scripts to next build and next start. Once everything works locally, deploy to Vercel, Netlify, or any Node server.

A free online tool like React to Next.js Converter can give you a head start on renaming and restructuring files, but we recommend understanding each change to avoid hidden issues in large codebases.

When to call in professional help

Migrating a small, component-heavy app is a weekend task. But applications with complex routing, custom Webpack configurations, or deeply intertwined state can become a time sink. Our migration services exist precisely for that—handling the heavy lifting so your team stays focused on product, not plumbing.

FAQ

Can I keep React Router when I migrate React to Next.js?

Technically you can mount a router inside a client component, but you’ll lose the benefits of file-based routing, static generation, and many Next.js optimizations. The community consensus (and Anna Saad’s guide) is to drop react-router-dom entirely and use Next.js’s native routing.

Will my state management (Redux, Context) still work?

Yes, but you must wrap the provider in a client component with the 'use client' directive. Server Components cannot use React Context directly; they would render a client subtree that contains the provider. This is a standard pattern documented in the App Router migration guide.

Do I have to rewrite every component as a Server Component?

No. Next.js supports a hybrid model. You can mark any component as 'use client' and it behaves exactly like a traditional React component. Start by keeping your existing components as client components, then gradually refactor parts that are purely presentational or data-fetching into Server Components for better performance.

Written by
techpotions
All entries
Payload CMS + Next.js, Done Right
The weekly

One email a week, from the workshop.

What we published, what we shipped, and the free packs as they land. No drip sequence, no webinar, unsubscribe in one click.