Vite 6 Complete Guide: Configuration, Plugins, and Production Optimization

Vite 6 is the most significant release since Vite 2 introduced the plugin system. The headline feature is Rolldown — a Rust-based bundler that replaces both esbuild (used in development for dependency pre-bundling) and Rollup (used in production builds) with a single, unified tool. But beyond the bundler swap, Vite 6 brings a redesigned configuration system, an expanded plugin API, and optimizations that measurably reduce build times for large projects.

This guide covers everything you need to know to configure, extend, and optimize Vite 6. It is written for developers who already use Vite and want to understand what changed, as well as teams evaluating Vite for a new project. Where previous Vite versions required workarounds for certain production scenarios, Vite 6 addresses most of those gaps directly.

Architecture: How Vite 6 Works

Understanding Vite's architecture prevents the most common configuration mistakes. Vite operates in two fundamentally different modes, and knowing which mode your code is running in determines how you configure it.

Development Mode

In development, Vite serves your source files directly over native ES modules. The browser requests import statements as individual HTTP requests, and Vite transforms each file on demand. There is no bundling step — your 500-file React application loads as 500 individual modules (after dependency pre-bundling consolidates node_modules).

Vite 6 replaces esbuild with Rolldown for dependency pre-bundling. Pre-bundling converts CommonJS packages to ESM and consolidates packages with many internal modules (lodash-es has 600+ files) into a single file to avoid waterfalling hundreds of HTTP requests. Rolldown performs this step 30-40% faster than esbuild in benchmarks, primarily because it handles the conversion and bundling in a single pass rather than the two-pass approach esbuild required.

Production Mode

In production, Vite bundles your application into optimized chunks. Vite 6 uses Rolldown here too, replacing the Rollup bundler from previous versions. The output is the same — hashed filenames, code splitting, tree shaking, minification — but the build is faster because Rolldown's Rust implementation avoids the JavaScript runtime overhead that Rollup incurred.

The unification matters beyond raw speed. In previous Vite versions, the different behavior between esbuild (dev) and Rollup (production) caused subtle bugs: code that worked in development broke in production because the two tools resolved modules differently, handled edge cases in CSS differently, or produced different output for the same source. Rolldown eliminates this entire class of dev/prod divergence.

Configuration Patterns

Vite's configuration file (vite.config.ts) is the control surface for both modes. Vite 6 introduces environment-specific configuration blocks that replace the older conditional patterns.

// vite.config.ts — Vite 6 project configuration
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { compression } from 'vite-plugin-compression2'

export default defineConfig({
  plugins: [
    react(),
    compression({ algorithm: 'brotli' }),
  ],

  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components',
      '@utils': '/src/utils',
    },
  },

  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      },
    },
  },

  build: {
    target: 'es2022',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router'],
          charts: ['recharts'],
        },
      },
    },
  },

  // Vite 6: environment-specific overrides
  environments: {
    client: {
      build: {
        outDir: 'dist/client',
      },
    },
    ssr: {
      build: {
        outDir: 'dist/server',
        ssr: true,
      },
    },
  },
})

Environment-Specific Configuration

The environments block is new in Vite 6. It replaces the pattern of using conditional logic (if (mode === 'production')) for environment-specific settings. Each environment — client, ssr, or custom environments you define — gets its own resolved configuration that inherits from the root config and can override any property.

This is particularly valuable for SSR applications where the client and server bundles have different targets, different externalization rules, and different output directories. Previously, managing these differences required either multiple config files or brittle conditional logic. The environments API makes the intent explicit.

Dependency Optimization

Vite pre-bundles dependencies listed in optimizeDeps.include during startup. For most projects, Vite detects dependencies automatically by scanning your source files. Manual configuration is needed when a dependency is loaded dynamically or when a dependency re-exports from many sub-packages.

Common patterns that require manual optimizeDeps configuration: barrel files that re-export from dozens of sub-modules (Material UI, Ant Design), packages that use require() internally despite having an ESM entry point, and packages that load optional peer dependencies at runtime.

The Plugin API

Vite's plugin API is its most powerful extension point. Plugins hook into every stage of the dev server and build pipeline: module resolution, code transformation, chunk generation, and asset handling. The API is Rollup-compatible with Vite-specific extensions, which means most Rollup plugins work in Vite without modification.

// vite-plugin-markdown.ts — custom plugin example
import { Plugin } from 'vite'
import { marked } from 'marked'

export function markdownPlugin(): Plugin {
  return {
    name: 'vite-plugin-markdown',

    // Transform .md files into ES modules
    transform(code, id) {
      if (!id.endsWith('.md')) return null

      const html = marked.parse(code)
      // Extract frontmatter metadata
      const frontmatterMatch = code.match(
        /^---\n([\s\S]*?)\n---/
      )
      const metadata = frontmatterMatch
        ? parseFrontmatter(frontmatterMatch[1])
        : {}

      return {
        code: `
          export const html = ${JSON.stringify(html)};
          export const metadata = ${JSON.stringify(metadata)};
          export default html;
        `,
        map: null,
      }
    },

    // Add HMR support for .md files
    handleHotUpdate({ file, server }) {
      if (file.endsWith('.md')) {
        server.ws.send({ type: 'full-reload' })
        return []
      }
    },
  }
}

function parseFrontmatter(raw: string): Record<string, string> {
  const result: Record<string, string> = {}
  for (const line of raw.split('\n')) {
    const [key, ...rest] = line.split(':')
    if (key && rest.length) {
      result[key.trim()] = rest.join(':').trim()
    }
  }
  return result
}

Plugin Execution Order

Plugin order matters and is a frequent source of confusion. Vite runs plugins in this order: alias resolution, then plugins with enforce: 'pre', then normal plugins (no enforce), then Vite's internal transforms (JSX, CSS modules, asset handling), then plugins with enforce: 'post', then production-only minification.

Practical rule: if your plugin needs to transform code before Vite processes it (for example, converting a custom file format to JavaScript), use enforce: 'pre'. If your plugin needs to process the final output (for example, adding license headers or generating a manifest), use enforce: 'post'. Most plugins belong in the normal position.

Rolldown Migration

For teams upgrading from Vite 5, the Rolldown migration is the most impactful change. Rolldown is API-compatible with Rollup for the vast majority of plugin hooks, but there are edge cases where behavior differs.

Aspect Vite 5 (Rollup) Vite 6 (Rolldown)
Bundler language JavaScript Rust (with JS plugin bridge)
Build speed (large project) Baseline 2-5x faster
Dev pre-bundling esbuild Rolldown (unified)
Plugin compatibility Rollup ecosystem Rollup-compatible (95%+)
Tree shaking Rollup algorithm Improved (cross-module analysis)
Code splitting Rollup chunks Same API, better defaults
Source maps Rollup source maps Faster generation, same output
CSS handling PostCSS + Rollup Native CSS support in Rolldown

The 5% of Rollup plugins that do not work with Rolldown typically rely on internal Rollup APIs that were never part of the public plugin interface. The Vite team maintains a compatibility tracker, and most popular plugins (PostCSS, image optimization, SVG inlining, compression) have already been updated. Check the Vite 6 migration guide for the current compatibility list before upgrading.

Build Performance Gains

Real-world build time improvements vary by project, but the pattern is consistent. Small projects (under 100 modules) see 1.5-2x improvement. Medium projects (500-2000 modules) see 2-3x improvement. Large monorepo applications (5000+ modules) see 3-5x improvement. The gains scale with project size because Rolldown's Rust implementation avoids the garbage collection pauses and single-threaded bottlenecks that JavaScript-based bundlers hit at scale.

Source map generation sees the largest relative improvement — up to 6x faster in some projects — because Rolldown generates source maps in parallel during the bundling process rather than as a sequential post-processing step.

Library Mode

Vite's library mode lets you build npm packages instead of applications. It produces multiple output formats (ESM, CJS, UMD) from a single entry point, handles TypeScript declaration generation, and externalizes peer dependencies automatically.

// vite.config.ts — library mode configuration
import { defineConfig } from 'vite'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'

export default defineConfig({
  plugins: [
    dts({
      rollupTypes: true,  // bundle .d.ts files
      insertTypesEntry: true,
    }),
  ],

  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyLib',
      formats: ['es', 'cjs'],
      fileName: (format) => `my-lib.${format}.js`,
    },
    rollupOptions: {
      // Externalize peer dependencies
      external: ['react', 'react-dom', 'react/jsx-runtime'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
    // Generate CSS as a separate file
    cssCodeSplit: false,
  },
})

Key library mode considerations in Vite 6: use vite-plugin-dts for TypeScript declarations (Vite does not generate .d.ts files natively). Set cssCodeSplit: false if your library includes CSS — this produces a single CSS file that consumers can import. Always externalize peer dependencies to avoid bundling React or other frameworks into your library output.

SSR Configuration

Vite 6's SSR support has matured from experimental to production-ready. The environments API (described earlier) replaces the ad-hoc ssr options from Vite 5 with a structured approach to server-side builds.

For SSR applications, Vite handles two distinct bundles: the client bundle (runs in the browser) and the server bundle (runs in Node.js or an edge runtime). Each has different requirements — the client bundle needs code splitting and asset hashing, while the server bundle needs to externalize Node.js built-ins and server-only packages.

Vite 6 simplifies SSR configuration by making externalization automatic. Packages in node_modules are externalized in the SSR build by default unless they contain CSS imports or non-standard exports that require bundling. The ssr.noExternal option lets you opt specific packages back into the bundle when needed — typically packages that use CSS-in-JS or import static assets.

SSR with Streaming

Vite 6 natively supports streaming SSR through the ssrLoadModule API. The development server loads and executes server modules in an isolated context, supporting hot module replacement for server code. This means you can modify a server-rendered component, save the file, and see the change reflected on the next request without restarting the server — a workflow that previously required framework-specific tooling like Next.js or Nuxt.

Production Optimization Checklist

After years of debugging production Vite builds, certain optimizations consistently deliver measurable improvements. Here is the checklist, ordered by impact.

1. Configure manual chunks deliberately. Vite's default code splitting creates chunks based on dynamic imports and shared dependencies. For most applications, explicitly separating vendor code, route-level code, and shared utilities into named chunks produces better caching behavior. The manualChunks function gives you full control over which modules land in which chunk.

2. Enable Brotli or gzip compression. Vite does not compress output files by default. Use vite-plugin-compression2 to pre-compress assets during the build. Brotli typically achieves 15-20% better compression than gzip for JavaScript and CSS. Configure your CDN or web server to serve pre-compressed files when the client supports them.

3. Audit your dependency tree. Run npx vite-bundle-visualizer to generate a treemap of your production bundle. Common findings: moment.js shipping all locales (switch to dayjs or date-fns), lodash imported as a monolith (use lodash-es with tree shaking), and icon libraries bundling thousands of unused SVGs.

4. Set the correct build target. The build.target option controls which JavaScript features Vite preserves versus transpiles. Setting target: 'es2022' avoids transpiling features like top-level await, private class fields, and Array.at() that all modern browsers support. Lower targets produce larger, slower output.

5. Use CSS code splitting. With cssCodeSplit: true (the default), Vite extracts CSS for each async chunk into its own file. This means CSS for a route the user has not visited is never loaded. Disable this only for library builds where you want a single CSS output file.

6. Optimize images at build time. Use vite-plugin-imagemin or @svgr/rollup to process images during the build rather than relying on runtime optimization. Convert PNGs to WebP or AVIF where browser support allows. Inline small SVGs as React components to avoid additional HTTP requests.

Common Pitfalls

Certain Vite configurations cause problems that only surface in production or at scale. These are the ones I encounter most frequently when reviewing projects.

Barrel file performance. Index files that re-export from dozens of sub-modules (export * from './Button', export * from './Input', etc.) force Vite to load and transform every sub-module even when only one export is used. Vite 6's Rolldown bundler handles this better than Rollup did, but the dev server still suffers. For large component libraries, use direct imports (import { Button } from '@/components/Button') instead of barrel imports.

PostCSS plugin overhead. PostCSS runs on every CSS file during both development and production. Complex PostCSS configurations (Tailwind with many content paths, autoprefixer with broad browser targets, custom plugins with AST traversal) can dominate build time. Profile your PostCSS pipeline — postcss-devtools shows per-plugin timing — and eliminate plugins that add negligible value.

Dynamic import anti-patterns. Vite cannot tree-shake modules loaded with fully dynamic import paths (import(`./pages/${name}.tsx`)). It bundles every file matching the glob pattern into the output, even if most are never loaded. Use explicit dynamic imports or route-based splitting instead.

Development-only dependencies in production. Packages imported only in test files or dev scripts can leak into the production bundle if they are imported through a shared module. Vite's tree shaking catches most cases, but conditional imports and side-effectful modules can bypass it. Audit your bundle regularly.

Vite vs Other Build Tools in 2026

Feature Vite 6 Webpack 6 Turbopack Rspack
Dev server startup <300ms 2-8s <500ms 400-800ms
HMR speed <50ms 200-500ms <50ms 50-100ms
Production build (medium app) 3-8s 15-40s 5-12s 4-10s
Plugin ecosystem Large (Rollup-compatible) Largest Growing (Webpack-compat) Webpack-compatible
Framework support React, Vue, Svelte, Solid, Qwik All major Next.js only All major
Configuration complexity Low High Medium (Next.js config) Medium (Webpack-like)

Vite's advantage is the combination of fast development experience and a clean configuration model. Webpack retains the deepest plugin ecosystem and handles edge cases that Vite's simpler architecture cannot. Turbopack is tightly coupled to Next.js and not available as a standalone tool. Rspack offers Webpack compatibility with Rust-powered speed, making it the best migration path for teams locked into Webpack plugin configurations.

When to Choose Vite 6

Vite 6 is the right choice for most new frontend projects in 2026. It is the default build tool for Vue (via create-vue), Svelte (via SvelteKit), and Solid (via SolidStart). React projects that do not use Next.js benefit from Vite's faster development experience and simpler configuration compared to Create React App (now deprecated) or raw Webpack.

Stay with your current build tool if you depend heavily on Webpack plugins that have no Vite equivalent, if you use Turbopack through Next.js and are satisfied with its performance, or if your build pipeline involves custom Webpack loaders that would need to be rewritten as Vite plugins. Migration has a cost, and the performance gains do not justify it for every project.

The best build tool is the one you spend the least time configuring. Vite 6 delivers on that principle better than any alternative — fast defaults, escape hatches where you need them, and a plugin API that makes customization composable rather than complex.

For teams starting fresh, Vite 6 with Rolldown provides the fastest development experience, the cleanest configuration model, and production output that matches or exceeds what heavier build tools produce. The unified bundler eliminates the dev/prod divergence that plagued earlier versions. The environments API handles SSR without the workarounds that Vite 5 required. It is, by a meaningful margin, the most productive build tool available in 2026.