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.