Upgrade from Create React App to Vite

A step by step guide to migrate a React project from Create React App to Vite

While most projects will rely on modern and feature-complete React frameworks like Next.js, you may have to work on a project that is still based on Create React App.

Create React App used (also knows as CRA) used to be the go-to tool to quickly create React projects, but it's now getting old and deprecated. And it's slow. Very slow. Vite is a modern tool that is way faster. By migrating a medium-size project from Create React App to Vite, I could cut the build time of a project from 15 minutes to less than 5 minutes on a CI server, and from 3 minutes to 30 seconds locally.

It is therefore really worth migrating a project based on CRA to Vite. This article will show you how to do it step by step. The migration should be a pretty straightforward process, but there are a few things to know to make it work.

Before you start

It may be a good idea to create a side default project with Vite to be able to look at its structure, and to be able to copy/paste some files from it.

npm create vite@latest

Choose following options :

  • React
  • JavaScript + SWC (I didn't choose TypeScript as the project I migrated was in JS)

Upgrade package.json file

Open the package.json file of your project and copy all devDependencies from the Vite project you created in the previous step to your project's package.json file.

In my case:

{
  // ...
  "devDependencies": {
    "devDependencies": {
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@vitejs/plugin-react-swc": "^3.3.2",
    "eslint": "^8.45.0",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "vite": "^4.4.5"
  }
}

Set your project type as module:

{
  // ...
  "type": "module"
}

Replace scripts section with Vite's one (keep your own custom scripts if you have some):

{
  // ...
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  }
}

Remove eslintConfig and browserslist entries from package.json file.

Copy vite.config.js file from the sample project you created earlier to your project's root directory.

Remove Create React App

Remove at least the react-scripts package:

npm remove react-scripts

You can also clean up some other dependencies and files:

npm remove web-vitals

And delete the following files:

  • src/reportWebVitals.js

Remove this line from src/index.jsx:

import reportWebVitals from "./reportWebVitals";

along with the call to the reportWebVitals() function.

Update index.html file

  • Move index.html file from public directory to the root's directory of the project.
  • Inside that file, replace %PUBLIC_URL%/ with / in index.html file.

After the following line:

<div id="root"></div>

Add:

<script type="module" src="/src/index.jsx"></script>

Again, you can have a look at the index.html file from the sample project you created earlier to make sure you have the right content.

Update environment variables

Environment variables are now handled differently in Vite, and you will have to update your codebase to make it work.

Replace in your project's code (.js and .jsx, and .env file itself):

  • process.env -> import.meta.env
  • REACT_APP_ -> VITE_ (use case sensitive search)

SVG files

If you import some SVG files as components, you will have to update your code to make it work with Vite, as this is not supported by default. To check whether you have some SVG files imported as components, search for import { ReactComponent as in your codebase. Full example :

import { ReactComponent as Logo } from './logo.svg'

Add the vite-plugin-svgr plugin to your project:

npm i vite-plugin-svgr

Configure it in vite.config.js:

import svgr from 'vite-plugin-svgr'

//...

export default defineConfig({
  plugins: [
    react(),
    svgr(),
    // ...
  ],
})

Rename .js files that are using JSX to .jsx

You may have used the .js extension including for files that are not purely Javascript, but that are using JSX. This was possible with Create React App, but is not supported by Vite. All files that contain some React components should have the .jsx extension.

  1. Search for files with the .js extension that are using JSX (search for import React may be a good way to check it).
  2. Copy all paths of the files matching the search. A good way to do it may be to use the VS Code's "Open in Editor" feature and then copy all paths from the editor.
  3. Rename all these files to .jsx using the rename command of your editor. This may be done by writing a small Bash script that you will execute in the terminal. Your script may look like a list of mv commands like this:
mv src/components/MyComponent.js src/components/MyComponent.jsx
mv src/components/MyOtherComponent.js src/components/MyOtherComponent.jsx

Alias updates

Aliases are an excellent way to make your imports shorter, more readable, and easier to maitain. If you have a lot of imports using relative paths like this:

import { Button } from '../../components/Button'

I really recommend you to use aliases to make them look like this:

import { Button } from '@components/Button'

You get two main benefits from this:

  • No need to type tons of ../ to go up in the directory tree, and having to count them
  • Files can be easily moved around without having to update all the imports.

You may have used aliases in your project, and you will have to update them to make them work with Vite.

In Create React App, you may have used the jsconfig.json file to define aliases. Example:

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

To make it work with Vite, you will have to update it like this:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "src/*": ["src/*"]
    }
  }
}

And add to the vite.config.js file:

resolve: {
  alias: {
    src: '/src',
  },
},

Now, any file in the src directory can be imported using the src alias. Example:

import { Button } from 'src/components/Button'

Automatically open the browser when running the dev server

By default, Vite doesn't open the browser when running the dev server. To make it work, add the open option to the vite.config.js file:

//...

export default defineConfig(() => {
  return {
    server: {
      open: true,
    },
    // ...
  }
})

Note that by default, you will run the dev server with the npm run dev command instead of npm start.

Keep the build directory

By default, Vite builds the project in a dist directory. If you want to keep the build directory, you can add the build.outDir option to the vite.config.js file:

//...

export default defineConfig(() => {
  return {
    plugins: [
      // ...
    ],
    // ...
    build: {
      outDir: './build',
    },
  }
})

You should now be able to run the dev server with npm run dev and build the project with npm run build.

Add some ESLint rules

The default Vite configuration enables an ESLint rule to make sure fast refresh works properly and get a warning if some files need to be refactored. If you are using ESLint (which is highly recommended), you may want to add this rule to your project. To do so, add the following lines to your .eslintrc file:

module.exports = {
  // ...
  rules: {
    // ...
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
}

That's it! You should now have a working project with Vite. You can now enjoy the speed of the dev server and the build process!