Skip to main content

How to Migrate from Create React App to Vite

·PkgPulse Team

TL;DR

CRA to Vite migration takes 30-60 minutes and gives you a 40x faster dev server. Create React App was deprecated in 2023 and should not be used for new projects or maintained indefinitely. The migration is well-documented and mechanical: remove react-scripts, install Vite, move index.html, rename env vars from REACT_APP_ to VITE_, update imports from process.env to import.meta.env. Most apps migrate with under 20 file changes.

Key Takeaways

  • Dev server: 8,000ms → 200ms — CRA bundles everything; Vite serves ESM natively
  • 5 main changes: react-scriptsvite, index.html location, env vars, tsconfig, scripts
  • Tests: CRA used Jest; switch to Vitest (see Jest → Vitest guide) or keep Jest standalone
  • CRA was deprecated 2023 — no security patches on CRA vulnerabilities
  • TypeScript CRA: same migration, just add TypeScript-specific steps

The Full Migration

1. Remove react-scripts

# Remove CRA
npm uninstall react-scripts

# Verify CRA is gone
cat package.json | grep react-scripts  # Should show nothing

2. Install Vite

npm install -D vite @vitejs/plugin-react

# For TypeScript CRA:
npm install -D vite @vitejs/plugin-react typescript

# Common extras:
npm install -D vite-tsconfig-paths   # Reads paths from tsconfig
npm install -D vite-plugin-svgr      # SVG as React component (if you use it)

3. Create vite.config.ts

// vite.config.ts — at project root
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import svgr from 'vite-plugin-svgr';  // Only if you import SVGs

export default defineConfig({
  plugins: [
    react(),
    tsconfigPaths(),   // Handles paths from tsconfig.json
    svgr(),            // If you use: import { ReactComponent as Logo } from './logo.svg'
  ],
  server: {
    port: 3000,        // Match CRA's default port
    open: true,        // Auto-open browser (like CRA)
  },
  build: {
    outDir: 'build',   // CRA used 'build'; Vite default is 'dist'
  },
});

4. Move index.html to Root

# CRA: public/index.html
# Vite: index.html at project root

mv public/index.html ./index.html
<!-- index.html — edit after moving -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- CRA: %PUBLIC_URL%/favicon.ico — replace with /favicon.ico -->
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!-- ADD THIS LINE — Vite requires explicit entry point -->
    <script type="module" src="/src/index.tsx"></script>
    <!-- ↑ .tsx for TypeScript, .jsx for JavaScript -->
  </body>
</html>

5. Update package.json Scripts

// Before (CRA):
{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

// After (Vite):
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest run"
  }
}

6. Rename Environment Variables

# CRA: REACT_APP_ prefix → Vite: VITE_ prefix
# Edit your .env files:

# Before (.env, .env.local, .env.production):
REACT_APP_API_URL=https://api.example.com
REACT_APP_GOOGLE_MAPS_KEY=abc123

# After:
VITE_API_URL=https://api.example.com
VITE_GOOGLE_MAPS_KEY=abc123
// Update all usage in your code:
// Before:
const apiUrl = process.env.REACT_APP_API_URL;
if (process.env.NODE_ENV === 'development') { ... }

// After:
const apiUrl = import.meta.env.VITE_API_URL;
if (import.meta.env.DEV) { ... }  // Boolean, not string
if (import.meta.env.PROD) { ... }

7. Update TypeScript Config

// tsconfig.json — update for Vite
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    "moduleResolution": "bundler",   // ← Key change for Vite
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,                   // Vite handles emit
    "jsx": "react-jsx",              // Same as CRA

    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    "types": ["vite/client"]         // Add Vite's types (import.meta.env)
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
// tsconfig.node.json (for vite.config.ts itself)
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}

CRA-Specific Gotchas

SVG Imports

// CRA: SVG as React component (auto-configured)
import { ReactComponent as Logo } from './logo.svg';

// Vite: requires vite-plugin-svgr
import { ReactComponent as Logo } from './logo.svg?react';
// Note the ?react query parameter

// Or update import syntax with svgr plugin configured:
import Logo from './logo.svg?react';  // Default export as component

Public Folder References

<!-- CRA: use %PUBLIC_URL% in index.html -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

<!-- Vite: use root-relative paths (no variable needed) -->
<link rel="manifest" href="/manifest.json" />

window.__env__ / Dynamic Runtime Config

// CRA apps sometimes inject env at runtime via public/env-config.js
// Vite bakes env at build time — same as CRA's default behavior
// If you need runtime config, use a server endpoint or meta tags

Verify Migration Success

# Start dev server
npm run dev
# ✅ Should start in <500ms at localhost:3000

# Build for production
npm run build
# ✅ Should complete, output in /build

# Preview production build
npm run preview
# ✅ Should serve from /build

# Check for CRA remnants
grep -r "react-scripts\|REACT_APP_\|process\.env\.NODE_ENV" src/
# ✅ Should find nothing after migration

Compare Vite and Webpack package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.