React Vite: Fast Build Tool and Dev Server Setup (2026)

Vite has become the default build tool for new React projects — replacing Create React App with dramatically faster cold starts, near-instant HMR and a streamlined plugin ecosystem. By leveraging native ES modules in development and Rollup for production bundles, Vite achieves millisecond feedback loops even in large codebases.

Project Setup

# New React + TypeScript project
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev

# Available templates
# react, react-ts, react-swc, react-swc-ts

Vite dev server starts in under 300ms regardless of project size because it doesn't bundle — it serves ES modules directly to the browser and only transforms files on demand.

vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'  // SWC for faster transforms
import path from 'path'

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

  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@hooks': path.resolve(__dirname, './src/hooks'),
      '@utils': path.resolve(__dirname, './src/utils'),
    },
  },

  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },

  build: {
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
          ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
        },
      },
    },
  },
})

Environment Variables

Vite uses VITE_ prefix for variables exposed to the client:

# .env                    — loaded always
# .env.local              — loaded always, gitignored
# .env.development        — loaded in dev mode
# .env.production         — loaded in production build

VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App

# Server-only (no VITE_ prefix — NOT exposed to browser)
DATABASE_URL=postgresql://...
SECRET_KEY=...
// Access in React code
const apiUrl = import.meta.env.VITE_API_URL
const appTitle = import.meta.env.VITE_APP_TITLE
const isDev = import.meta.env.DEV
const isProd = import.meta.env.PROD
const mode = import.meta.env.MODE  // 'development' | 'production'

// TypeScript — extend ImportMetaEnv
// src/vite-env.d.ts
interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_TITLE: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

Path Aliases

// vite.config.ts — already shown above

// tsconfig.json — keep TypeScript in sync with Vite aliases
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"]
    }
  }
}

// Now use clean imports everywhere
import { Button } from '@/components/ui/Button'
import { useAuth } from '@/hooks/useAuth'
import { formatDate } from '@/utils/date'

Dev Server Proxy

// vite.config.ts
server: {
  proxy: {
    // Simple proxy
    '/api': 'http://localhost:8080',

    // With options
    '/auth': {
      target: 'http://localhost:8080',
      changeOrigin: true,
      secure: false,
    },

    // WebSocket proxy
    '/ws': {
      target: 'ws://localhost:8080',
      ws: true,
    },

    // Rewrite path
    '/old-api': {
      target: 'http://localhost:8080',
      rewrite: (path) => path.replace(/^\/old-api/, '/new-api'),
    },
  },
}

Key Plugins

npm install -D @vitejs/plugin-react-swc     # React with SWC (faster than Babel)
npm install -D vite-plugin-svgr             # Import SVGs as React components
npm install -D vite-tsconfig-paths          # Auto-sync tsconfig paths
npm install -D rollup-plugin-visualizer     # Bundle size visualization
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import svgr from 'vite-plugin-svgr'
import tsconfigPaths from 'vite-tsconfig-paths'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    react(),
    svgr(),           // import Logo from './logo.svg?react'
    tsconfigPaths(),  // auto-reads paths from tsconfig.json
    visualizer({
      open: true,
      filename: 'dist/stats.html',
    }),
  ],
})

Build Optimization

// Manual chunks — control vendor splitting
build: {
  rollupOptions: {
    output: {
      manualChunks(id) {
        if (id.includes('node_modules')) {
          if (id.includes('react')) return 'react-vendor'
          if (id.includes('@tanstack')) return 'tanstack'
          if (id.includes('lucide')) return 'icons'
          return 'vendor'
        }
      },
    },
  },
  // Chunk size warning threshold
  chunkSizeWarningLimit: 600,
  // Minify with esbuild (default) or terser
  minify: 'esbuild',
}

// Dynamic imports — Vite automatically splits at import() boundaries
const HeavyComponent = lazy(() => import('./HeavyComponent'))

// Preload hints — Vite injects modulepreload links automatically

Testing with Vitest

Vitest is Vite-native and shares the same config:

npm install -D vitest @testing-library/react @testing-library/user-event jsdom
// vite.config.ts — add test config
/// <reference types="vitest" />
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
    css: true,
  },
})

// src/test/setup.ts
import '@testing-library/jest-dom'

// Writing tests — same API as Jest
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Counter } from './Counter'

test('increments count on click', async () => {
  render(<Counter />)
  await userEvent.click(screen.getByRole('button', { name: /increment/i }))
  expect(screen.getByText('1')).toBeInTheDocument()
})

Migrating from CRA

  1. Install Vite: npm install -D vite @vitejs/plugin-react-swc
  2. Create vite.config.ts with React plugin
  3. Move public/index.html to root and add <script type="module" src="/src/main.tsx"></script>
  4. Rename REACT_APP_* env vars to VITE_*
  5. Replace process.env.REACT_APP_* with import.meta.env.VITE_*
  6. Update package.json scripts: "dev": "vite", "build": "vite build", "preview": "vite preview"
  7. Remove react-scripts dependency
Performance comparison: A typical CRA project cold-starts in 15–30 seconds. The same project with Vite starts in under 500ms. HMR updates that took 3–5 seconds in CRA happen in under 50ms with Vite.