Vite Bundler Configuration for Web Project
Vite uses native ES modules in dev mode — files are served directly, without bundling. HMR works precisely: one file changes — one module updates. For production builds, it uses Rollup.
Result: cold start of dev server in 300–500 ms instead of 30–60 seconds with Webpack for large projects.
What's included in the work
Setting up vite.config.ts for React/Vue/Svelte, TypeScript, CSS/PostCSS, path aliases, backend API proxy, code splitting, environment variables, production optimizations.
Installation
# React + TypeScript
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install
# or Vue
npm create vite@latest my-app -- --template vue-ts
# or Svelte
npm create vite@latest my-app -- --template svelte-ts
vite.config.ts — React project
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
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'),
'@assets': path.resolve(__dirname, './src/assets'),
},
},
server: {
port: 3000,
strictPort: true, // error if port is busy, instead of choosing another
proxy: {
'/api': {
target: env.VITE_API_URL ?? 'http://localhost:8000',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api/, ''),
},
'/ws': {
target: env.VITE_WS_URL ?? 'ws://localhost:8000',
ws: true,
},
},
hmr: {
overlay: true,
},
},
preview: {
port: 4000,
strictPort: true,
},
css: {
modules: {
localsConvention: 'camelCaseOnly',
},
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
},
build: {
target: 'es2020',
outDir: 'dist',
sourcemap: mode !== 'production',
minify: 'esbuild',
rollupOptions: {
output: {
manualChunks: {
'vendor-react': ['react', 'react-dom'],
'vendor-router': ['react-router-dom'],
'vendor-query': ['@tanstack/react-query'],
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
},
},
// do not output chunk size warnings in CI
chunkSizeWarningLimit: 600,
},
optimizeDeps: {
include: [
'react',
'react-dom',
'react-router-dom',
'@tanstack/react-query',
],
},
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
},
}
})
tsconfig.json — aliases
Aliases need to be described in both vite.config.ts and tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@hooks/*": ["src/hooks/*"],
"@utils/*": ["src/utils/*"]
},
"types": ["vite/client"]
}
}
Environment variables
# .env.development
VITE_API_URL=http://localhost:8000
VITE_APP_NAME=MyApp Dev
# .env.production
VITE_API_URL=https://api.example.com
VITE_APP_NAME=MyApp
Only variables with VITE_ prefix end up in browser code:
// auto-typing
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_NAME: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
// usage
const apiUrl = import.meta.env.VITE_API_URL
Plugins for various tasks
npm install -D vite-plugin-svgr # SVG → React components
npm install -D @vitejs/plugin-legacy # legacy browser support
npm install -D vite-plugin-pwa # Progressive Web App
npm install -D rollup-plugin-visualizer # bundle analysis
npm install -D vite-plugin-checker # TypeScript + ESLint in dev
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import svgr from 'vite-plugin-svgr'
import legacy from '@vitejs/plugin-legacy'
import { VitePWA } from 'vite-plugin-pwa'
import { visualizer } from 'rollup-plugin-visualizer'
import checker from 'vite-plugin-checker'
export default defineConfig({
plugins: [
react(),
svgr({
svgrOptions: { exportType: 'named', ref: true },
}),
checker({
typescript: true,
eslint: { lintCommand: 'eslint src --ext .ts,.tsx' },
}),
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'My App',
short_name: 'App',
theme_color: '#1a1a2e',
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
}),
process.env.ANALYZE && visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'dist/bundle-stats.html',
}),
].filter(Boolean),
})
Code splitting via lazy
// src/router.tsx
import { lazy, Suspense } from 'react'
import { createBrowserRouter } from 'react-router-dom'
const ProductsPage = lazy(() =>
import('./pages/ProductsPage')
// Rollup hint for chunk name
// import(/* rollupOptions: { name: "products" } */ './pages/ProductsPage')
)
export const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
path: 'products',
element: (
<Suspense fallback={<PageSkeleton />}>
<ProductsPage />
</Suspense>
),
},
],
},
])
Static assets
// import with typing
import logoUrl from '@assets/logo.svg?url' // string URL
import LogoComponent from '@assets/logo.svg?react' // React component (svgr)
import rawSvg from '@assets/icon.svg?raw' // raw SVG as string
// URL for web worker
import MyWorker from './workers/heavy.worker?worker'
const worker = new MyWorker()
Multi-target build (lib mode)
If project is a UI library or shared package:
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'MyLib',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format}.js`,
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: { react: 'React', 'react-dom': 'ReactDOM' },
},
},
},
})
Scripts
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"build:analyze": "ANALYZE=true vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx --report-unused-disable-directives"
}
}
tsc --noEmit before vite build — check types before building. Vite transpiles TypeScript without type checking, so this is important.
What we do
We configure vite.config.ts for the project stack (React/Vue/Svelte), aliases, backend proxy, CSS/SCSS, add necessary plugins, optimize manual chunks for application characteristics, configure environment variables, add bundle visualizer.
Timeline: a few hours for new project, 1 day when migrating from webpack.







