Build production bundles with Vite — use when creating optimized builds, configuring code splitting, building libraries, setting up SSR, or customizing minification
Vite's vite build command bundles your project for production using Rolldown, producing optimized, tree-shaken, code-split output with hashed filenames.
vite dev (the dev server) insteadtsc for type checking# Standard production build
vite build
# With source maps
vite build --sourcemap
# Custom mode (loads .env.staging)
vite build --mode staging
# Custom output directory
vite build --outDir build
# Watch mode (rebuild on changes)
vite build --watch
# SSR server bundle
vite build --ssr src/entry-server.ts
# Preview the production build locally
vite preview
vite preview --port 8080
vite preview --host 0.0.0.0
vite.config.ts exists with your plugins and build optionsvite build -- outputs to dist/ by defaultvite preview -- serves dist/ on port 4173dist/ to your hosting providernpm run build # or: npx vite build
npx vite preview # verify locally
Output structure:
dist/
index.html
assets/
main-BkH2gR4a.js # Hashed JS bundle
main-DiwrgTda.css # Extracted CSS
vendor-C7BkSa0T.js # Vendor chunk (if code-split)
logo-a1b2c3d4.svg # Hashed static assets
favicon.ico # Copied from public/
Build a reusable npm package instead of an application.
// vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLib', // Global name for UMD/IIFE
formats: ['es', 'cjs', 'umd'],
fileName: (format) => `my-lib.${format}.js`,
},
rollupOptions: {
// Do not bundle peer dependencies
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
});
Configure package.json for dual publishing:
{
"name": "my-lib",
"type": "module",
"main": "dist/my-lib.cjs.js",
"module": "dist/my-lib.es.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/my-lib.es.js",
"require": "./dist/my-lib.cjs.js"
}
},
"files": ["dist"]
}
Generate type declarations separately:
tsc --emitDeclarationOnly --declaration --outDir dist
Produce both client and server bundles for server-side rendering.
# 1. Build client bundle
vite build --outDir dist/client
# 2. Build server bundle
vite build --outDir dist/server --ssr src/entry-server.ts
SSR config:
// vite.config.ts
export default defineConfig({
build: {
target: 'node20',
ssrManifest: true, // Module-to-preload mapping
manifest: true, // Asset hashing manifest
},
});
The SSR manifest maps modules to their preload directives so the server can inject <link rel="modulepreload"> tags into the HTML response.
Control the JavaScript syntax level of the output.
export default defineConfig({
build: {
// Default: broadly supported modern features
target: 'baseline-widely-available',
// Specific ES version
target: 'es2020',
// Target specific browsers
target: ['chrome100', 'firefox115', 'safari16'],
// Bleed-edge (no downleveling)
target: 'esnext',
},
});
For browsers that need polyfills (IE11, old mobile Safari):
npm install -D @vitejs/plugin-legacy
import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
}),
],
});
export default defineConfig({
build: {
// 'oxc' — default, Rust-based, 30-90x faster than terser
minify: 'oxc',
// 'terser' — maximum compression, requires: npm i -D terser
minify: 'terser',
// 'esbuild' — fast Go-based minifier (former default)
minify: 'esbuild',
// false — skip minification entirely
minify: false,
// CSS minification (separate from JS)
cssMinify: 'lightningcss', // default, fast Rust-based
cssMinify: 'esbuild', // alternative
cssMinify: false, // disable
},
});
export default defineConfig({
build: {
sourcemap: false, // No maps (default, smallest output)
sourcemap: true, // Separate .map files
sourcemap: 'inline', // Embedded in JS (large, debug only)
sourcemap: 'hidden', // .map files without URL reference
// Best for Sentry/Datadog upload
},
});
export default defineConfig({
build: {
rollupOptions: {
output: {
// Object form: named chunks
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
ui: ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
},
// Function form: dynamic chunking
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
},
},
},
chunkSizeWarningLimit: 500, // Warn on chunks > 500kB
cssCodeSplit: true, // Separate CSS per async chunk
},
});
export default defineConfig({
build: {
manifest: true, // .vite/manifest.json
},
});
The manifest maps source paths to hashed output:
{
"src/main.tsx": {
"file": "assets/main-BkH2gR4a.js",
"src": "src/main.tsx",
"isEntry": true,
"css": ["assets/main-DiwrgTda.css"]
}
}
Backend templates use this to inject the correct <script> and <link> tags.
export default defineConfig({
build: {
assetsDir: 'assets', // Subdirectory for assets
assetsInlineLimit: 4096, // Inline assets < 4kB as base64
copyPublicDir: true, // Copy public/ to outDir root
},
});
After building, check output size:
# Vite prints a size summary after build
npx vite build
# Analyze bundle visually with rollup-plugin-visualizer
npm install -D rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
open: true,
filename: 'stats.html',
gzipSize: true,
}),
],
});
Disable compressed size reporting for faster builds on large projects:
export default defineConfig({
build: {
reportCompressedSize: false,
},
});
{
"scripts": {
"build": "vite build",
"build:staging": "vite build --mode staging",
"build:analyze": "vite build && npx vite-bundle-visualizer",
"preview": "vite preview",
"build:ssr:client": "vite build --outDir dist/client",
"build:ssr:server": "vite build --outDir dist/server --ssr src/entry-server.ts"
}
}
emptyOutDir defaults to true only when outDir is inside the project root. If outDir is outside the root Vite refuses to empty it unless you explicitly set emptyOutDir: true.name field in build.lib is required for UMD and IIFE formats. It becomes the global variable name (e.g., window.MyLib).minify: 'terser' without installing the terser package causes a build error. Install it: npm i -D terser.sourcemap: true exposes source code to anyone with browser devtools. Use 'hidden' to generate maps for error tracking without client exposure.manualChunks function that assigns the same module to multiple chunks causes build errors. Each module can belong to only one chunk.ssr.noExternal.reportCompressedSize: false for faster CI builds.public/ are copied verbatim -- they are not processed, hashed, or tree-shaken. Do not import from public/; use src/assets/ for processed assets.build.watch keeps the process alive. Never enable it in CI pipelines or it will hang indefinitely.build.rollupOptions.input as an object mapping names to HTML file paths.