Vite's configuration system is refreshingly straightforward compared to other build tools, but there's still plenty to know when you want to customize your setup.
Whether you're building a simple React app or a complex multi-framework project, understanding your vite.config.js
file will save you hours of frustration.
In this article, I'll break down the basic structure of a vite.config
and dive into the more nuanced properties you can configure.
The Basics: Your First Config File
When you create a new Vite project, you'll get a basic config file that looks something like this:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
That defineConfig
function isn't just for show – it provides TypeScript intellisense and helps catch configuration errors early. Always use it, even in JavaScript projects.
Essential Configuration Options
Development Server Settings
The server
object controls how your development server behaves:
export default defineConfig({
server: {
port: 3000,
host: true, // Listen on all addresses
open: true, // Auto-open browser
cors: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false
}
}
}
})
The proxy configuration is particularly useful when your backend runs on a different port. No more CORS headaches during development.
Build Configuration
Control how Vite builds your production bundle:
export default defineConfig({
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser', // or 'esbuild'
rollupOptions: {
external: ['vue'], // Don't bundle Vue in a library
output: {
globals: {
vue: 'Vue'
}
}
}
}
})
The build configuration gives you fine-grained control over your production output. The outDir
specifies where the built files will be placed (defaulting to 'dist'), while sourcemap: true
generates source maps for easier debugging in production.
The minify
option lets you choose between 'terser' for maximum compression or 'esbuild' for faster builds with slightly larger output.
The rollupOptions
section provides direct access to Rollup's configuration, which is particularly useful when building libraries. In this example, external: ['vue'] prevents Vue from being bundled into the library output, assuming it will be provided by the consuming application.
The corresponding globals
mapping tells Rollup what global variable name to use when Vue is accessed externally. This pattern is essential for creating libraries that don't duplicate dependencies already present in the host application, reducing bundle size and avoiding version conflicts.
Path Resolution and Aliases
Set up clean import paths that make your code more maintainable:
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils'),
}
}
})
Now you can write import Button from '@components/Button'
instead of ../../components/Button
.
Working with Plugins
Plugins are where Vite really shines. The ecosystem is rich and most plugins are zero-config:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [
react(),
// Add more plugins as needed
]
})
Vite's plugin architecture is built on top of Rollup's plugin system, extended with additional hooks for development-specific features like hot module replacement.
This design allows Vite to leverage the mature Rollup plugin ecosystem while adding its own optimizations. Most official plugins like @vitejs/plugin-react
work out of the box with sensible defaults - simply installing and adding them to the plugins array is often all you need.
The ecosystem includes plugins for virtually every use case: TypeScript support, CSS preprocessing, PWA generation, bundle analysis, and framework-specific optimizations.
During development, plugins can hook into Vite's dev server to transform files on-the-fly, while in production they integrate seamlessly with the Rollup build process. This unified approach means you write your plugin configuration once and it works consistently across both development and production environments, eliminating the complex webpack loader configurations that plagued earlier build tools.
Popular plugins include @vitejs/plugin-vue
for Vue projects, vite-plugin-pwa
for Progressive Web Apps, and vite-plugin-eslint
for linting integration.
Environment-Specific Configuration
You'll often need different settings for development and production:
export default defineConfig(({ command, mode }) => {
const config = {
plugins: [react()],
}
if (command === 'serve') {
// Development-specific config
config.server = {
port: 3000,
}
} else {
// Production build config
config.build = {
minify: 'terser',
}
}
return config
})
This configuration function demonstrates Vite's ability to adapt based on the current environment. The function receives two parameters:
command
(either 'serve' for development or 'build' for production) and mode
(the current mode like 'development' or 'production'). By checking the command type, you can conditionally apply different settings.
In this example, when serving in development mode, it sets a custom port of 3000 for the development server. When building for production, it configures the build process to use Terser for JavaScript minification, which helps reduce bundle size.
CSS and Asset Handling
Vite handles CSS preprocessing and assets elegantly:
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`
}
},
modules: {
localsConvention: 'camelCase'
}
},
assetsInclude: ['**/*.glb'] // Include additional asset types
})
This configuration showcases Vite's powerful CSS and asset processing capabilities.
The css.preprocessorOptions
section allows you to configure CSS preprocessors like Sass/SCSS, where additionalData
automatically injects imports into every SCSS file, making global variables and mixins available without manual imports.
The modules configuration controls CSS Modules behavior, with localsConvention
: 'camelCase' converting kebab-case class names to camelCase when imported in JavaScript (so .my-class becomes styles.myClass).
The assetsInclude
option extends Vite's built-in asset handling to recognize additional file types like .glb 3D model files, treating them as static assets that can be imported and referenced in your code. This unified approach to styling and assets eliminates the need for complex webpack loaders while providing intuitive defaults.
TypeScript Configuration
If you're using TypeScript, create a vite.config.ts
file instead:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
// TypeScript will provide full intellisense here
})
Make sure your tsconfig.json
includes the config file and has proper module resolution settings.
A few tweaks can significantly improve build performance:
export default defineConfig({
build: {
target: 'esnext', // Use modern JS features
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
}
},
optimizeDeps: {
include: ['react', 'react-dom'], // Pre-bundle these dependencies
exclude: ['@your-company/internal-package']
}
})
These optimizations target both build speed and runtime performance. Setting target: 'esnext'
allows Vite to skip transpilation of modern JavaScript features, reducing build time and output size when targeting modern browsers.
The manualChunks
configuration implements strategic code splitting by grouping related dependencies into separate chunks - this enables better browser caching since vendor libraries change less frequently than your application code. When users return to your site, they can reuse the cached vendor chunk even if your app code has updated.
The optimizeDeps
section controls Vite's dependency pre-bundling during development. Including frequently-used dependencies like React ensures they're pre-processed and cached, speeding up initial dev server startup and subsequent hot reloads.
Conversely, excluding internal packages or dependencies that change frequently prevents unnecessary re-bundling. This configuration creates an optimal balance between development speed and production performance, with chunks sized appropriately for efficient network delivery and caching strategies.
Configuration File Organization
For complex projects, consider splitting your configuration:
// vite.config.js
import { defineConfig } from 'vite'
import { plugins } from './config/plugins'
import { server } from './config/server'
import { build } from './config/build'
export default defineConfig({
plugins,
server,
build
})
This keeps your main config file clean and makes it easier to manage large configurations.
Testing Your Configuration
Always test your configuration changes in both development and production modes. Run npm run build
and npm run preview
to ensure everything works as expected. Consider setting up different configuration files for different environments if your needs are complex enough.
The beauty of Vite's configuration system is that it starts simple but scales with your project's complexity. Start with the basics and add configuration options as you need them. Most projects will do just fine with a minimal setup, and you can always extend it later when requirements change.