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

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

DodaTech Updated Jun 20, 2026 6 min read

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.

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<br/>Webpack Basics"] --> B["Vite<br/>This Tutorial"]
  B --> C["Vite Plugins<br/>Custom Development"]
  C --> D["SSR & Library Mode<br/>Advanced Vite"]
  B --> E["Production Build<br/>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<br/>Server"]
    C["HMR<br/>WebSocket"]
    D["Pre-bundling<br/>esbuild"]
  end
  E["Browser"] -->|"import /src/App.vue"| B
  B -->|"Transform on the fly"| E
  C <-->|"Hot update"| E
  D -->|"node_modules<br/>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

FeatureViteWebpack
Dev server approachNative ESM, no bundlingBundle all modules
Cold start~100–300ms~5–30s
HMRInstant (< 50ms)200ms–2s
Production bundlerRollupwebpack
ConfigurationMinimal, sensible defaultsExtensive, verbose
Plugin ecosystemGrowing, Rollup-compatibleMature, massive
Best forNew 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 .graphql files in the src/ directory, inlines them as JavaScript template literals, and adds a virtual module virtual:graphql-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.

Related Tutorials

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

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro