DEV Community

Henrique Jensen
Henrique Jensen

Posted on

Migrating from create-react-app to Vite: A Quick and Easy Guide

In recent times, React has shifted its recommendation from using create-react-app (CRA) as the starting point for new projects. Instead, it now encourages developers to use a framework for most projects. However, for smaller projects, a full-fledged framework might not be necessary. In such cases, Vite) emerges as a promising option. Vite is not a framework like Next.js or Remix, but it offers several features that make it an excellent choice for small projects, such as personal portfolios, like my portfolio. In this blog post, we will walk you through the steps to migrate from create-react-app to Vite for your next project.

Migration steps

These migration steps assume that we have a CRA project with Typescript.

  1. Removing CRA
  2. Installing Vite dependencies
  3. Moving index.html
  4. Adding vite.config.ts
  5. Adding vite-env.d.ts
  6. Adding vite scripts
  7. Fixing tsconfig.json
  8. Migrating from Jest to Vitest
  9. Extra

Step 1 - Removing CRA

The first step is to uninstall create-react-app from your project.

npm uninstall react-scripts
Enter fullscreen mode Exit fullscreen mode

Step 2 - Installing Vite dependencies

Next, install the required dependencies for Vite.

npm install vite @vitejs/plugin-react-swc vite-tsconfig-paths vite-plugin-svgr
Enter fullscreen mode Exit fullscreen mode

Note: Depending on your specific needs, you can choose a different plugin from the official Vite plugins documentation.

Step 3 - Moving index.html

create-react-app uses public/index.html as the default entry point, while Vite looks for index.html at the root level. To make the transition, move your index.html to the root directory and update the script tag accordingly.

<!-- index.html -->
<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>
Enter fullscreen mode Exit fullscreen mode

Step 4 - Adding vite.config.ts

Create a vite.config.ts file at the root of your project with the following content:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  base: '/',
  plugins: [react()]
})
Enter fullscreen mode Exit fullscreen mode

Step 5 - Adding vite-env.d.ts

Create a vite-env.d.ts file inside the src folder with the following content:

/// <reference types="vite/client" />

Enter fullscreen mode Exit fullscreen mode

Step 6 - Adding vite scripts

Replace the existing CRA scripts in package.json with Vite scripts.

 "scripts": {
    "start": "vite",
    "build": "tsc && vite build",
    "serve": "vite preview"
}
Enter fullscreen mode Exit fullscreen mode

Step 7 - Fixing tsconfig.json

Update your tsconfig.json to its final version.

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "noFallthroughCasesInSwitch": true,
    "jsx": "react-jsx",
    "types": ["vite/client", "vite-plugin-svgr/client"]
  },
  "include": ["src"]
}
Enter fullscreen mode Exit fullscreen mode

Step 8 - Migrating from Jest to Vitest

As we are moving to Vite, another good idea is to consider migrating from Jest to Vitest as well. Here are the steps:

8.1 - Install Vitest dependencies:

npm i -D jsdom vitest @vitest/coverage-v8
Enter fullscreen mode Exit fullscreen mode

8.2 - Update vite.config.ts to include Vitest configurations:

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  base: '/',
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/setupTests.ts',
    css: true,
    reporters: ['verbose'],
    coverage: {
      reporter: ['text', 'json', 'html'],
      include: ['src/**/*'],
      exclude: [],
    }
  },
})
Enter fullscreen mode Exit fullscreen mode

8.3 - Update package.json with Vitest scripts:

  "scripts": {
    "start": "vite",
    "build": "tsc && vite build",
    "serve": "vite preview",
    "test": "vitest",
    "test:coverage": "vitest run --coverage --watch=false"
  },
Enter fullscreen mode Exit fullscreen mode

Step 9 - Extras

If you use GitHub Actions to push your code to GitHub Pages, you'll need to update the workflow file as Vite generates a dist folder when we run npm run build.

name: GitHub Pages

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  deploy:
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: "16"

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - run: npm ci
      - run: npm run build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        if: github.ref == 'refs/heads/master'
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./dist

Enter fullscreen mode Exit fullscreen mode

With these steps, you should now have successfully migrated your create-react-app project to Vite, enjoying its benefits and optimizing your development process for smaller projects. Happy coding!

Top comments (8)

Collapse
 
dmikester1 profile image
Mike Dodge

Thank you for a great write-up! This helped a lot for converting over a fairly large React app.
That being said, there were a few "gotchas" I encountered that were not mentioned. Like in a few other comments, I had to remove all occurrences of %PUBLIC_URL% from the index.html file. I had to convert over about 95% of my .js files to .jsx. I was having an issue importing a .m4v video file into a couple of my components. I learned I had to add this 'assetsInclude' line to vite.config.ts:
export default defineConfig({
assetsInclude: ['**/*.m4v'],
...

Lastly, I had to rename my env vars in my .env file from REACT_APP_ABC to VITE_ABC and then wherever I used them in my code I had to change process.env.REACT_APP_ABC to import.meta.env.VITE_ABC. Also remember the current running environment(production, development, etc) is now at import.meta.env.MODE.

Collapse
 
mezieb profile image
Okoro chimezie bright

Well done 👍

Collapse
 
nelwhix profile image
Nelwhix • Edited

Nice article, I followed this and got an Internal URI malformed error on the browser. then on the terminal I got that I have to rename every file in my project that uses jsx to a .jsx extension and I am trying to migrate a larger codebase with tons of files. Please any help?

Collapse
 
miionu profile image
Rospars Quentin • Edited

You only need to remove the %PUBLIC_URL% in the index.html file. It worked for me :)

Collapse
 
yousefzw profile image
yousefZw

I replaced %PUBLIC_URL% to public. E.g.

<link rel="icon" href="/public/favicon.ico" />

It works now.

Collapse
 
briandesousa1 profile image
Brian De Sousa

Nice work!

If you want to keep index.html under src, you could also create the vite.config.ts under src and change the start script in step 6 to vite serve src to serve the code from an alternate directory.

Collapse
 
honeyman22 profile image
Nishan Bhattarai

I have done all above mantioned steps but got this error

$ npm start

tsreact@0.1.0 start
vite

node:internal/modules/cjs/loader:1327
return process.dlopen(module, path.toNamespacedPath(filename));
^

Error: The specified module could not be found.
\?\D:\My Folder (AITC OFFICE)\aitc-admin-panel\node_modules\@rollup\rollup-win32-x64-msvc\rollup.win32-x64-msvc.node
at Module._extensions..node (node:internal/modules/cjs/loader:1327:18)
at Module.load (node:internal/modules/cjs/loader:1091:32)
at Module._load (node:internal/modules/cjs/loader:938:12)
at Module.require (node:internal/modules/cjs/loader:1115:19)
at require (node:internal/modules/helpers:130:18)
at Object. (D:\My Folder (AITC OFFICE)\aitc-admin-panel\node_modules\rollup\dist\native.js:60:48)
at Module._compile (node:internal/modules/cjs/loader:1241:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1295:10)
at Module.load (node:internal/modules/cjs/loader:1091:32)
at Module._load (node:internal/modules/cjs/loader:938:12) {
code: 'ERR_DLOPEN_FAILED'
}

Node.js v20.9.0

Collapse
 
yuval1982 profile image
yuval1982

Great guide, thanks!

However, I needed 2 more changes to make my app work (our service is served as a framework in our company's platform):

  1. change build.outDir in vite.config.js to 'build' - vite's default is 'dist', and our platform is looking for the manifest file inside 'build'.
  2. since our url is relative to the platform, i changed homepage in package.json from './' to './wlmvmc' (that's our service' name) and the base in vite.config.js to './', so the correct paths will be injected to index.html