As web development evolves, many developers find themselves wanting to migrate their Create React App (CRA) projects to Next.js to take advantage of features like server-side rendering, static site generation, and improved routing. Today, I’m breaking down an exciting new experimental codemod tool that has recently landed in the @next/codemod package designed to automate this migration process.
Experimental CRA to Next.js Migration Tool
This transformation has recently been added to the official @next/codemod package as an experimental feature, making it easier than ever to migrate existing CRA applications to Next.js. Being experimental, it may continue to evolve as the Next.js team gathers feedback from early adopters.
What is a Codemod?
Before diving in, let’s clarify what a codemod is: it’s an automated tool that helps transform source code from one format to another. In this case, the newly introduced experimental codemod helps transform a CRA project into a Next.js project by automatically updating file structures, configurations, and code patterns.
Codemod Overview
This newly added experimental codemod is a sophisticated transformer designed to handle the complex task of migrating a CRA project to Next.js. At its core, it:
- Analyzes your CRA project structure
- Transforms key files to be Next.js compatible
- Creates necessary Next.js configuration files
- Updates package dependencies
- Sets up the pages directory with proper components
Let’s explore how this experimental feature works in detail.
Project Analysis and Validation
When you run the experimental codemod, it first validates your project directory and determines if it’s a proper CRA project by looking for react-scripts in your dependencies. It also detects if you’re using TypeScript by checking for a tsconfig.json file and .ts/.tsx files in your project.
this.isCra = hasDep('react-scripts')
this.isVite = !this.isCra && hasDep('vite')
if (!this.isCra && !this.isVite) {
fatalMessage(`Error: react-scripts was not detected, is this a CRA project?`)
}
this.shouldUseTypeScript =
fs.existsSync(path.join(this.appDir, 'tsconfig.json')) ||
globby.sync('src/**/*.{ts,tsx}', {
cwd: path.join(this.appDir, 'src'),
}).length > 0
Transforming the Entry Point
One of the most critical transformations in this experimental codemod is converting your CRA entry point (src/index.js or src/main.js) into a React component that can be rendered within Next.js. The codemod uses a custom transformation to convert ReactDOM.render calls into a proper component:
const indexTransformRes = await runJscodeshift(
indexTransformPath,
{ ...this.jscodeShiftFlags, silent: true, verbose: 0 },
[path.join(this.appDir, 'src', this.indexPage)]
)
It also performs safety checks to ensure your app doesn’t have multiple render roots or nested renders, which would need manual intervention.
Handling Global CSS
Next.js handles CSS differently than CRA. The experimental codemod identifies global CSS imports in your application and moves them to the appropriate location:
const globalCssRes = await runJscodeshift(
globalCssTransformPath,
{ ...this.jscodeShiftFlags },
[this.appDir]
)
These CSS imports will be added to the _app.js file, which is the entry point for Next.js applications.
Creating the Next.js Pages Structure
Next.js uses a file-system based router with a /pages directory. The experimental codemod creates this directory and populates it with essential files:
_app.js: The application wrapper that includes global styles and layout elements_document.js: Customizes the HTML document structure[[...slug]].js: A catch-all route that imports your transformed CRA application
The codemod extracts elements from your public/index.html file and distributes them to the appropriate Next.js files:
// For _app.js
${
titleTag || metaViewport
? `return (
<>
<Head>
${titleTag ? `<title>...</title>` : ''}
${metaViewport ? `<meta... />` : ''}
</Head>
<Component {...pageProps} />
</>
)`
: 'return <Component {...pageProps} />'
}
Package.json Updates
The experimental codemod updates your package.json to replace CRA dependencies with Next.js dependencies:
// Removes react-scripts
const packagesToRemove = {
[packageName]: undefined,
}
// Adds next
const newDependencies = [
{
name: 'next',
version: 'latest',
},
]
It also transforms your npm scripts, converting react-scripts start to next dev, react-scripts build to next build, etc.
Next.js Configuration
The experimental codemod creates a next.config.js file with settings to ensure compatibility with your CRA project:
module.exports = {
// Preserves proxy settings from CRA
async rewrites() { ... },
// Preserves PUBLIC_URL environment variable
env: {
PUBLIC_URL: '${homepagePath === '/' ? '' : homepagePath || ''}'
},
// Enables CRA compatibility mode
experimental: {
craCompat: true,
},
// Preserves CRA's image handling
images: {
disableStaticImages: true
}
}
Client-Side Rendering Fallback
To handle potential issues with server-side rendering (SSR), the experimental codemod creates a dynamic import for your application with SSR disabled by default:
import dynamic from 'next/dynamic'
const NextIndexWrapper = dynamic(() => import('${relativeIndexPath}'), { ssr: false })
export default function Page(props) {
return <NextIndexWrapper {...props} />
}
This prevents common errors when migrating from CRA, which assumes client-side rendering only, to Next.js which supports SSR.
Using the Experimental Codemod
To use this newly added experimental codemod, you can install @next/codemod and run the transformation:
npx @next/codemod cra-to-next ./your-cra-app
The codemod will analyze your project and perform the transformation steps outlined above.
Limitations and Experimental Status
As this is a recently added experimental feature in @next/codemod, there are some limitations to be aware of:
- It doesn’t support projects with multiple ReactDOM.render calls
- SVG imports using the
{ReactComponent}syntax aren’t supported - Some browser-only code might need manual adjustments
- Complex webpack configurations may require additional tweaking
Being an experimental feature, you might encounter edge cases that haven’t been fully addressed yet. The Next.js team is actively collecting feedback to improve this codemod.
Conclusion
This recently added experimental codemod in the @next/codemod package significantly reduces the manual work required to migrate from CRA to Next.js. It handles the structural changes, configuration updates, and common code transformations automatically while providing helpful error messages when it encounters issues it can’t resolve.
If you’re considering migrating your CRA project to Next.js, this experimental codemod is an excellent starting point that will save you hours of manual migration work. After running it, you’ll have a functioning Next.js application that preserves your original CRA functionality while enabling you to gradually adopt Next.js features. Being an experimental feature, you should thoroughly test your application after migration and may need to make manual adjustments for more complex use cases.