Create React App made it easy to get a React project off the ground, but it has aged poorly. Development servers are slow to start, hot module replacement lags on anything beyond a small project, and the underlying Webpack configuration is difficult to customize without ejecting.
Vite was built to address exactly these problems. It uses native ES modules during development, which means it skips the bundling step entirely and serves files directly to the browser. The result is a dev server that starts in under a second and reflects changes almost instantly.
This guide walks through migrating an existing CRA project to Vite, step by step.
What this covers:
Why the switch is worth making
Setting up the Vite project structure
Moving source files and fixing entry points
Handling environment variables and public assets
Common issues and how to resolve them
Why Migrate from CRA to Vite
The practical differences are most noticeable during development. Vite starts immediately regardless of project size because it does not pre-bundle the entire application before serving it. Changes appear in the browser before you have moved your hand back to the mouse.
Beyond speed, a few other things are worth noting:
JSX, TypeScript, and CSS preprocessing work without additional configuration
Production builds are smaller due to native tree-shaking and Rollup-based bundling
The configuration file is straightforward and does not require ejecting to customize
The project is actively maintained with a clear roadmap
CRA has been in maintenance mode since early 2023. For any project expecting continued development, migrating is a reasonable decision sooner rather than later.
Step 1: Verify Your CRA Project
Before touching anything, confirm the existing project runs cleanly:
npm install
npm start
Work through any existing errors before starting the migration. Carrying unresolved issues into a new build setup makes debugging harder.
Note: If you see an OpenSSL error on Node 18 or higher, switch to Node 16 temporarily with
nvm use 16. This is a known CRA compatibility issue, not a problem with your code.
Step 2: Create a Migration Branch
Keep the migration isolated so you can compare before and after, or roll back if needed:
git checkout --orphan vite-migration
Step 3: Scaffold a New Vite Project
Create a fresh Vite project alongside your existing one:
npm create vite@latest vite-app
When prompted, select React and choose JavaScript or TypeScript depending on your project. Then install dependencies:
cd vite-app
npm install
Tip: If your CRA project uses React 17, update the React version in
package.jsonbefore runningnpm installto avoid peer dependency conflicts.
Step 4: Move Your Source Files
Replace the scaffolded files with your own:
Delete the
srcandpublicfolders inside the Vite projectCopy the
srcandpublicfolders from your CRA project into the Vite projectReplace
index.htmlin the Vite root with the one from your CRApublicfolder
All your components, styles, and assets are now in place. The structure should look familiar.
Step 5: Update File Extensions and the Entry Point
Vite requires JSX syntax to live in .jsx files, not .js. Rename any .js files that contain JSX:
mv src/App.js src/App.jsx
Rename the entry point as well:
mv src/index.js src/main.jsx
Then update index.html to reference the new entry point:
<script type="module" src="/src/main.jsx"></script>
This is the most common step where things break if skipped, so check every file that contains JSX syntax.
Step 6: Start the Dev Server
Run the Vite dev server:
npm run dev
Vite defaults to port 5173. To keep it on port 3000 for consistency with your old setup, update vite.config.js:
export default defineConfig({
server: {
port: 3000
}
});
Open the browser and verify the app loads. At this point most projects will be running, with any remaining issues coming from environment variables or public asset paths.
Step 7: Fix Environment Variables and Public Asset Paths
Remove %PUBLIC_URL%
Vite does not support the %PUBLIC_URL% placeholder that CRA injects. Replace every instance in index.html with a root-relative path:
<!-- Before -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- After -->
<link rel="icon" href="/favicon.ico" />
Update Environment Variables
CRA uses REACT_APP_ as the prefix for environment variables exposed to the browser. Vite uses VITE_ instead, and accesses them through import.meta.env rather than process.env.
Update your .env files:
# Before
REACT_APP_API_URL=https://api.example.com
# After
VITE_API_URL=https://api.example.com
Update every reference in your code:
// Before
const apiUrl = process.env.REACT_APP_API_URL;
// After
const apiUrl = import.meta.env.VITE_API_URL;
Troubleshooting
Blank screen after starting the dev server Almost always caused by a .js file containing JSX that was not renamed to .jsx. Check the browser console for the specific file path.
Module not found errors CRA supports absolute imports configured via jsconfig.json. Vite does not pick these up automatically. Add the equivalent alias in vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
src: path.resolve(__dirname, './src')
}
}
});
process.env is undefined This means an environment variable reference was missed during the migration. Search the codebase for process.env and update each instance to import.meta.env.
Key Takeaways
Vite's native ES module approach eliminates the slow startup and rebuild times that come with CRA's Webpack setup.
The migration involves scaffolding a fresh Vite project and moving your source files across.
Any
.jsfile containing JSX must be renamed to.jsxfor Vite to process it correctly.Replace
%PUBLIC_URL%inindex.htmlwith root-relative paths.Environment variables need the
VITE_prefix and are accessed viaimport.meta.envinstead ofprocess.env.
Conclusion
CRA served its purpose for a long time, but the tooling landscape has moved on. Vite delivers a noticeably faster development experience with less configuration overhead, and the migration path for most projects is straightforward enough to complete in an afternoon.
For larger projects with heavy Webpack customization or third-party plugins, expect some additional work mapping those configurations to Vite equivalents. For everything else, the steps above should get you running cleanly.
Running into a specific issue during your migration? Describe it in the comments and include the error message.




