Skip to main content
Technology & EngineeringMigration Patterns256 lines

Cra to Nextjs

Migrate a Create React App project to Next.js for server-side rendering and file-based routing

Quick Summary25 lines
You are an expert in migrating Create React App (CRA) projects to Next.js for server-side rendering, static generation, file-based routing, and improved performance.

## Key Points

1. **Install Next.js** — add Next.js alongside the existing CRA setup.
2. **Restructure to App or Pages Directory** — move components into the Next.js file-based routing structure.
3. **Replace React Router** — convert route definitions to file-system routes.
4. **Migrate Data Fetching** — move API calls from `useEffect` to `getServerSideProps`, `getStaticProps`, or Server Components.
5. **Update Configuration** — translate CRA environment variables and proxy setup to `next.config.js`.
6. **Remove CRA** — uninstall `react-scripts` and related dependencies.
- Migrate pages one at a time. Keep CRA running in parallel until all routes are moved.
- Mark components that use `useState`, `useEffect`, or browser APIs with `'use client'` at the top of the file.
- Use Server Components by default; only add `'use client'` when interactivity is required.
- Replace `react-helmet` or `react-helmet-async` with Next.js `metadata` exports.
- Move API calls out of `useEffect` into server-side fetching wherever possible to eliminate loading spinners and improve SEO.
- Use `next/image` to replace `<img>` tags for automatic optimization.

## Quick Example

```bash
npm install next
npm uninstall react-scripts
```
skilldb get migration-patterns-skills/Cra to NextjsFull skill: 256 lines
Paste into your CLAUDE.md or agent config

Create React App to Next.js — Migration Patterns

You are an expert in migrating Create React App (CRA) projects to Next.js for server-side rendering, static generation, file-based routing, and improved performance.

Core Philosophy

Overview

CRA is a client-side-only React setup with no built-in server rendering or routing conventions. Next.js provides SSR, SSG, API routes, file-based routing, and image optimization out of the box. The migration involves restructuring files into Next.js conventions, replacing React Router with file-based routing, and adapting data fetching to use Next.js patterns.

Migration Strategy

  1. Install Next.js — add Next.js alongside the existing CRA setup.
  2. Restructure to App or Pages Directory — move components into the Next.js file-based routing structure.
  3. Replace React Router — convert route definitions to file-system routes.
  4. Migrate Data Fetching — move API calls from useEffect to getServerSideProps, getStaticProps, or Server Components.
  5. Update Configuration — translate CRA environment variables and proxy setup to next.config.js.
  6. Remove CRA — uninstall react-scripts and related dependencies.

Step-by-Step Guide

1. Install Next.js

npm install next
npm uninstall react-scripts

2. Restructure the project

CRA structure:

src/
  App.js
  index.js
  pages/
    Home.js
    About.js
    UserProfile.js
  components/
    Header.js
    Footer.js

Next.js App Router structure:

app/
  layout.tsx
  page.tsx              ← Home
  about/
    page.tsx            ← About
  users/
    [id]/
      page.tsx          ← UserProfile
src/
  components/
    Header.tsx
    Footer.tsx

3. Create the root layout (replaces index.js + App.js shell)

// app/layout.tsx
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import '@/styles/globals.css';

export const metadata = {
  title: 'My App',
  description: 'Migrated from CRA to Next.js',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

4. Replace React Router with file-based routing

// Before — CRA with React Router
// src/App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import UserProfile from './pages/UserProfile';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users/:id" element={<UserProfile />} />
      </Routes>
    </BrowserRouter>
  );
}

// After — Next.js file-based routing
// app/page.tsx
export default function HomePage() {
  return <h1>Welcome</h1>;
}

// app/about/page.tsx
export default function AboutPage() {
  return <h1>About Us</h1>;
}

// app/users/[id]/page.tsx
export default function UserProfilePage({ params }: { params: { id: string } }) {
  return <h1>User {params.id}</h1>;
}

5. Replace navigation

// Before — React Router
import { useNavigate, Link } from 'react-router-dom';
const navigate = useNavigate();
navigate('/about');
<Link to="/users/42">View Profile</Link>

// After — Next.js
import { useRouter } from 'next/navigation';
import Link from 'next/link';
const router = useRouter();
router.push('/about');
<Link href="/users/42">View Profile</Link>

6. Migrate data fetching to Server Components

// Before — CRA useEffect pattern
function UserProfile() {
  const { id } = useParams();
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${id}`).then(r => r.json()).then(setUser);
  }, [id]);

  if (!user) return <Spinner />;
  return <div>{user.name}</div>;
}

// After — Next.js Server Component (no loading spinner needed)
// app/users/[id]/page.tsx
async function getUser(id: string) {
  const res = await fetch(`${process.env.API_URL}/users/${id}`, { cache: 'no-store' });
  if (!res.ok) throw new Error('Failed to fetch user');
  return res.json();
}

export default async function UserProfilePage({ params }: { params: { id: string } }) {
  const user = await getUser(params.id);
  return <div>{user.name}</div>;
}

7. Update environment variables

# CRA uses REACT_APP_ prefix
REACT_APP_API_URL=https://api.example.com

# Next.js uses NEXT_PUBLIC_ prefix for client-side variables
NEXT_PUBLIC_API_URL=https://api.example.com

# Server-only variables need no prefix in Next.js
API_SECRET=secret_key

8. Update next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Proxy API requests (replaces CRA's proxy field in package.json)
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'http://localhost:8080/api/:path*',
      },
    ];
  },

  images: {
    domains: ['cdn.example.com'],
  },
};

module.exports = nextConfig;

9. Update package.json scripts

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}

Best Practices

  • Migrate pages one at a time. Keep CRA running in parallel until all routes are moved.
  • Mark components that use useState, useEffect, or browser APIs with 'use client' at the top of the file.
  • Use Server Components by default; only add 'use client' when interactivity is required.
  • Replace react-helmet or react-helmet-async with Next.js metadata exports.
  • Move API calls out of useEffect into server-side fetching wherever possible to eliminate loading spinners and improve SEO.
  • Use next/image to replace <img> tags for automatic optimization.

Common Pitfalls

  • Forgetting 'use client' — components using hooks, event handlers, or browser APIs must be marked as Client Components.
  • Window/document references — code that accesses window or document at the top level breaks during SSR. Guard with typeof window !== 'undefined' or move to useEffect.
  • CSS-in-JS compatibility — some CSS-in-JS libraries (styled-components, emotion) require additional configuration for SSR. Check Next.js docs for your library.
  • Direct process.env access on the client — only NEXT_PUBLIC_* variables are exposed to the browser. Server-only env vars are undefined on the client.
  • Losing SPA behavior — Next.js client-side navigation via <Link> is fast, but using <a> tags causes full page reloads. Always use next/link.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add migration-patterns-skills

Get CLI access →