4 Reasons Why You Should Prefer Vite Over Create-React-App (CRA)

Create React App (CRA) has long been the go-to tool for most developers to scaffold React projects and set up a dev server. It offers a modern build setup with no configuration.

But, we see increased development and build time when the project size increases. This slow feedback loop affects developers’ productivity and happiness.

To address these issues, there is a new front-end tooling in the ecosystem: Vite!

Unlike CRA, Vite does not build your entire application before serving, instead, it builds the application on demand. It also leverages the power of native ES modules, esbuild, and Rollup to improve development and build time.

Let’s take a look in detail at what can make CRA slow.

Why is CRA slow?

CRA uses a webpack under the hood. The webpack bundles the entire application code before it can be served. With a large codebase, it takes more time to spin up the development server and reflecting the changes made takes a long time.

The diagram below shows how all the code must be bundled in order to start a bundle-based dev server.

What is Vite?

Vite is a next-generation, front-end tool that focuses on speed and performance.

It consists of two major parts:

  • A development server that provides rich feature enhancements over native ES modules: fast Hot Module Replacement (HMR), pre-bundling, support for typescript, jsx, and dynamic import.
  • A build command that bundles your code with Rollup, pre-configured to output optimized static assets for production.

Why prefer Vite over create-react-app?

1. Faster spin-up of the development server

Unlike a CRA or bundler-based build setup, Vite does not build the entire application before serving. It divides the application modules into two categories: dependencies and source code.

  1. Dependencies are plain JavaScript that do not change often during development (large component libraries like mui).
  • Vite pre-bundles these dependencies using esbuild, which is 10-100x faster than JavaScript-based bundlers.
  • Pre-bundling ensures that each dependency maps to only one HTTP request, avoiding HTTP overhead and network congestion.
  • As dependencies do not change, they can also be cached and we can skip pre-bundling.
  1. Source code often contains non-plain JavaScript that needs transforming (e.g. JSX, CSS) and will be edited very often.
    Vite serves source code over native ESM.

ESM stands for EMACScript modules. It is a recent addition to the JavaScript language specification that deals with how modules are loaded in the Javascript application.

Benefits of building a dev server around native ESM

  • There is no need for bundling. That’s a big chunk of work that you don’t have to do anymore.
  • Native EMS is on-demand by nature. Based on the browser request, Vite transforms and serves source code on demand. If the module is not needed for some screen, it is not processed. The diagram shows how the native ESM-based dev server starts with only the required modules loaded.

2. Less waiting time for reflecting file updates

As the size of the codebase increases, file updates get slower in CRA. This is not the case with Vite.

Let’s see how Vite handles file updates.

  • In Vite, Hot Module Replacement(HMR) is performed over native ESM. When a file is edited, Vite invalidates the chain between the edited module and its closest HMR boundary. This makes HMR updates simple and fast regardless of the size of your application.
  • Modules are fetched over HTTP requests. Vite leverages HTTP headers for caching to speed up full-page reloads. Source code module requests are made conditional via 304 Not Modified. And dependency module requests are cached via Cache-Control: max-age=31536000, immutable.

3. Improved build performance

Vite ships with a pre-configured build command that bakes in many performance optimizations out of the box.

While code splitting, Webpack, and Rollup produce a common chunk (code that is shared between two or more other chunks). This, when combined with dynamic import, can lead to many network round trips.

As shown in the image in unoptimized scenarios, when async chunk A is imported, the browser does not know that it needs common chunk C without first requesting and parsing A. After figuring out that it needs common chunk C, the browser then imports it, leading to an extra network round trip.

Entry ---> A ---> C

Whereas, Vite rewrites code-split dynamic import calls with a preload step. When A is requested, C is fetched in parallel. This eliminates network round trips.

Entry ---> (A + C)

Vite automatically extracts the CSS used by modules in an async chunk and generates a separate file for it. The CSS file is automatically loaded via a <link> tag when the associated async chunk is loaded.

4. Rich features

Vite provides out-of-the-box support for TypeScript, JSX, CSS, and more.

  • TypeScript

Vite supports importing `.ts` files out of the box. It uses esbuild to transpile TypeScript into JavaScript which is about 20~30x faster than vanilla tsc. The HMR updates can reflect in the browser in under 50ms.

  • JSX

Viet supports `.jsx` and `.tsx` files out of the box. JSX transpilation is handled via esbuild.

  • CSS

Importing `.css` files injects content to the page via a` <style>` tag with HMR support.

Vite provides support for .scss, .sass,.less, .styl and .stylus files, PostCSS, and CSS. modules.

  • JSON

JSON files can be directly imported into Vite. It also supports named imports.

  • Dynamic Imports

Dynamic import with variables can be used with Vite.

Considering the aforementioned benefits of Vite, I decided to migrate an existing project from CRA to Vite. Let’s take a look at how I did it.

Steps to migrate CRA to Vite

  1. Remove the react-scripts dependency from the package.json.
  2. Add sass in package.json, if you are using CSS or SCSS.
yarn add -D sass
npm i --save-dev sass

3. Add vite and @vitejs/plugin-react as dev dependencies.

"devDependencies": {
"@vitejs/plugin-react": "1.3.2",
"vite": "2.7.0"

4. Replace the start and build scripts as below:

"scripts": {
"start": "vite",
"build": "vite build"

5. Create a file vite.config.js in the root of the project with the below code:

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

export default () => {
return defineConfig({
plugins: [react()],

6. Move the index.html file from the public folder to the root of the project.

7. Remove all the occurrences of %PUBLIC_URL% from index.html.
The lines affected should look like the following lines after the change:

<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/logo192.png" />

8. Add the script tag below into the body of index.html:

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

NOTE: In CRA, src/index has extension of .js, change it to jsx.

9. Change the extension of .js files to .jsx, if they contain jsx syntax.
If not changed, you will get the following error:

[plugin:vite:import-analysis] Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.

Want to know why this change is needed?
Read through Evan You’s tweet explaining why components containing jsx syntax should have a .jsx extension.

10. Update all environment variables from REACT_APP to VITE.

11. Vite does not support absolute path by default.
You need to either use a relative path or add an alias.

Originally published at https://semaphoreci.com on September 22, 2022.



Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com