Skip to content

Vite

A comprehensive guide to Vite, the next-generation frontend build tool.

Introduction to Vite

Vite (French word for "fast", pronounced /vit/) is a modern frontend build tool created by Evan You, the creator of Vue.js. It provides a faster and leaner development experience for modern web projects. Vite consists of two major parts:

  1. A dev server that serves your source files over native ES modules, with rich built-in features and astonishingly fast Hot Module Replacement (HMR).
  2. A build command that bundles your code with Rollup, pre-configured to output highly optimized static assets for production.

Key Features

  • Lightning-fast cold start: Vite leverages native ES modules in the browser to avoid bundling during development.
  • Instant hot module replacement (HMR): Updates are reflected in the browser instantly without reloading the page.
  • True on-demand compilation: Only the modules being edited are transformed and updated.
  • Out-of-the-box support for TypeScript, JSX, CSS preprocessors, and more.
  • Optimized build: Production builds are highly optimized using Rollup.
  • Framework-agnostic: Works with Vue, React, Preact, Lit, Svelte, and vanilla JavaScript.
  • Plugin ecosystem: Extensible via Rollup-compatible plugins.

Getting Started with Vite

Installation

You can create a new Vite project using npm, yarn, or pnpm:

bash
# npm
npm create vite@latest my-vite-app -- --template vanilla

# yarn
yarn create vite my-vite-app --template vanilla

# pnpm
pnpm create vite my-vite-app --template vanilla

Available templates include:

  • vanilla: Plain JavaScript
  • vanilla-ts: TypeScript
  • vue: Vue
  • vue-ts: Vue + TypeScript
  • react: React
  • react-ts: React + TypeScript
  • preact: Preact
  • preact-ts: Preact + TypeScript
  • lit: Lit
  • lit-ts: Lit + TypeScript
  • svelte: Svelte
  • svelte-ts: Svelte + TypeScript

Project Structure

A typical Vite project structure looks like this:

my-vite-app/
├── node_modules/
├── public/             # Static assets that will be served as-is
│   └── favicon.ico
├── src/                # Application source code
│   ├── assets/         # Assets that will be processed by Vite
│   │   └── logo.png
│   ├── components/     # Application components
│   │   └── HelloWorld.vue
│   ├── App.vue         # Root component (for Vue projects)
│   └── main.js         # Application entry point
├── index.html          # HTML entry point
├── package.json        # Project dependencies and scripts
├── vite.config.js      # Vite configuration
└── README.md           # Project documentation

Development Server

To start the development server:

bash
# Navigate to your project directory
cd my-vite-app

# Install dependencies
npm install

# Start the development server
npm run dev

The development server will start at http://localhost:5173 by default.

Core Concepts

ES Modules

Vite leverages native ES modules in the browser, which allows it to serve source code without bundling during development. This results in faster startup times and more efficient hot module replacement.

html
<!-- index.html -->
<script type="module" src="/src/main.js"></script>
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

createApp(App).mount('#app')

Hot Module Replacement (HMR)

Vite provides an HMR API that allows modules to exchange and update without reloading the page:

javascript
// Counter module with HMR support
export let count = 0

export function increment() {
  count++
}

// HMR interface
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    count = newModule.count
  })
}

Static Asset Handling

Vite automatically processes and optimizes static assets:

javascript
// Importing assets
import imgUrl from './img.png'               // Resolved to public URL
import svgContent from './icon.svg?raw'      // Raw content as string
import Component from './Component.vue?url'  // Get the URL of the component

// Using assets in templates (Vue example)
<img :src="imgUrl" alt="Image">

Environment Variables

Vite exposes environment variables on the special import.meta.env object:

javascript
console.log(import.meta.env.MODE)          // 'development' or 'production'
console.log(import.meta.env.VITE_API_URL)  // Custom variables prefixed with VITE_

Custom environment variables can be defined in .env files:

# .env
VITE_API_URL=https://api.example.com

Configuration

Basic Configuration

Vite is configured through a vite.config.js file in the project root:

javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  server: {
    port: 3000,
    open: true
  },
  build: {
    outDir: 'dist',
    minify: 'terser'
  }
})

Common Configuration Options

Plugins

Plugins extend Vite's functionality:

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
    // Custom plugin
    {
      name: 'my-plugin',
      transform(code, id) {
        if (id.endsWith('.special')) {
          return { code: transformSpecialFile(code), map: null }
        }
      }
    }
  ]
})

Development Server

Configure the development server:

javascript
export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 3000,
    strictPort: true,
    https: true,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

Build Options

Configure production builds:

javascript
export default defineConfig({
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    assetsInlineLimit: 4096, // 4kb
    cssCodeSplit: true,
    sourcemap: true,
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router'],
          utils: ['./src/utils/index.js']
        }
      }
    }
  }
})

Resolve

Configure module resolution:

javascript
export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '~': path.resolve(__dirname, './node_modules')
    },
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  }
})

Mode-specific Configuration

You can specify different configurations for development and production:

javascript
export default defineConfig(({ command, mode }) => {
  const isProduction = mode === 'production'
  
  return {
    define: {
      __APP_VERSION__: JSON.stringify('1.0.0'),
      __API_URL__: isProduction
        ? JSON.stringify('https://api.example.com')
        : JSON.stringify('http://localhost:8080')
    },
    build: {
      minify: isProduction ? 'terser' : false
    }
  }
})

Advanced Features

CSS Processing

Vite has built-in support for CSS preprocessing:

javascript
// Import CSS
import './style.css'

// CSS Modules
import styles from './style.module.css'
element.className = styles.heading

// CSS Preprocessors (requires installing the appropriate preprocessor)
import './style.scss'
import './style.less'
import './style.styl'

Configure CSS in vite.config.js:

javascript
export default defineConfig({
  css: {
    modules: {
      scopeBehaviour: 'local',
      localsConvention: 'camelCaseOnly'
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/styles/variables.scss";`
      },
      less: {
        javascriptEnabled: true
      }
    },
    postcss: {
      plugins: [autoprefixer()]
    }
  }
})

TypeScript Integration

Vite supports TypeScript out of the box:

typescript
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

interface User {
  name: string
  age: number
}

const user: User = {
  name: 'John',
  age: 30
}

console.log(user)
createApp(App).mount('#app')

Configure TypeScript in vite.config.js:

javascript
export default defineConfig({
  esbuild: {
    target: 'es2020'
  }
})

WebAssembly

Import WebAssembly modules directly:

javascript
import init, { add } from './add.wasm'

init().then(() => {
  console.log(add(1, 2)) // 3
})

Web Workers

Import Web Workers directly:

javascript
// Standard worker
import Worker from './worker.js?worker'
const worker = new Worker()

// Shared worker
import SharedWorker from './worker.js?sharedworker'
const sharedWorker = new SharedWorker()

// Inline worker
import InlineWorker from './worker.js?worker&inline'

Server-Side Rendering (SSR)

Vite provides first-class support for server-side rendering:

javascript
// vite.config.js
export default defineConfig({
  ssr: {
    // SSR-specific options
    external: ['some-external-dependency'],
    noExternal: ['some-dependency-to-bundle']
  }
})

Basic SSR setup:

javascript
// server.js
import fs from 'fs'
import path from 'path'
import express from 'express'
import { createServer as createViteServer } from 'vite'

async function createServer() {
  const app = express()
  
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })
  
  app.use(vite.middlewares)
  
  app.use('*', async (req, res) => {
    const url = req.originalUrl
    
    try {
      // Read index.html
      let template = fs.readFileSync(
        path.resolve(process.cwd(), 'index.html'),
        'utf-8'
      )
      
      // Apply Vite HTML transforms
      template = await vite.transformIndexHtml(url, template)
      
      // Load the server entry
      const { render } = await vite.ssrLoadModule('/src/entry-server.js')
      
      // Render the app HTML
      const { html: appHtml, preloadLinks } = await render(url)
      
      // Inject the app-rendered HTML into the template
      const html = template
        .replace(`<!--preload-links-->`, preloadLinks)
        .replace(`<!--app-html-->`, appHtml)
      
      // Send the rendered HTML back
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.message)
    }
  })
  
  app.listen(3000)
}

createServer()

Testing with Vite

Unit Testing with Vitest

Vitest is a Vite-native unit test framework:

bash
# Install Vitest
npm install -D vitest

Configure Vitest in vite.config.js:

javascript
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    coverage: {
      reporter: ['text', 'json', 'html']
    }
  }
})

Write tests:

javascript
// src/utils.test.js
import { describe, it, expect } from 'vitest'
import { sum } from './utils'

describe('sum function', () => {
  it('adds two numbers correctly', () => {
    expect(sum(1, 2)).toBe(3)
  })
  
  it('handles negative numbers', () => {
    expect(sum(-1, -2)).toBe(-3)
  })
})

Run tests:

bash
npx vitest

Component Testing

Test Vue components with Vitest and Vue Test Utils:

bash
npm install -D @vue/test-utils jsdom
javascript
// src/components/Counter.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from './Counter.vue'

describe('Counter.vue', () => {
  it('increments count when button is clicked', async () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('Count: 0')
    
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('Count: 1')
  })
})

End-to-End Testing

Use Cypress with Vite:

bash
npm install -D cypress

Configure Cypress for Vite:

javascript
// cypress.config.js
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:5173',
    supportFile: false
  }
})

Write E2E tests:

javascript
// cypress/e2e/basic.cy.js
describe('Basic test', () => {
  it('visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Hello Vite')
  })
  
  it('increments counter', () => {
    cy.visit('/')
    cy.contains('Count: 0')
    cy.get('button').contains('Increment').click()
    cy.contains('Count: 1')
  })
})

Deployment

Static Site Deployment

Build your Vite project for production:

bash
npm run build

The build output will be in the dist directory by default, which you can deploy to any static hosting service:

  • Netlify
  • Vercel
  • GitHub Pages
  • AWS S3 + CloudFront
  • Firebase Hosting
  • Cloudflare Pages

Example deployment to Netlify:

bash
# Install Netlify CLI
npm install -g netlify-cli

# Deploy to Netlify
netlify deploy --prod --dir=dist

Base Path Configuration

If your site is deployed to a subdirectory, configure the base path:

javascript
// vite.config.js
export default defineConfig({
  base: '/my-app/'
})

Environment Variables for Different Environments

Create environment-specific files:

.env                # Loaded in all environments
.env.local          # Loaded in all environments, ignored by git
.env.development    # Loaded in development mode
.env.production     # Loaded in production mode

Performance Optimization

Code Splitting

Vite automatically performs code splitting for dynamic imports:

javascript
// Async component loading
const UserProfile = () => import('./components/UserProfile.vue')

// Dynamic import with comments for chunk naming
import(/* webpackChunkName: "admin" */ './admin.js')

Preloading

Use <link rel="modulepreload"> to preload modules:

javascript
// vite.config.js
export default defineConfig({
  build: {
    modulePreload: {
      polyfill: true
    }
  }
})

Asset Optimization

Optimize assets during build:

javascript
// vite.config.js
export default defineConfig({
  build: {
    assetsInlineLimit: 4096, // 4kb
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        assetFileNames: 'assets/[name].[hash].[ext]'
      }
    }
  }
})

Integrations with Frameworks

Vue

bash
# Create a Vue project with Vite
npm create vite@latest my-vue-app -- --template vue

Configure Vue in vite.config.js:

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vue({
      reactivityTransform: true
    }),
    vueJsx()
  ]
})

React

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

Configure React in vite.config.js:

javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // Use React plugin options
      babel: {
        plugins: ['babel-plugin-macros']
      },
      jsxImportSource: '@emotion/react'
    })
  ]
})

Svelte

bash
# Create a Svelte project with Vite
npm create vite@latest my-svelte-app -- --template svelte

Configure Svelte in vite.config.js:

javascript
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    svelte({
      compilerOptions: {
        dev: !process.env.PRODUCTION
      }
    })
  ]
})

Best Practices

Project Structure

Organize your Vite project for maintainability:

src/
├── assets/         # Static assets
├── components/     # Reusable components
├── composables/    # Composition API functions (Vue)
├── hooks/          # Custom hooks (React)
├── layouts/        # Layout components
├── pages/          # Page components
├── router/         # Routing configuration
├── stores/         # State management
├── styles/         # Global styles
├── utils/          # Utility functions
└── main.js         # Entry point

Performance Tips

  1. Use dynamic imports for code splitting:

    javascript
    const AdminPanel = () => import('./components/AdminPanel.vue')
  2. Lazy load images with Intersection Observer:

    javascript
    import { useIntersectionObserver } from '@vueuse/core'
    
    const image = ref(null)
    const isVisible = ref(false)
    
    useIntersectionObserver(image, ([{ isIntersecting }]) => {
      if (isIntersecting) {
        isVisible.value = true
      }
    })
  3. Optimize dependencies:

    bash
    # Analyze bundle size
    npx vite-bundle-analyzer
  4. Use tree-shaking friendly imports:

    javascript
    // ❌ Bad - imports the entire library
    import lodash from 'lodash'
    
    // ✅ Good - imports only what's needed
    import { debounce } from 'lodash-es'

Security Best Practices

  1. Sanitize user input to prevent XSS attacks:

    javascript
    import DOMPurify from 'dompurify'
    
    const sanitizedHTML = DOMPurify.sanitize(userInput)
  2. Use environment variables for sensitive information:

    javascript
    const apiKey = import.meta.env.VITE_API_KEY
  3. Set proper CSP headers in production:

    javascript
    // server middleware
    app.use((req, res, next) => {
      res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self'"
      )
      next()
    })

Troubleshooting

Common Issues and Solutions

  1. HMR not working:

    • Check if the file is properly imported
    • Ensure the module has proper HMR acceptance code
    • Check for syntax errors
  2. Build errors:

    • Check for circular dependencies
    • Verify that all imports are correct
    • Look for missing dependencies in package.json
  3. CSS processing issues:

    • Ensure preprocessor packages are installed
    • Check preprocessor syntax
    • Verify configuration in vite.config.js
  4. Environment variables not working:

    • Ensure variables are prefixed with VITE_
    • Check that .env files are in the project root
    • Restart the dev server after changing .env files

Debugging Tips

  1. Enable source maps:

    javascript
    // vite.config.js
    export default defineConfig({
      build: {
        sourcemap: true
      }
    })
  2. Use Vite's debug logging:

    bash
    # Set the DEBUG environment variable
    DEBUG=vite:* npm run dev
  3. Inspect network requests in the browser's developer tools

  4. Check for plugin conflicts by disabling plugins one by one

Frequently Asked Questions

How does Vite compare to webpack?

Vite differs from webpack in several key ways:

  • Vite uses native ES modules during development, while webpack bundles everything
  • Vite has a faster startup time and HMR
  • Webpack has a larger ecosystem and more mature tooling
  • Vite uses Rollup for production builds, which can be more efficient for modern code

Can I use Vite with existing projects?

Yes, you can migrate existing projects to Vite:

  1. Install Vite and necessary plugins
  2. Create a vite.config.js file
  3. Update import statements to use ES modules syntax
  4. Adjust the HTML entry point
  5. Update scripts in package.json

How do I handle legacy browsers?

Use the @vitejs/plugin-legacy plugin:

javascript
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
})

How do I configure Vite for monorepos?

For monorepos, you can use:

  • Workspace packages with npm/yarn/pnpm
  • Vite's optimizeDeps.include option to include workspace dependencies
  • Shared configuration with defineConfig and composition
javascript
// vite.config.js
export default defineConfig({
  optimizeDeps: {
    include: ['@my-org/shared-lib']
  },
  resolve: {
    preserveSymlinks: true
  }
})

This document will be continuously updated. If you have any questions, please provide feedback through GitHub Issues.

VitePress Development Guide