Migrating from Create React App (CRA) to Vite
Tags: vite, cra, react, javascript
Published: 2022-08-22 09:51:14

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:

- [Vite](https://vitejs.dev/)
- [@vitejs/plugin-react](https://github.com/vitejs/vite/tree/main/packages/plugin-react)
- [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths)
- [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr)

```bash
npm install --save-dev vite @vitejs/plugin-react vite-tsconfig-paths vite-plugin-svgr
```

> **Note:**

> We need [vite-tsconfig-paths](https://github.com/aleclarson/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](https://github.com/pd4d10/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.

```typescript
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()],
});
```

### 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](https://vitejs.dev/guide/#index-html-and-project-root).

### 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:

```html
<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:

```json
{
  "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"]
}
```

You can also take a look at Vite's `tsconfig.json` file [here](https://github.com/vitejs/create-vite-app/blob/master/template-react-ts/tsconfig.json) 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:

```typescript
/// <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:

```json
"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](https://cathalmacdonnacha.com/setting-up-eslint-prettier-in-vitejs).

### Tests

I've also written a guide on how you can replace Jest with Vitest [here](https://cathalmacdonnacha.com/migrating-from-jest-to-vitest).

### 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:

```typescript
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](https://vitejs.dev/guide/env-and-mode.html).

### 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`.

```typescript
// 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:

```typescript
// 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:

```typescript
// vite.config.ts

export default defineConfig({
  ...
  server: {
    port: 4000,
  },
});
```

## 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](https://github.com/vitejs/vite/issues/9113#issuecomment-1184319357), 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](https://github.com/okta/okta-auth-js/issues/641) and the fix was to specifcally tell Vite to load the `umd` bundle in the Vite config file:

```typescript
// 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](Troubleshooting) 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](https://twitter.com/cmacdonnacha)

Bye for now 👋