Vite Cheat Sheet
Covers scaffolding projects, vite.config.ts options, environment variables, dev server proxying, and the production build/preview workflow.
Scaffolding & Configuration
Creating a project and configuring the dev server.
# Scaffold a new projectnpm create vite@latest my-app -- --template react-tscd my-app && npm install && npm run dev
vite.config.ts
Plugins, server proxy, and path aliases.
import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';export default defineConfig({ plugins: [react()], server: { port: 5173, proxy: { '/api': 'http://localhost:4000', // proxy API calls during dev }, }, resolve: { alias: { '@': '/src' }, },});
Environment Variables
Exposing config to client code safely.
// .env// VITE_API_URL=https://api.example.com// Usage in client code - only VITE_ prefixed vars are exposedconsole.log(import.meta.env.VITE_API_URL);console.log(import.meta.env.MODE); // 'development' | 'production'console.log(import.meta.env.DEV); // booleanconsole.log(import.meta.env.PROD); // boolean
Build & Preview
Production build and local preview commands.
npm run build # tsc && vite build -> outputs to dist/npm run preview # serve the production build locallyvite build --outDir build # custom output directory
Key Features
What makes Vite's architecture fast.
- Native ESM dev server- serves source files over native ES modules, no bundling needed in dev
- esbuild pre-bundling- dependencies are pre-bundled with esbuild for a fast cold start
- Rollup production build- uses Rollup under the hood for an optimized production bundle
- Hot Module Replacement (HMR)- near-instant updates in the browser without a full page reload
- import.meta.glob()- eagerly or lazily import multiple modules matching a glob pattern
- Plugin API- Rollup-compatible plugin interface plus Vite-specific hooks
Writing a Custom Plugin
A minimal Rollup-compatible plugin using Vite-specific hooks for dev-only transforms.
// plugins/virtual-config.jsexport default function virtualConfigPlugin(options = {}) { const virtualModuleId = 'virtual:app-config'; const resolvedId = '\0' + virtualModuleId; return { name: 'virtual-config', resolveId(id) { if (id === virtualModuleId) return resolvedId; }, load(id) { if (id === resolvedId) { return `export default ${JSON.stringify(options)}`; } }, transform(code, id) { if (id.endsWith('.svg') && this.environment?.config.command === 'serve') { return { code: `export default ${JSON.stringify(code)}`, map: null }; } }, configureServer(server) { server.middlewares.use('/health', (req, res) => res.end('ok')); }, };}
SSR Build Pipeline
Building separate client and server bundles for server-side rendering.
# package.json scripts# "build:client": "vite build --outDir dist/client"# "build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server"npm run build:clientnpm run build:server
SSR Dev Middleware Mode
Attaching Vite as middleware inside an Express server for SSR with HMR in dev.
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 template = await vite.transformIndexHtml( req.originalUrl, '<!doctype html>...<div id="root"></div>' ); const { render } = await vite.ssrLoadModule('/src/entry-server.tsx'); const html = template.replace('<!--ssr-outlet-->', await render(req.originalUrl)); res.status(200).set({ 'Content-Type': 'text/html' }).end(html); }); app.listen(5173);}createServer();
Manual Chunk Splitting & Dep Pre-Bundling
Forcing large vendor libraries into stable, cacheable chunks and controlling esbuild pre-bundling.
export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { 'vendor-react': ['react', 'react-dom'], 'vendor-charts': ['recharts', 'd3-scale'], }, }, }, chunkSizeWarningLimit: 800, }, optimizeDeps: { include: ['deep-nested-cjs-lib'], // force pre-bundle a dep esbuild misses exclude: ['@my-org/linked-package'], // skip pre-bundling a linked/workspace package esbuildOptions: { target: 'es2020' }, },});
Advanced Concepts & APIs
Deeper mechanics behind Vite's dev/build split.
- Pre-bundling cache invalidation- esbuild's dependency cache (in node_modules/.vite) is invalidated automatically when package.json, lockfile, or vite.config change
- import.meta.hot- the low-level HMR API (accept, dispose, invalidate) that framework plugins like @vitejs/plugin-react build their fast refresh on top of
- transformIndexHtml hook- plugin hook for injecting or rewriting tags into index.html during both dev and build
- applyToEnvironment / apply option- restricts a plugin hook to run only during 'serve' or 'build', avoiding unnecessary work in the other mode
- CSS code splitting- CSS imported by an async chunk is extracted into its own file and loaded alongside that chunk automatically
- define- vite.config option for global constant replacement at build time, similar to webpack's DefinePlugin
- vite preview --outDir- serves a built output directory statically to sanity-check the production bundle without a full deploy
Only environment variables prefixed with VITE_ are exposed to client code via import.meta.env - this is a deliberate security boundary, so never prefix real secrets that way or they'll end up in your client bundle.