Before Vite, starting a development server on a large React project could take 30-60 seconds. Every file save triggered a re-bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. that felt painfully slow. Vite was built to fix this, and it approaches the problem in a fundamentally different way. When AI tools generate conflicting Vite configurations, understanding how Vite actually works lets you pick the right answer.
Why Vite is fast
Traditional bundlers like Webpack process your entire application before the dev server can start. If you have 500 files, all 500 get bundled before you see anything in the browser.
Vite skips that step entirely during development. It serves files on demand using the browser's native ES moduleWhat is es modules?The official JavaScript module standard using import and export - enabled in Node.js via "type": "module" in package.json. support. When the browser requests a file, Vite transforms just that one file and serves it. Files that are never requested are never processed.
Webpack: Bundle all 500 files → start server → open browser (30s)
Vite: Start server instantly → browser requests file → transform on demand (<1s)Think of it like a restaurant. Webpack pre-cooks every dish before opening. Vite takes your order and cooks only what you asked for. The second approach is obviously faster when you only need a few dishes.
Creating a Vite project
The fastest way to start is with the official scaffoldingWhat is scaffolding?Auto-generating the basic file structure and starter code for a project or feature so you don't have to write it from scratch. command:
# Create a new project (prompts you to choose a framework)
npm create vite@latest my-app
# Available frameworks:
# Vanilla, React, Vue, Svelte, Preact, Lit, Solid, Qwik
cd my-app
npm install
npm run dev
# → http://localhost:5173Your project is running in seconds, not minutes.
Project structure
A fresh Vite + React project has a clean, minimal structure:
my-app/
├── index.html ← entry point (HTML, not JS - this is unusual)
├── vite.config.ts ← Vite configuration
├── package.json
├── tsconfig.json
├── public/ ← static files copied as-is (favicon, robots.txt)
│ └── favicon.ico
└── src/
├── main.tsx ← JavaScript entry point (loaded by index.html)
├── App.tsx ← root React component
├── App.css
└── assets/ ← images/fonts imported in code (processed by Vite)
└── logo.svgThe key difference from other tools: Vite's entry point is index.html, not a JavaScript file. The HTML file contains a <script> tag pointing to your main.tsx:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head><title>My App</title></head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>webpack.config.js alongside vite.config.ts when autocompleting config files. If you see both, delete the webpack one, the two tools are incompatible and having both will cause confusion.The vite.config.ts file
Most Vite projects need very little configuration. Here is a typical React setup:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000, // default is 5173
open: true, // open browser on start
proxy: {
'/api': 'http://localhost:8080' // proxy API calls to backend
}
},
resolve: {
alias: {
'@': '/src' // import from '@/components/...' instead of '../../'
}
},
build: {
outDir: 'dist', // output directory for production build
sourcemap: true // generate source maps for debugging
}
});Each section handles a different concern. You only add the sections you actually need, an empty config file with just plugins: [react()] is completely valid.
| Config section | Purpose | When you need it |
|---|---|---|
plugins | Add framework support (React, Vue, etc.) | Always |
server | Dev server port, proxy, HTTPS | When defaults don't work |
resolve.alias | Path shortcuts (@/ instead of ../../) | Medium+ projects |
build | Output dir, source maps, chunk splitting | When deploying |
css | PostCSS, preprocessors, CSS modules config | When using Sass/Less |
define | Global constants replaced at build time | Feature flags, versions |
Hot Module ReplacementWhat is hot module replacement?A dev server feature that swaps updated code into your running app without a full page reload, preserving your current state. (HMR)
HMR is Vite's most immediately useful feature. When you save a file, Vite pushes only the changed moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. to the browser. Your component state is preserved, there is no full page reload, and you see the change in under 100ms.
You edit App.tsx → Vite detects the change (via file watcher)
→ Transforms only the changed file
→ Sends it to the browser via WebSocket
→ Browser swaps the module in place
→ Component state is preservedThis matters most when working on stateful UI, a multi-step form, a shopping cart, a game. Without HMR, every save resets you to the initial state.
Environment variables
Vite has a strict security rule: only variables prefixed with VITE_ are exposed to client-side code. Everything else is invisible to the browser, preventing accidental leaks of secrets.
# .env file
DATABASE_URL=postgres://secret@db:5432 # NOT exposed to browser
API_SECRET=sk-abc123 # NOT exposed to browser
VITE_API_URL=https://api.example.com # Exposed to browser
VITE_APP_TITLE=My App # Exposed to browser// In your React code
console.log(import.meta.env.VITE_API_URL); // "https://api.example.com"
console.log(import.meta.env.DATABASE_URL); // undefined (intentional)
console.log(import.meta.env.VITE_APP_TITLE); // "My App"
console.log(import.meta.env.DEV); // true in dev, false in prod
console.log(import.meta.env.PROD); // false in dev, true in prod.env files with variables like REACT_APP_API_URL (the Create React App convention) instead of VITE_API_URL. If you are using Vite, the REACT_APP_ prefix does nothing, your variable will be undefined. Always use the VITE_ prefix.After changing .env files, you must restart the dev server. Vite reads environment variables at startup, not on every request.
Common Vite errors and fixes
| Error message | Cause | Fix |
|---|---|---|
Cannot resolve module './Button' | Wrong import path or case mismatch | Check spelling; Linux is case-sensitive |
import.meta.env.VITE_X is undefined | Missing VITE_ prefix or server not restarted | Rename variable, restart dev server |
[plugin:vite:react-babel] ... | JSX in a .js file instead of .jsx/.tsx | Rename file to .jsx or .tsx |
Pre-transform error | Circular dependency or invalid import | Check for import cycles |
Port 5173 is already in use | Another Vite instance is running | Kill the other process or change port |
Quick reference
| Command | What it does |
|---|---|
npm create vite@latest | Scaffold a new Vite project |
npm run dev | Start dev server with HMR (port 5173) |
npm run build | Create optimized production build in dist/ |
npm run preview | Serve dist/ locally to test the production build |
// vite.config.ts, typical React project configuration
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
},
resolve: {
alias: {
'@': '/src'
}
},
build: {
outDir: 'dist',
sourcemap: true
}
});