What is Vite?
Vite is a build tool with two jobs: a development server that is almost instantly ready, and a production build that produces optimised bundles. It was created by Evan You in 2020 and has become the default toolchain for most modern front-end frameworks.
The key insight is that development and production have different bottlenecks. In development you want fast startup and fast updates, so Vite does not bundle at all — it serves your source over native ES modules and transforms files on demand. In production you want the smallest, fastest output, so Vite uses Rollup to bundle and optimise.
The development server
Running vite starts a dev server that serves your source as native ES modules. The browser requests each module, and Vite transforms it on the fly.
# terminal
vite # start the dev server
vite build # production build
vite preview # preview the production build locally
Because there is no full bundle to produce, startup is measured in milliseconds and stays constant as the project grows. Dependencies are pre-bundled once with esbuild, since they change rarely, which avoids a flood of small requests. Hot module replacement then updates only the modules that changed, preserving application state where possible.
The production build
vite build runs the Rollup pipeline and writes the result to dist by default.
- Tree-shaking removes unused exports.
- Code splitting produces chunks for routes and dynamic imports.
- Minification shrinks JavaScript, CSS and HTML.
- Asset hashing appends content hashes for long-term caching.
- CSS handling extracts and minifies stylesheets.
The output is a set of static files you can deploy to any host, CDN or static platform.
Configuration
Vite reads a vite.config.ts file at the project root.
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
resolve: {
alias: { "@": "/src" },
},
server: {
port: 5173,
proxy: {
"/api": "http://localhost:8787",
},
},
build: {
outDir: "dist",
sourcemap: true,
},
});
The resolve.alias option creates import shortcuts, and server.proxy forwards API requests to a backend during development, which avoids CORS issues. Most framework setups generate this file for you and only need small additions.
Plugins
Plugins are how Vite supports frameworks and tooling. They implement a Rollup-compatible interface with additional Vite-specific hooks.
// plugins.ts
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [vue()],
});
Common plugins cover React, Vue, Svelte and Solid, plus CSS tools such as Tailwind, legacy browser support, PWA features and bundle analysis. Because the interface is Rollup-compatible, a large portion of the Rollup plugin ecosystem works directly.
Environment variables
Vite loads variables from .env files and exposes only those prefixed with VITE_ to client code.
# .env
VITE_API_URL=https://api.example.com
DB_PASSWORD=secret
// env.ts
const url = import.meta.env.VITE_API_URL;
const isDev = import.meta.env.DEV;
The VITE_ prefix is a safety mechanism: anything without it stays out of the client bundle, so secrets cannot leak by accident. Variables are replaced at build time, not read at runtime, and are accessed through import.meta.env.
Assets and CSS
You import assets directly from JavaScript, and Vite handles the rest.
// assets.ts
import logo from "./logo.svg";
import "./styles.css";
import styles from "./Button.module.css";
Small assets are inlined as data URLs, larger ones are emitted with hashed names, and CSS imported this way is bundled and, in development, hot-reloaded. The public directory is for files that must keep an exact path, such as robots.txt or a favicon referenced by URL.
Code splitting
Dynamic import() creates a separate chunk loaded on demand, which is the main lever for keeping the initial bundle small.
// lazy.ts
const { Chart } = await import("./Chart");
Apply it to routes, modals, editors and any feature that is not needed on first paint. Frameworks built on Vite often handle route-level splitting for you, but understanding the primitive helps when you need finer control.
Beyond the app
Vite is not only for single-page apps. Library mode builds a distributable package with the right formats and externalised dependencies. Vite also supports server-side rendering, and it is the engine inside Vitest, Astro, Nuxt and SvelteKit. That shared foundation is why the same config, aliases and plugins work across development, testing and building.
Best practices
- Keep the config minimal and let framework plugins handle the details.
- Use
resolve.aliasfor clean imports instead of long relative paths. - Proxy the API in development rather than hardcoding a host.
- Prefix client env vars with
VITE_and keep secrets unprefixed. - Split heavy features with dynamic imports.
- Import assets from JavaScript so they get hashed and optimised.
- Enable source maps for production if you use an error tracker.
Common mistakes
- Expecting
process.envin client code instead ofimport.meta.env. - Exposing secrets by prefixing them with
VITE_. - Putting files in
publicthat should be imported and hashed. - Shipping one enormous bundle by never using dynamic imports.
- Adding plugins that duplicate what a framework plugin already does.
- Fighting the config instead of using a framework preset.
Where to go next
Vite is the foundation of the modern toolchain. Compare it with Webpack for older projects, run tests with Vitest, and see it power frameworks in the React, Astro and SvelteKit guides. Then open a project’s vite.config.ts and make one deliberate change to see the effect.