April 21, 2026
Migrating from CRA to Next.js: A Practical Guide
I still remember the moment my client asked, "Can we make this faster?" It was a Create React App project I had built two years earlier. A full-featured dashboard with charts, tables, and real-time updates. The kind of app that once made me proud.
But pride does not pay the bills when users bounce because pages take eight seconds to load.
We migrated from CRA to Next.js in a weekend. Here is exactly how we did it.
Why We Moved (The Pain Was Real)
Create React App served us well. It was the default for years. You ran create-react-app, you had a project, you were building.
But then our dashboard grew. Forty-seven components. Twelve API calls on load. A client that needed real-time stock prices updating every second.
The JavaScript bundle hit 2.4 megabytes. Users on slower connections saw white screens for seconds. We tried code splitting, lazy loading, memoization. Every optimization felt like patching a sinking ship.
The breaking point was the client complaint: "Our competitors load in two seconds."
That Monday, I started the migration.
The Old Project: What We Had
Our CRA project structure looked familiar: components, pages, api, App.js, index.js. Routes were handled by react-router. Data fetching lived in useEffect. Every page loaded everything, then hid what it did not need.
The first step was understanding what we actually had to move.
Step One: Audit Your Components
Before touching code, I listed every component and its purpose. Three categories emerged:
Server-side ready (static or data): Headers, footers, sidebars, content that did not change after load.
Client-side interactive (dynamic): Filter buttons, form submissions, real-time updates, anything with onClick or state that changed.
Hybrid: Components that fetched data once but then stayed still.
This separation mattered. Next.js works best when you send the server what can be server-rendered, and keep the client what truly needs JavaScript.
Step Two: Install Next.js
We kept our existing src folder and added Next.js alongside. We used the App Router (the new default in Next.js 13+). It meant our pages would be server-rendered by default, with client components only where needed.
Step Three: The Migration
Routing changed from react-router to file-based routing. Each page became a file in the app folder. Components that needed interactivity got "use client" at the top.
Data fetching moved from useEffect to async/await in the component. No more loading states. The server fetches, the client receives HTML.
Environment variables moved from .env to .env.local. Base URLs changed from process.env to NEXT_PUBLIC_.
The Results
Bundle size dropped from 2.4MB to 380KB. First contentful paint went from 8.2 seconds to 1.4 seconds. Time to interactive from 12 seconds to 3 seconds.
The client was happy. We were faster than their competitors.
The Bottom Line
Migration from CRA to Next.js is not as hard as you think. The patterns are simple. The benefits are immediate. Your future users will thank you.
If you are still on CRA, try the migration this weekend. Your dashboard will thank you on Monday.