Vite is not just a faster bundler; it’s a fundamentally different approach to front-end tooling that leverages native ES modules in the browser for development.

Let’s see Vite in action. Imagine a simple React app.

# Create a new Vite React project
npm create vite@latest my-react-app --template react

# Navigate into the project
cd my-react-app

# Install dependencies
npm install

# Start the dev server
npm run dev

When you run npm run dev, Vite starts a dev server. Instead of bundling your entire application into a few large files before serving them, Vite serves your source code directly to the browser. The browser, in turn, requests modules as needed using native ES module (import/export) syntax. This means that during development, Vite only needs to process and transform your code on demand, module by module, leading to near-instantaneous server start times and incredibly fast Hot Module Replacement (HMR).

For production builds, Vite uses Rollup under the hood. This provides highly optimized, pre-bundled code for deployment. The transition from development to production is seamless, with Vite handling the necessary optimizations like code splitting, tree-shaking, and minification.

The core problem Vite solves is the slowdown of traditional bundlers (like Webpack) as projects grow. As your codebase expands, the time it takes for Webpack to traverse the entire dependency graph, bundle everything, and update the browser during HMR becomes prohibitively long. Vite sidesteps this by leveraging the browser’s native module support. During development, when you change a file, Vite only needs to re-process that specific module and its direct dependencies, rather than re-bundling the entire application. This makes the HMR updates feel instantaneous, regardless of your project’s size.

Here’s a look at the key configuration options you control in vite.config.js:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  // Base public path when served in development or production
  // Default: '/'
  base: '/my-app/',

  // Directory to serve files from
  // Default: 'public'
  publicDir: 'static',

  // Directory to output build artifacts
  // Default: 'dist'
  build: {
    outDir: 'build',
    // Rollup options
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        nested: resolve(__dirname, 'nested/index.html'),
      },
      output: {
        // Example: custom chunk naming
        chunkFileNames: 'assets/chunks/[name]-[hash].js',
        entryFileNames: 'assets/entries/[name]-[hash].js',
        assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
      },
    },
    // Minify options
    minify: 'esbuild', // or 'terser'
  },

  // Server options
  server: {
    // Port to run the dev server on
    // Default: 5173
    port: 3000,
    // Automatically open the app in the browser
    open: true,
    // Proxy for API requests
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },

  // Resolve options for modules
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },

  // CSS options
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
  },
});

The base option is crucial for single-page applications deployed to subdirectories. If your app is hosted at https://example.com/my-app/, setting base: '/my-app/' ensures that Vite correctly resolves assets and routes. Without it, assets would be looked for at the root (/), leading to 404 errors.

The build.rollupOptions.input allows you to define multiple entry points for your application, enabling you to build multiple distinct applications or pages within a single Vite project. This is extremely powerful for complex sites or micro-frontend architectures.

While Vite’s development server leverages native ES modules for speed, its production build still uses Rollup to create optimized bundles. The build.minify option lets you choose between esbuild (very fast, good compression) and terser (slower but potentially better compression, more mature). For most cases, esbuild offers a great balance of speed and optimization.

When configuring server.proxy, the rewrite function is your friend for mapping API paths. For example, if your frontend makes requests to /api/users, and your backend API is running on http://localhost:8080, rewrite: (path) => path.replace(/^\/api/, '') will strip the /api prefix before forwarding the request to the target, so the backend sees /users.

The most surprising thing about Vite’s performance is how it manages to achieve such rapid HMR during development without a massive, complex client-side watcher. It truly leans into the browser’s native capabilities and transforms only what’s necessary, when it’s necessary.

The next step in optimizing your Vite setup might involve exploring advanced plugin development or diving deep into Rollup’s plugin ecosystem for fine-grained control over the build process.

Want structured learning?

Take the full Vite course →