Vite Cheatsheet - Dev Server, Build & Config

Vite trades dev-server startup for a near-instant experience via native ESM and on-demand compilation, and its config reads differently from Webpack. This reference covers scaffolding, the dev server, production build, environment variables, plugins, proxy and SSR builds, and calls out where it diverges from legacy bundling. Use it when starting a project, migrating an older Webpack setup, or when a config you set "just doesn't apply". After reading you can take a project from scaffolding to a production build, and know exactly where to edit for dev vs build.

Dev Tools·44 commands·Last updated 2026-07-21
vitewebpackBuild ToolsFrontendbundler

Typical Use Case

For developers building front-end projects with Vite, covering everything from project scaffolding to SSR and library mode. Two groups deserve priority: environment variables, where client-side vars must carry the VITE_ prefix and must never hold secrets; and configuration options, where base determines the deployment path and build.target determines browser support. CLI flags and vite.config.ts options are listed separately, because the two take effect at different times and are debugged in different ways.

Project Creation & Init 5

npm create vite@latest
Create new project, interactive template selection
npm create vite@latest my-app -- --template react-ts
Specify template: react-ts / vue-ts / svelte-ts
npm create vite@latest my-app -- --template vanilla-ts
Plain TypeScript template, no framework
npm install
Install project dependencies
npm run dev
Start dev server (default port 5173)

Dev Server 7

vite
Start dev server, default port 5173
vite --port 3000
Specify port number
vite --host 0.0.0.0
Listen on all network interfaces, LAN access
vite --open
Auto open browser on start
vite --cors
Enable CORS headers
vite --strictPort
Exit if port is taken instead of auto-increment
vite --force
Force re-run dependency pre-bundling

Production Build 7

vite build
Production build, outputs to dist
vite build --outDir dist
Specify output directory (default dist)
vite build --watch
Watch mode, rebuild on file changes
vite build --sourcemap
Generate source maps for debugging
vite build --minify esbuild
Use esbuild for minification (default terser)
vite preview
Preview build output, simulates production
vite build --report
Generate build report (requires plugin)

Configuration 7

import { defineConfig } from 'vite'
Use defineConfig for type hints and autocomplete
root: './src'
Project root directory, defaults to current dir
base: '/my-app/'
Public base path, set when deploying to sub-path
build.outDir: 'dist'
Build output directory
build.assetsDir: 'assets'
Static assets subdirectory
build.target: 'es2020'
Build target browser version
build.emptyOutDir: true
Empty output directory before build

Env Variables & Modes 6

import.meta.env.VITE_API_URL
Client env vars, must have VITE_ prefix
import.meta.env.MODE
Current mode: development / production
import.meta.env.DEV
Boolean, whether in dev mode
import.meta.env.PROD
Boolean, whether in production mode
vite build --mode staging
Build with specified mode, loads .env.staging
loadEnv(mode, process.cwd(), '')
Load env vars in vite.config.ts

Plugins & Integration 6

import vue from '@vitejs/plugin-vue'
Vue 3 SFC support plugin
import react from '@vitejs/plugin-react'
React Fast Refresh support plugin
import { visualizer } from 'rollup-plugin-visualizer'
Bundle size visualization
vite-plugin-pwa
PWA progressive web app support
vite-plugin-checker
TypeScript/ESLint type checking, async non-blocking
vite-plugin-inspect
Inspect Vite plugin intermediate state, debug plugins

SSR & Library Mode 6

build.ssr: true
Enable SSR server-side rendering build
build.ssrManifest: true
Generate SSR manifest for preload hints
build.lib: { entry: 'src/index.ts', name: 'MyLib' }
Library mode, package as publishable library
build.lib.formats: ['es', 'cjs', 'umd']
Library output formats: ESM / CommonJS / UMD
ssr.external: ['vue']
Externalize dependencies in SSR, not bundled
ssr.noExternal: ['my-lib']
Force bundle certain dependencies in SSR

Parameter matrix

ParameterEffectExample
vite --portSpecify the dev server portvite --port 3000
vite --host 0.0.0.0Listen on all network interfaces, reachable on the LANvite --host 0.0.0.0
vite --strictPortError out when the port is taken instead of auto-incrementingvite --strictPort
vite --forceForce a rebuild of dependency pre-bundlingvite --force
vite buildProduction build, outputs to distvite build --sourcemap
vite previewPreview the build output locallyvite preview
basePublic base path, required when deploying to a subpathbase: '/my-app/'
build.targetTarget browser version for the buildbuild: { target: 'es2020' }
import.meta.env.VITE_*Client env variables, must have the VITE_ prefiximport.meta.env.VITE_API_URL
vite build --modeSpecify the mode, loads .env.<mode>vite build --mode staging
build.outDirBuild output directory, defaults to distbuild: { outDir: 'dist' }
build.ssr / build.libSSR build / library mode bundlingbuild: { lib: { entry: 'src/index.ts' } }

Common pitfalls

SymptomReading import.meta.env.SECRET_KEY in code returns undefined.

CauseClient environment variables must have the VITE_ prefix and must not contain sensitive information (it gets bundled into the output).

FixRename it with the prefix; keep secrets server-side only and never in import.meta.env.

SymptomAfter deploying to a subpath the page is blank and assets 404.

Causebase was not set to the subpath.

FixSet base: '/my-app/' in vite.config.ts to match the actual mount path on the server.

SymptomA newly added dependency reports module not found, and restarting the dev server does not help.

CauseThe dependency pre-bundling cache is stale.

FixRun vite --force to rebuild the pre-bundle, or delete node_modules/.vite and restart.

Symptombuild reports a conflict between the browserslist target and the manual target.

CauseBoth build.target and package.json's browserslist are configured.

FixKeep only one of them; usually let build.target win and remove the conflicting config.

SymptomThe output is blank in old browsers.

CauseThe build target is too new and the emitted syntax is not supported by the target browsers.

FixLower build.target (such as 'es2015'), or add @vitejs/plugin-legacy.

SymptomIt works in development but API request paths are wrong after the production build.

CauseThe code uses hardcoded absolute paths or relies on the dev proxy config.

FixUse import.meta.env.VITE_API_URL consistently, and verify production behavior with vite preview.

Troubleshooting

  1. 1Confirm the current mode and available env variables

    vite build --mode staging

    MODE decides which .env file is loaded; cross-check with import.meta.env.MODE.

  2. 2Force a rebuild when dependency pre-bundling misbehaves

    vite --force

    Rebuilding the dependency optimization cache solves most "just-installed package not found" problems.

  3. 3Reproduce issues with a production build

    vite build && vite preview

    dev and build behave differently; preview exposes problems with the real output.

  4. 4Locate asset paths after deployment

    grep -oE 'src="[^"]*"' dist/index.html | head

    Check whether asset paths in the output carry the base prefix; a missing prefix is the root cause of 404s.

  5. 5Analyze the composition of the output size

    vite build --report

    Use rollup-plugin-visualizer to generate a visualization report and locate the biggest size contributors.

Command Examples

Scaffold with a template and start the dev server

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

The template name has to go after --, otherwise npm swallows it as its own option and you still get the interactive menu. dev defaults to port 5173 and silently walks up to 5174 when the port is taken — pin it with vite --port --strictPort whenever a script depends on the URL.

Output

VITE v5.x  ready in 214 ms

  ➜  Local:   http://localhost:5173/

Base config: sub-path deploy, asset dir, build target

import { defineConfig } from 'vite' export default defineConfig({ base: '/my-app/', build: { outDir: 'dist', assetsDir: 'assets', target: 'es2020', emptyOutDir: true, }, })

base sets the prefix baked into every asset reference; deploy to a sub-path and forget it and you ship a blank site full of 404s. emptyOutDir keeps stale files from lingering in dist and being picked up. A low target keeps old browsers working; too high a target may emit syntax the device cannot parse.

Per-mode env files behind the VITE_ prefix

# .env.staging VITE_API_URL=https://staging.example.com // Read in code: env object + key assembly (only the VITE_ prefix is recognized) const key = "VITE_API_URL"; const base = import.meta.env[key]; // Load the specified mode's .env.staging at build time vite build --mode staging

Variables without the VITE_ prefix are never injected into client code, so reading API_URL there just yields undefined; use loadEnv in the config to read unprefixed vars on the Node side. --mode selects which .env file loads — omit it and you silently ship the development endpoint.

Library mode with ESM, CJS, and UMD output

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

Library mode has no HTML entry to discover your exports, so index.ts must export the public API explicitly or the bundle comes out empty. formats decides who can consume you: shipping only es breaks any CommonJS host with a module-not-found error.

SSR: externalize vue, inline your own library

export default defineConfig({ build: { ssr: true, ssrManifest: true, }, ssr: { external: ['vue'], noExternal: ['my-lib'], }, })

external leaves the dependency to be required on the server instead of bundled; noExternal is for built-in CJS or your own libraries that read source paths internally. They point opposite ways, and misconfiguring one gives the classic server-side "Cannot find module".

Common Pitfalls

  • Build-time text replacement reaches into plain string literals: a contiguous env-var expression inside code or a comment is substituted with a literal value. Put it in a tooltip, a URL template, or i18n text and the runtime string comes out wrong, and SSR and client output diverge.
  • Client env vars must carry the VITE_ prefix; unprefixed keys are permanently undefined in the browser. Do not assume a secret in .env stays private — adding the prefix publishes it in the bundle.
  • When dependency pre-bundling fails ("Failed to resolve import" or two copies of one package after a fresh install), editing .env or vite.config changes nothing. Rebuild the cache with vite --force.
  • If base does not match the deploy sub-path, every asset URL in the output is offset, giving a white page or 404 assets with nothing visible in DevTools.
  • A mode that matches no .env file (say --mode staging with only .env.production) does not error; it silently falls back to the base .env and ships the development endpoint to production.
  • vite preview is only a local static preview: it does not reproduce production cache headers, reverse proxies, or the SSR runtime. A clean preview does not mean a clean deploy, and SSR builds still need build.ssr plus ssrManifest wired up.

Tips

  • Vite dev server uses native ESM, no bundling needed for instant cold start
  • Use vite.config.ts for configuration with full TypeScript support
  • Environment variables with VITE_ prefix are exposed to client code
  • Use defineConfig utility for type hints and autocomplete
  • Vite has first-class support for Vue, React, Svelte, Lit frameworks

FAQ

Vite silently moved to another port when 5173 was busy — how do I stop that?

By default Vite falls forward to 5174, 5175 and so on when 5173 is taken, which keeps local work unblocked but silently breaks anything that hardcodes the URL — a shared doc, a proxy config, a colleague bookmark. Pass --strictPort to make the server fail fast instead: vite --strictPort 3000. For LAN development use vite --host 0.0.0.0, since it binds loopback only by default. And when dependency pre-bundling goes wrong after upgrading a package, vite --force discards the optimize cache and rebuilds it.

Why does my environment variable come back undefined in the browser?

Vite only exposes variables prefixed with VITE_ to client code. A key named MY_API_URL in .env is never bundled, so it must be VITE_API_URL to be readable as import.meta.env.VITE_API_URL. Unprefixed keys stay visible to the server and to your config file only — a deliberate safety boundary so secrets cannot ship to the browser. Built-ins include import.meta.env.MODE (development / production), DEV and PROD as booleans. To differentiate per mode, build with vite build --mode staging, which loads .env.staging, or call loadEnv(mode, process.env.cwd(), '') inside vite.config.ts.

Assets 404 after deploying to a sub-path — how do I fix it?

The build assumes site-root-relative URLs, so serving the bundle from /my-app/ breaks every asset reference. Set base: '/my-app/' in vite.config.ts and the emitted URLs gain that prefix; vite --base lets you try it out during development. Neighbouring options: build.outDir for the output directory (dist by default), build.assetsDir for the static asset sub-directory (assets by default), build.emptyOutDir to wipe the output directory before writing so stale files from older builds do not linger, and build.target for the browser target. After changing base, verify with vite preview, which serves the real build rather than the dev server.

What do build.ssr and build.lib actually produce, and how do they differ?

build.ssr: true emits a server-rendering bundle: the same source renders on the server, the HTML is assembled there and shipped down, and the client only hydrates. ssrManifest: true emits the preload manifest telling the client what to fetch early, while ssr.external: ['vue'] leaves listed dependencies to be required at runtime instead of bundling them, and ssr.noExternal: ['my-lib'] forces the opposite. build.lib: { entry: 'src/index.ts', name: 'MyLib' } is library mode — output meant for other projects to import — with build.lib.formats: ['es', 'cjs', 'umd'] choosing the module formats consumers need. Different goals; do not mix them.

My bundle keeps growing — how do I find out which dependency is responsible?

Vite ships no built-in analysis, so add rollup-plugin-visualizer: register it as a plugin in vite.config.ts and run one build, and it produces an interactive treemap showing raw and bundled size per module. That tells you immediately whether a UI library, a locale file pulled in wholesale, or a polyfill is carrying most of the weight. Only then change how you import — switch to per-module imports or a lighter alternative. Note it is written as import { visualizer } from 'rollup-plugin-visualizer' and works as a Rollup plugin, so it belongs in the config rather than being treated as a Vite-only plugin.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us