The Role Vite Plays in Vue

The Role Vite Plays in Vue

What Vite actually does in a Vue project, from dev server to production build, and why it replaced Vue CLI.

Reza Baar

Reza Baar

May 13, 2026

The Role Vite Plays in Vue

When you scaffold a Vue project today with pm create vue@latest, you get Vite as the build tool. But what does Vite actually do? And why did the Vue ecosystem move away from Vue CLI and webpack? In this post, we'll look at each piece of what Vite handles in a Vue project and why it matters.

Vite Is Not a Vue Tool

First, an important distinction. Vite is a general-purpose build tool. It works with React, Svelte, Solid, and plain JavaScript too. What makes it work with Vue is a plugin: @vitejs/plugin-vue.

When you create a Vue project, the generated vite.config.ts looks like this:

      // vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
});

    

Vite itself provides the dev server, the build pipeline, and the module system. The Vue plugin teaches Vite how to understand .vue files. Without the plugin, Vite has no idea what a Single File Component is.

What Happens During Development

When you run npm run dev, Vite starts a dev server. Here's what it does differently from the webpack-based Vue CLI.

No Bundling in Dev

Vue CLI with webpack would bundle your entire app before serving it. For large projects, this meant waiting 30 seconds or more for the dev server to start.

Vite doesn't bundle in development. Instead, it serves your source files directly as native ES modules. When the browser requests a file, Vite transforms it on demand and sends it back.

      Browser requests /src/App.vue
  → Vite intercepts the request
  → @vitejs/plugin-vue compiles the SFC
  → Vite sends back JavaScript the browser can execute

    

The startup is nearly instant because Vite only processes files as they're requested, not the entire dependency graph upfront.

Pre-Bundling Dependencies

There's one exception to the "no bundling" rule. Your node_modules dependencies (Vue itself, any UI library, etc.) are pre-bundled using esbuild on first startup. This happens because:

  • Many npm packages ship CommonJS modules, not ES modules
  • A package like lodash-es has hundreds of internal modules that would mean hundreds of HTTP requests

Vite bundles these into single ES modules and caches them in node_modules/.vite. This happens once and is cached until your dependencies change.

      # You'll see this on first startup
Pre-bundling dependencies:
  vue
  vue-router
  pinia

    

SFC Compilation

The @vitejs/plugin-vue plugin is responsible for turning .vue files into JavaScript. A Single File Component like this:

      <!-- src/components/Counter.vue -->
<script setup lang="ts">
const count = ref(0);
</script>

<template>
  <button @click="count++">Count: {{ count }}</button>
</template>

<style scoped>
button {
  font-size: 1.2rem;
}
</style>

    

Gets compiled into roughly three parts:

  1. The <script> block becomes a JavaScript module
  2. The <template> block is compiled into a render function by @vue/compiler-sfc
  3. The <style scoped> block is extracted and injected as CSS with scoped selectors

The compiled output looks something like:

      // Simplified compiled output
import { ref, openBlock, createElementBlock, toDisplayString } from 'vue';

const _sfc_main = {
  setup() {
    const count = ref(0);
    return { count };
  },
};

function _sfc_render(_ctx) {
  return (
    openBlock(),
    createElementBlock(
      'button',
      { onClick: () => _ctx.count++ },
      'Count: ' + toDisplayString(_ctx.count)
    )
  );
}

// Style is injected separately with scoped data attributes

    

You never see this output directly. Vite handles it transparently.

🔥 Hot Module Replacement (HMR)

This is the feature you interact with most. When you edit a .vue file and save, the change appears in the browser almost instantly without a full page reload.

Vite's HMR works at the module level. When you change Counter.vue, Vite only recompiles that one file and sends the update over a WebSocket connection to the browser. The Vue plugin hooks into Vue's component system to hot-swap the component while preserving its state.

      You edit Counter.vue
  → Vite detects the file change
  → @vitejs/plugin-vue recompiles just that SFC
  → Vite sends a HMR update via WebSocket
  → The browser replaces the component
  → Component state (like count) is preserved

    

With webpack, HMR updates slowed down as projects grew because the bundler had to re-analyze the dependency graph. Vite's approach stays fast regardless of project size because it only processes the changed file.

What Happens During Build

When you run npm run build, Vite switches from its dev-mode behavior to a production build powered by Rollup (for now - March 2026).

      // vite.config.ts
export default defineConfig({
  plugins: [vue()],
  build: {
    target: 'es2015',
    outDir: 'dist',
  },
});

    

The build pipeline:

  1. All source files are compiled (SFCs, TypeScript, etc.)
  2. Rollup bundles everything into optimized chunks
  3. Code splitting happens automatically at dynamic import() boundaries
  4. CSS is extracted and minified
  5. Static assets get content hashes for cache busting

The output lands in dist/:

      dist/
  index.html
  assets/
    index-abc123.js
    index-def456.css
    vendor-ghi789.js

    

Code Splitting with Vue Router

Vite and Rollup handle code splitting automatically when you use dynamic imports. With Vue Router, lazy-loaded routes create separate chunks:

      // src/router/index.ts
const routes = [
  {
    path: '/',
    component: () => import('../pages/Home.vue'),
  },
  {
    path: '/about',
    component: () => import('../pages/About.vue'),
  },
  {
    path: '/dashboard',
    component: () => import('../pages/Dashboard.vue'),
  },
];

    

Each import() becomes a separate chunk. The browser only downloads the code for the route the user visits. Vite handles the chunk naming and loading automatically.

TypeScript Support

Vite supports TypeScript out of the box. It uses esbuild for TypeScript transpilation, which is extremely fast. But there's an important detail: Vite strips types without type-checking.

      // This compiles fine in Vite even if the type is wrong
const count: string = 42;

    

Vite prioritizes speed over correctness during development. Type checking is left to your editor (VS Code with Volar or now knows is “Vue - Official”) and a separate vue-tsc step in CI:

      // package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "type-check": "vue-tsc --noEmit"
  }
}

    

The build script runs type checking first with vue-tsc, then builds with Vite. This keeps the dev experience fast while catching type errors before production.

Static Asset Handling

Vite handles imports of static assets like images, fonts, and SVGs:

      <script setup lang="ts">
import logoUrl from '../assets/logo.svg';
</script>

<template>
  <img :src="logoUrl" alt="Logo" />
</template>

    

During dev, logoUrl resolves to the file path. During build, the file is copied to dist/assets/ with a content hash, and the import resolves to the hashed filename.

For assets in the public/ directory, they're served as-is without processing:

      <template>
  <!-- Served directly from /public/favicon.ico -->
  <link rel="icon" href="/favicon.ico" />
</template>

    

Environment Variables

Vite exposes environment variables through import.meta.env:

      # .env
VITE_APP_TITLE=My Vue App
VITE_API_URL=https://api.example.com

    
      console.log(import.meta.env.VITE_APP_TITLE); // "My Vue App"
console.log(import.meta.env.MODE); // "development" or "production"
console.log(import.meta.env.DEV); // true in dev
console.log(import.meta.env.PROD); // true in production build

    

Only variables prefixed with VITE_ are exposed to client code. This prevents accidentally leaking server secrets into the browser bundle.

The Plugin System

Vite's plugin API is compatible with Rollup's, which means the Rollup plugin ecosystem works with Vite too. Common Vue-related plugins:

      // vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import AutoImport from 'unplugin-auto-import/vite';

export default defineConfig({
  plugins: [
    vue(),
    Components({
      // Auto-import components from ./src/components/
    }),
    AutoImport({
      imports: ['vue', 'vue-router'],
      // Auto-import Vue and Vue Router APIs
    }),
  ],
});

    

These plugins replicate some of what Nuxt gives you for free (auto-imports, component resolution) in a plain Vue project.

Why It Replaced Vue CLI

Vue CLI was built on webpack. It worked well for years, but had fundamental speed limitations:

  • Dev server startup required bundling the entire app
  • HMR updates got slower as the project grew
  • The configuration was complex and deeply tied to webpack internals
  • The build was slower than what Rollup and esbuild could deliver

Vite solved these by using native ES modules in dev and esbuild for transforms. The Vue team officially deprecated Vue CLI in favor of create-vue (which scaffolds Vite projects) and Vite is the recommended build tool in the Vue documentation.

Key Takeaways

  • Vite is a general build tool, not Vue-specific. @vitejs/plugin-vue is what makes it understand .vue files
  • In development, Vite serves files as native ES modules with no bundling, making startup nearly instant
  • @vitejs/plugin-vue compiles SFCs into render functions, JavaScript modules, and scoped CSS
  • HMR works at the individual file level, staying fast regardless of project size
  • Production builds use Rollup for optimized, code-split bundles
  • Vite transpiles TypeScript but doesn't type-check. Use vue-tsc for that
  • Vite replaced Vue CLI because native ES modules and esbuild are fundamentally faster than webpack bundling

Conclusion

Vite handles the entire dev-to-production pipeline for Vue projects: serving files in development, compiling SFCs, hot-reloading changes, and building optimized bundles. Understanding what it does at each step helps you debug build issues and configure your project with intention rather than copying config snippets.

I hope this post has been helpful. Please let me know if you have any questions or comments. Happy coding!

More certificates.dev articles

Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Looking for Certified Developers?

We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.

Let us help you get the resources you need.

Contact Us
Customer Testimonial for Hiring
like a breath of fresh air
Everett Owyoung
Everett Owyoung
Head of Talent for ThousandEyes
(a Cisco company)