Vite: Next-Gen Frontend Build Tool Explained
In this tutorial, you'll learn Vite including project setup, HMR, CSS and asset handling, environment variables, build optimization, and migrating from Webpack to Vite for faster development.
Why Vite Matters
Traditional bundlers like Webpack process your entire application before you can see any change. As projects grow, this wait time becomes painful. Vite takes a different approach: it serves source files natively during development and bundles only when you build for production. The result is instant server start and near-instant Hot Module Replacement.
By the end of this guide, you will scaffold a Vite project, configure plugins, optimize builds, and migrate an existing project from Webpack.
What is Vite?
Vite (French for "quick") is a build tool created by Evan You, the creator of Vue.js. It leverages native ES modules in the browser during development and uses Rollup for production builds.
flowchart LR A[Vite] --> B[Dev Server] A --> C[Build Command] B --> D[Native ESM] B --> E[HMR] B --> F[esbuild Transpile] C --> G[Rollup Bundle] G --> H[Code Splitting] G --> I[Tree Shaking] G --> J[Asset Optimization]
Project Setup
# Create a Vite project
npm create vite@latest my-app
# Or specify a template
npm create vite@latest my-app -- --template react-ts
# Navigate and install
cd my-app
npm install
# Start dev server
npm run dev
Expected Output
$ npm run dev
VITE v6.0.0 ready in 245ms
Local: http://localhost:5173/
Network: use --host to expose
Project Structure
my-app/
├── index.html # Entry point
├── vite.config.js # Vite configuration
├── package.json
├── public/ # Static assets
│ └── favicon.ico
├── src/
│ ├── main.jsx # Application entry
│ ├── App.jsx
│ ├── App.css
│ └── assets/ # Processed assets
│ └── logo.svg
└── node_modules/
Configuration
Vite configuration lives in vite.config.js or vite.config.ts.
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,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
minify: 'esbuild',
},
})
Hot Module Replacement
HMR in Vite updates only the changed module without reloading the page.
// src/counter.js
export function setupCounter(element) {
let counter = 0
const setCounter = (count) => {
counter = count
element.innerHTML = `count is ${counter}`
}
element.addEventListener('click', () => setCounter(counter + 1))
setCounter(0)
}
When you edit this file, Vite updates only the changed module. The counter state is preserved because HMR does not reload the page.
How HMR Works
sequenceDiagram Developer->>Vite: Edit counter.js Vite->>Browser: Send WebSocket update Browser->>Browser: Re-import counter.js module Browser->>Browser: Apply changes without reload Note over Browser: Counter state preserved
CSS and Assets
CSS Imports
// src/main.jsx
import './style.css'
import './responsive.css'
CSS Modules
/* src/Button.module.css */
.button {
padding: 10px 20px;
background: blue;
color: white;
}
// src/Button.jsx
import styles from './Button.module.css'
export function Button() {
return <button className={styles.button}>Click</button>
}
Asset URLs
import logo from './assets/logo.svg'
// logo is the resolved URL
const img = `<img src="${logo}" alt="Logo" />`
Expected Behavior
Assets smaller than 4KB are inlined as base64. Larger files are copied to the dist directory with hashed filenames.
$ npm run build
dist/
├── assets/
│ ├── logo-a1b2c3d4.svg
│ ├── index-c5d6e7f8.js
│ └── index-e9f0a1b2.css
└── index.html
Environment Variables
Vite uses import.meta.env for environment variables. Variables must be prefixed with VITE_ to be exposed to client code.
# .env
VITE_API_URL=http://localhost:8080
VITE_APP_TITLE=My App
# .env.development
VITE_API_URL=http://localhost:8080
# .env.production
VITE_API_URL=https://api.example.com
// src/api.js
const apiUrl = import.meta.env.VITE_API_URL
export async function fetchData() {
const response = await fetch(`${apiUrl}/data`)
return response.json()
}
Expected Output
console.log(import.meta.env.VITE_APP_TITLE)
// "My App"
console.log(import.meta.env.MODE)
// "development" or "production"
Build Optimization
Code Splitting
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['./src/utils.js'],
},
},
},
},
})
Tree Shaking
Vite automatically tree-shakes unused exports using Rollup. This removes dead code from the production bundle.
// src/math.js
export function add(a, b) { return a + b }
export function subtract(a, b) { return a - b }
export function multiply(a, b) { return a * b }
// src/main.jsx
import { add } from './math'
console.log(add(2, 3))
The subtract and multiply functions are excluded from the production bundle.
Chunk Size Warning
// vite.config.js
export default defineConfig({
build: {
chunkSizeWarningLimit: 500, // KB
},
})
Migrating from Webpack
Before (Webpack)
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
],
devServer: {
port: 3000,
hot: true,
},
}
After (Vite)
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: { port: 3000 },
})
And update index.html:
<!-- public/index.html (before) -->
<!-- No script tag needed -->
<!-- index.html (after) -->
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Migration Steps
- Remove Webpack config, babel config, and related npm packages
- Move
index.htmlto project root - Add Vite config with necessary plugins
- Update
package.jsonscripts:"dev": "vite","build": "vite build" - Replace
process.envwithimport.meta.env - Run
npm run devand fix any import issues
Common Errors
| Problem | Cause | Fix |
|---|---|---|
import.meta.env undefined |
Not using Vite's dev server | Use vite or vite build commands |
| Images not showing in production | Wrong asset path | Use import or new URL('./asset.png', import.meta.url) |
| HMR not working | File not properly imported | Ensure the file is imported via import, not a direct script tag |
| CORS errors in dev | API on different port | Configure server.proxy in vite.config.js |
| Build fails with syntax error | Missing plugin for JSX/TypeScript | Add @vitejs/plugin-react or @vitejs/plugin-vue |
Practice Questions
1. What technology does Vite use for development-time code transformation?
esbuild.
2. How do you access environment variables in Vite?
import.meta.env.VITE_VARIABLE_NAME.
3. What is the purpose of manualChunks in Vite's build configuration?
To manually control Code Splitting, grouping modules into separate chunks.
4. How does Vite's dev server serve JavaScript differently from Webpack?
Vite serves native ES modules without Bundling. Webpack bundles everything before serving.
5. What is the index.html role in a Vite project?
It is the entry point that references the application's source files via <script type="module">.
Challenge
Migrate a small React application from Create React App to Vite. The application must use environment variables for API configuration, CSS modules for styling, and include at least one dynamic import. Compare the dev server start time and build output size between CRA and Vite.
Real-World Task
Set up a new TypeScript + React project with Vite. Configure proxy for an API server, set up environment variables for development and production, implement Code Splitting for route-based chunks, and configure the build to output to a custom directory. Deploy the production build to a static hosting platform.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro