How to migrate from create-react-app to Vite

An article to save you several Google and Stack Overflow searches

Stefan Todoran
5 min readOct 5, 2023

Gone are the days where create-react-app was the go-to tool for setting up a new React project. As web development needs have evolved, create-react-app has been unable to keep up, and between the emergence of improved modern alternatives and its abandonment by the React team, create-react-app is no longer recommended for new projects. Currently the module is sitting at over 450+ pull requests and counting, and if you create a new React project using create-react-app and --template typescript it comes with 8 vulnerabilities fresh out of the box!

A brand new TypeScript create-react-app comes with 8 vulnerabilities straight out the box.

Granted, CRA is a build tool, so these aren’t “real” vulnerabilities, but they sure are annoying. If you’d like to learn about why (many) vulnerabilities reported by npm audit relating to build tools are not of real concern, check out this article about why npm audit is broken. But we’re getting off track…

Vite, which means “fast” in French, lives up to its name by offering remarkable speed improvements during development. This tutorial will guide you through the process of migrating your React application from CRA to Vite, helping you unlock the benefits of improved performance and a streamlined development experience.

Migration Overview

These are the three main steps that the migration process will require:

  • Package Installation (removing create-react-app and installing Vite)
  • Entry-point Refactor (updating the index.html entry file)
  • Configuration (creating and updating scripts and configuration files)

Additionally, if you’d like to replace Jest with Vitest there are some extra steps you may have to undergo to complete your migration. If you are using Jest and would like to preserve your configuration, I’d recommend you begin the process with npm run eject, since CRA hides a lot of the setup for Jest. However, this tutorial will not cover migrating Jest to Vitest.

Package Installation

First, we need to uninstall react-scripts. Then, we need to install Vite and some of its dependencies. I’ve provided the commands for both npm and yarn below.

npm uninstall react-scripts
npm install vite @vitejs/plugin-react-swc vite-tsconfig-paths vite-plugin-svgr
yarn remove react-scripts
yarn add vite @vitejs/plugin-react-swc vite-tsconfig-paths vite-plugin-svgr

The exact plugins you will need for your project may vary, however if you are unsure of exactly which plugins will be necessary for your project’s specific needs, you can include those which I’ve listed above to get started and make changes later. To dive deeper into Vite plugins, check out the Vite plugins documentation.

Here are brief descriptions for each of the plugins included above:

  • vitejs/plugin-react-swc is a plugin for the Vite development server that replaces Babel with SWC for faster compilation speeds, among other benefits.
  • vite-tsconfig-paths is a package that helps the TypeScript compiler to resolve module imports using the paths defined in your tsconfig.json file.
  • vite-plugin-svgr is a plugin which transforms SVGs into React components using svgr under the hood. If you don’t use SVGs, you can skip installing this dependency.

Entry-point Refactor

Unlike CRA, which looks at public/index.html for its default entry point, Vite uses index.html in the root directory of your project. Given that your project was bootstrapped with CRA, your index.html file likely looks something like this:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="My React app bootstrapped with create-react-app!"
/>

<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>My React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

First, you will need to add a type="module" script tag pointing to src/index.tsx (or src/index.js). If you for some reason already have a script tag pointing to index.tsx, make sure that after you move public/index.html to the root directory you update the relative path.

Then, you will need to remove all references to %PUBLIC_URL%. In the example above there are 3, favicon.ico, logo192.png, and manifest.json, but you may have more. You may leave these files in the public directory. Your index.html file should now look something like this:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="My React app bootstrapped with create-react-app!"
/>

<link rel="apple-touch-icon" href="/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<title>My React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

Configuration

Next, there are two configuration files we need to create. First, in the root directory, create vite.config.ts and give it the content below. If you’ve used different plugins than this tutorial, your Vite configuration file may look different, and for more complex projects there may be more finagling you may need to do.

import {defineConfig} from "vite";
import react from "@vitejs/plugin-react-swc";
import svgr from "vite-plugin-svgr";

export default defineConfig({
plugins: [react(), svgr()],
build: {
outDir: "build",
},
});

For example, if you use the process.env.NODE_ENV variable to differentiate between development and production builds, you will need to modify your Vite configuration file to allow access to this mode.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import svgr from "vite-plugin-svgr";

export default ({ mode }: { mode: boolean }) => {
return defineConfig({
plugins: [react(), svgr()],
build: {
outDir: "build",
},
define: {
"process.env.NODE_ENV": `"${mode}"`,
},
});
};

The following two steps are only relevant for TypeScript projects.

Once you’ve set up your Vite configuration file, open the src directory. Then create a new file in this directory called vite-env.d.ts with the following content:

/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />

Lastly, you will need to update tsconfig.json (if you are working with TypeScript). Evidently every configuration file will look a little different, but the settings you should probably make sure you set are the following:

{
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2021"],
"allowJs": false,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": ["vite/client", "vite-plugin-svgr/client"] /* Skip type checking all .d.ts files. */
}
}

You are definitely going to want to set noEmit to false since Vite’s build process involves first running tsc. I forgot this option the first time around and had to manually delete a whole bunch of emitted JavaScript files since I hadn’t committed changes. Speaking of build processes, the last step we are missing is to replace the old react-scripts scripts in package.json with our new Vite scripts.

{
"name": "my-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
...
},
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
}

Conclusion

Congratulations! You’ve successfully completed you first migration from create-react-app to Vite! This represents a significant step forward in optimizing your React development process.

Every migration and every React project will undoubtedly look a bit different, but hopefully this tutorial helped you avoid some of the pitfalls along the way.

Keep exploring the world of web development, stay curious, and as always, happy coding!

--

--

Stefan Todoran

Hey there, I'm Stefan! Currently I'm an Applied Scientist at UiPath, and I'm also doing computer vision research with a geoscience + AI/ML lab called GeoSMART.