I recently migrated a production app within my company from create-react-app (CRA) to Vite, and the results have been great so far!
In this article, I go through all the steps I took as part of the migration, in the hope that it might help others who are going through the same process.
Why switch?
I want to start by saying that I really like CRA, it's helped me to quickly set up and maintain lots of projects (personal and professional). However, here are some of the reasons why we ultimately decided to make the switch:
- No dedicated maintainer.
- Slow to release. This will only cause more issues down the line as more features are added to React and Webpack.
- Increasing number of vulnerabilities causing github dependabot alerts which our security team require we fix, regardless of it being a build tool or not.
- Speed. This wasn't really an issue for me as I rarely restart my dev server and my CI pipeline handles the production build for me. In saying that, the app I'm migrating is quite small, so this may be a bigger deal for those with larger and more complex apps. I certainly wouldn't migrate for this reason alone, but I must admit, the speed improvements are quite impressive.
- Vite has matured a lot and the community has grown significantly compared to when I first created this CRA based app 2 years ago. If I was to evaluate both again for a new project, I would choose Vite this time around.
Given all of these, I thought it was time to make the switch.
The only real "disadvantage" to using Vite is that it doesn't type-check your code, it only transpiles it to Javascript. I personally think this is fine as many IDE's nowadays have great Typescript support.
Migration steps
Here are all of the steps I took to migrate from CRA to Vite. It's worth noting that I am migrating a Typescript project, though most of the steps should be similar to Javascript projects.
Let's get started! ๐
1. Install dependencies
We need to install these 4 dependencies:
npm install --save-dev vite @vitejs/plugin-react vite-tsconfig-paths vite-plugin-svgr
Note:
We need vite-tsconfig-paths in order to tell Vite how to resolve absolute paths from the tsconfig file. This way you can import modules like this:
import MyButton from 'components'
instead of
import MyButton from '../../../components'
We need vite-plugin-svgr in order to import SVGs as React components. For example:
import { ReactComponent as Logo } from './logo.svg'
.
2. Create Vite config file
Create a vite.config.ts
at the root of your project. This is where you specify all of the Vite configuration options.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import svgrPlugin from 'vite-plugin-svgr';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), viteTsconfigPaths(), svgrPlugin()],
});
3. Move index.html
Move the index.html
file from the /public
folder out to the root of your project. Find out why this is done here.
4. Update index.html
URLs are treated a bit differently in Vite, so we'll have to remove all references of %PUBLIC_URL%
. For example:
// Before
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
// After
<link rel="icon" href="/favicon.ico" />
We need to also add an entry point to the <body>
element:
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!-- Add entry point ๐ -->
<script type="module" src="/src/index.tsx"></script>
5. Update tsconfig.json
The main things you need to update in the tsconfig.json
file are target
, lib
and types
. For example:
{
"compilerOptions": {
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vite/client", "vite-plugin-svgr/client"],
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}
You can also take a look at Vite's tsconfig.json
file here for reference.
6. Create vite-env.d.ts
file
Since we're using Typescript, we need to create a vite-env.d.ts
file under the src
folder with the following contents:
/// <reference types="vite/client" />
7. Remove react-scripts
It's time to say goodbye to CRA once and for all. ๐ Run this command to uninstall it: npm uninstall react-scripts
.
We can also delete the react-app-env.d.ts
file.
8. Update scripts in package.json
Since we've removed react-scripts
, we now need to update the scripts within package.json
to reference vite
instead:
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
9. Start it up!
Once you run npm start
, you should now hopefully see your app open in the browser, powered by the amazing Vite.
If your app is small enough, this is all you might need to do. Otherwise, read on for more optional steps.
Optional steps
Here are some additional optional steps you can take, depending on your own project setup.
ESLint & Prettier
I've written a separate guide on how you can set up ESLint & Prettier here.
Tests
I've also written a guide on how you can replace Jest with Vitest here.
Environmental variables
It's pretty straightforward to migrate environmental variables, you simply rename REACT_APP_
to VITE_
within your .env
files. I just did a find and replace for these and it worked a treat.
Then, instead of referencing the variables using process.env.REACT_APP_
, you'll use import.meta.env.VITE_
.
You can even go one step further and specify types for your environment variables by creating an env.d.ts
file in the src
folder. For example:
interface ImportMetaEnv {
readonly VITE_TOKEN: string;
readonly VITE_CLIENT_ID: number;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
If you need to check for node environments (i.e development or production), you can do so by using the import.meta.env
object:
if (import.meta.env.DEV) {
// do something in development mode only
}
if (import.meta.env.PROD) {
// do something in production mode only
}
For more on environmental variables, see these Vite docs.
Change build output folder
In Vite, the default production build folder name is dist
, you can change this to CRA's default build
folder if needed. I found this useful as my CI/CD scripts all referenced build
.
// vite.config.ts
export default defineConfig({
...
build: {
outDir: 'build',
},
});
Automatically open the app on server start
Something I liked about CRA is that it would automatically open the app in the browser on server start. Vite has this option too:
// vite.config.ts
export default defineConfig({
...
server: {
open: true,
},
});
Change port number
If you need to change the port number from the default 3000
, specify like this:
// vite.config.ts
export default defineConfig({
...
server: {
port: 4000,
},
});
Benchmarks
Here about some benchmarks I recorded before and after the migration:
CRA | Vite | |
---|---|---|
npm install | 21 seconds | 9 seconds |
Server startup time (cold) | 11 seconds | 856 milliseconds |
Tests run | 17 seconds | 14 seconds |
Production build | 45 seconds | 17 seconds |
Production build size | 886 KB / gzip: 249 KB | 656.91 KB / gzip: 195.21 KB |
You can really see the improvements in the server startup time, but other than that there wasn't a huge difference. Bear in mind though that this was a very small app, so could be even more important for those larger apps.
Troubleshooting
Here are some errors you may come across:
1. When running npm start
, I see the following error: Error: Cannot find module 'node:path'
.
Answer: As per this issue, you have to make sure you have updated your node
version to 14.18.0
or v16+
.
2. When running npm start
, I see the following error: No matching export in MODULE_NAME for import TYPE_NAME
.
Answer: This often happens when you're using a library with a umd
bundle, where Vite expects an ESM
bundle. This happened to me with okta-auth-js and the fix was to specifcally tell Vite to load the umd
bundle in the Vite config file:
// vite.config.ts
export default defineConfig({
...
resolve: {
alias: {
'@okta/okta-auth-js': '@okta/okta-auth-js/dist/okta-auth-js.umd.js',
},
},
});
3. My screen is blank after running npm start
.
Answer: As per steps 3 and 4, make sure you've moved and updated the index.html
file.
Check out the Vite troubleshooting docs for more.
Final thoughts
Overall I've been very happy with Vite. The migration was straightforward and the developer experience has improved significantly. It can do everything CRA can, but with better implementations. If you found this article helpful or have additional tips, please leave a comment below. ๐
Want to see more?
I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on Twitter: https://twitter.com/cmacdonnacha
Bye for now ๐
Top comments (8)
Gladly I was able to switch to Vite when my project was still small. Vite is way better than Webpack on every aspect
Wait a sec - vite replaces webpack too ? I thought vite was just a CRA tooling replacement.
Yep, it uses ESBuild in dev, and Parcel for prod builds.
I didn't realize there were any other bundlers apart from webpack considering every article out there talks about webpack. What makes ESBuild and Parcel better than webpack that made Vite kickout out webpack ?
Speed mainly ๐โโ๏ธ
vitejs.dev/guide/why.html
Nice, everyone should switch from CRA to vite!
btw, for the eslint thing, there's a
vite-plugin-eslint
that you can use to make it function nearly identical to how CRA used it.Yep I did try that but found it didn't work too well with Prettier so decided to go with an approach that gives a little bit more control. ๐
Thanks for sharing ๐