Skip to content

Vite Deep Dive — Dev Server, HMR, Plugins, SSR & Build Optimization

DodaTech Updated 2026-06-20 6 min read

In this tutorial, you'll learn about Vite Deep Dive. We cover key concepts, practical examples, and best practices.

Vite is a next-generation build tool that leverages native ES modules for a fast dev server and Rollup for optimized production builds. This deep dive covers how Vite works under the hood, its plugin API, SSR support, library mode, environment variables, CSS handling, and a practical comparison with Webpack.

ℹ️ Info

What you'll learn: Vite's ESM-based dev server architecture, HMR protocol, Rollup build pipeline, plugin development, SSR setup, library mode for publishing packages, env variable patterns, CSS/PostCSS integration, and how Vite compares with Webpack. Why it matters: Vite is the default build tool for Vue, Svelte, Astro, and increasingly React/Next.js projects. Real-world use: DodaZIP's frontend dashboard uses Vite with React, TypeScript, and Tailwind — compiling 300+ components in under 2 seconds for HMR.

Learning Path

flowchart LR
  A["Build Tools
Webpack Basics"] --> B["Vite
This Tutorial"] B --> C["Vite Plugins
Custom Development"] C --> D["SSR & Library Mode
Advanced Vite"] B --> E["Production Build
Rollup Optimization"] style B fill:#f90,color:#fff,stroke-width:2px

How Vite's Dev Server Works

flowchart TD
  subgraph "Vite Dev Server"
    B["Native ESM
Server"] C["HMR
WebSocket"] D["Pre-bundling
esbuild"] end E["Browser"] -->|"import /src/App.vue"| B B -->|"Transform on the fly"| E C <-->|"Hot update"| E D -->|"node_modules
dependencies"| B

Vite serves source files as native ES modules. The browser imports them directly — no bundling during development.

npx create-vite@latest my-app --template react
cd my-app
npm run dev

Expected output:

VITE v6.0.0  ready in 135ms
➜  Local:   http://localhost:5173/

HMR in Detail

Vite's HMR uses a WebSocket connection between server and browser:

// vite.config.js — Adding custom HMR handling
export default {
  server: {
    hmr: {
      protocol: 'ws',
      host: 'localhost',
      port: 24678,
      overlay: true
    }
  }
}

Each framework integration implements handleHotUpdate:

if (import.meta.hot) {
  import.meta.hot.accept('./module.js', (newModule) => {
    console.log('Module updated:', newModule)
  })
}

Rollup-Based Production Build

npm run build

Expected output:

vite v6.0.0 building for production...
✓ built in 3.2s
dist/index.html  0.48 kB
dist/assets/index-D1x2E3F4.js  142.30 kB

Configure Rollup options:

export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash-es', 'date-fns']
        }
      }
    },
    target: 'es2020',
    minify: 'esbuild', // or 'terser' for IE11 support
    sourcemap: true
  }
}

Plugins

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

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

Custom Plugin Example

function myPlugin() {
  return {
    name: 'my-plugin',
    enforce: 'pre',
    transform(code, id) {
      if (id.endsWith('.special.txt')) {
        return {
          code: `export default ${JSON.stringify(code)}`,
          map: null
        }
      }
    }
  }
}

SSR (Server-Side Rendering)

// vite.config.js
export default defineConfig({
  build: {
    ssr: 'src/entry-server.jsx'
  }
})
// server.js
import express from 'express'
import { createServer } from 'vite'

const app = express()
const vite = await createServer({
  server: { middlewareMode: true },
  appType: 'custom'
})

app.use(vite.middlewares)
app.use('*', async (req, res) => {
  const { render } = await vite.ssrLoadModule('/src/entry-server.jsx')
  const html = await render(req.url)
  res.send(html)
})

Library Mode

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.ts',
      name: 'MyLib',
      formats: ['es', 'cjs', 'umd']
    },
    rollupOptions: {
      external: ['react', 'react-dom']
    }
  }
})

Build creates dist/my-lib.mjs, dist/my-lib.cjs, and dist/my-lib.umd.js.

Environment Variables

// .env
VITE_API_URL=https://api.example.com
// .env.production
VITE_API_URL=https://api.prod.example.com
console.log(import.meta.env.VITE_API_URL)

Only VITE_ prefixed variables are exposed. Access import.meta.env.MODE for development or production.

CSS Handling

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

Vite vs Webpack

Feature Vite Webpack
Dev server approach Native ESM, no bundling Bundle all modules
Cold start ~100–300ms ~5–30s
HMR Instant (< 50ms) 200ms–2s
Production bundler Rollup webpack
Configuration Minimal, sensible defaults Extensive, verbose
Plugin ecosystem Growing, Rollup-compatible Mature, massive
Best for New projects (2024+) Legacy projects, complex webpack configs

Common Errors

  1. Cannot find module in production but works in dev — Dev server uses ESM resolution; production uses Rollup. If you use bare imports without installing the package, dev works but build fails. Always install dependencies.
  2. HMR not working with symlinked packages — Vite's file watcher doesn't follow symlinks by default. Use server.watch.ignored: false or set resolve.symlinks: false.
  3. Environment variables returning undefined — Only VITE_ prefixed variables are exposed. Non-prefixed variables are not available in client code.
  4. SSR build errors with Node.js built-ins — Vite's SSR build runs in Node.js, not the browser. Use build.rollupOptions.external to exclude Node modules.
  5. Legacy browser support issues — Vite targets modern browsers. For IE11 or older Chrome, use @vitejs/plugin-legacy which generates fallback chunks.
  6. Asset paths wrong in production — Set base: '/my-app/' in config if your app is served from a subdirectory.
  7. TypeScript path aliases not resolved in build — Configure resolve.alias in vite.config.js matching your tsconfig.json paths.
  8. CSS modules not applying — File must be named *.module.css (or .scss, .less). Plain .css files don't activate CSS modules.

Practice Questions

1. Why is Vite's dev server faster than Webpack's? Vite serves files as native ES modules without bundling. Only the requested file is transformed. Webpack must bundle and rebuild the entire dependency graph on every change.

2. How do you create a library with Vite? Use build.lib configuration with entry point, name, and formats. The build produces ESM, CJS, and/or UMD outputs.

3. What's the difference between import.meta.env.MODE and import.meta.env.PROD? MODE returns the string 'development' or 'production'. PROD returns a boolean (true in production mode).

4. How does Vite handle CSS imports? Vite supports .css, .scss, .less, .styl, and CSS modules (.module.css) out of the box. PostCSS is applied automatically if postcss.config.js exists.

5. Challenge: Build a Vite plugin Create a Vite plugin that: reads all .<a href="/apis/graphql/">graphql</a> files in the src/ directory, inlines them as JavaScript template literals, and adds a virtual module virtual:<a href="/apis/graphql/">graphql</a>-schema that exports all queries combined.

Mini Project: Multi-Page Vite App

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: '/index.html',
        admin: '/admin.html'
      }
    }
  }
})
npm run build

Produces dist/index.html, dist/admin.html, and shared assets.

FAQ

Can I use Vite for production?

Yes. Thousands of production sites use Vite. The build output is optimized with Rollup, tree-shaking, code splitting, and asset hashing.

Does Vite work with TypeScript?

Yes, natively. Vite transpiles TypeScript via esbuild during dev and Rollup plugins during build. It does NOT type-check — use tsc --noEmit separately.

How do I migrate from Webpack to Vite?

Install vite and @vitejs/plugin-react (or your framework's plugin), move config to vite.config.js, and adjust import paths. Many Webpack loaders have Vite equivalents.

What's the difference between Vite and Turbopack?

Vite is mature and stable. Turbopack (Next.js) is experimental and faster for very large apps but only works within Next.js.

Does Vite support monorepos?

Yes. Vite has native workspace support and integrates with pnpm workspaces, npm workspaces, and Turborepo.

GitHub Actions Deep Dive
ESLint & Prettier Setup
Jest Testing Deep Dive

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro