DEV Community

Cover image for Migrating from Create React App (CRA) to Vite
Cathal Mac Donnacha 🚀
Cathal Mac Donnacha 🚀

Posted on • Originally published at cathalmacdonnacha.com

Migrating from Create React App (CRA) to Vite

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
Enter fullscreen mode Exit fullscreen mode

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()],
});
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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"
},
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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',
  },
});
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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',
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
ninhnd profile image
Dang Ninh

Gladly I was able to switch to Vite when my project was still small. Vite is way better than Webpack on every aspect

Collapse
 
anjanesh profile image
Anjanesh Lekshminarayanan

Wait a sec - vite replaces webpack too ? I thought vite was just a CRA tooling replacement.

Collapse
 
cathalmacdonnacha profile image
Cathal Mac Donnacha 🚀

Yep, it uses ESBuild in dev, and Parcel for prod builds.

Thread Thread
 
anjanesh profile image
Anjanesh Lekshminarayanan • Edited

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 ?

Thread Thread
 
cathalmacdonnacha profile image
Cathal Mac Donnacha 🚀 • Edited

Speed mainly 🏃‍♂️

vitejs.dev/guide/why.html

Collapse
 
brense profile image
Rense Bakker

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.

Collapse
 
cathalmacdonnacha profile image
Cathal Mac Donnacha 🚀

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. 👍

Collapse
 
bwca profile image
Volodymyr Yepishev

Thanks for sharing 🙂