Migrating from Create React App (CRA) to Vite

Migrating from Create React App (CRA) to Vite

Learn how to migrate your CRA app over to Vite.


7 min read

Featured on Hashnode

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. You can also use vite-plugin-checker if you prefer having type errors directly reported in the browser.

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


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 to import SVGs as React components. For example:

import Logo from './logo.svg?react'.

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 svgr from 'vite-plugin-svgr';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
      include: '**/*.svg?react',

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-jsx"
  "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.


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,


Here about some benchmarks I recorded before and after the migration:

npm install21 seconds9 seconds
Server startup time (cold)11 seconds856 milliseconds
Tests run17 seconds14 seconds
Production build45 seconds17 seconds
Production build size886 KB / gzip: 249 KB656.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 this could be even more important for those larger apps.


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 specifically 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 ๐Ÿ‘‹