vite-mastery

0.5 · difficulty 1/4 · 12 min read

Vite CLI Workflow

A practical workflow for create-vite, dev, build, preview, --host, --mode, --debug, and --force.

Vite 8.1Stable

Two CLIs, Two Jobs

There are two command families in a Vite workflow:

CommandPurposeWhen to use it
create-viteScaffold a projectAt the start of a project
viteRun the current projectEvery day after creation

pnpm create vite@latest my-app --template react-ts creates the template. After that, your daily workflow is powered by the locally installed vite package:

json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

Do not keep running create-vite inside an existing app. To upgrade Vite, update the vite dependency in package.json and reinstall.

vite dev: The Development Server

vite, vite dev, and vite serve are equivalent for development:

bash
pnpm dev

This command validates that the config can load, dev-only plugins work, the browser can request source files through native ESM, and HMR updates the page after edits.

The dev server is not a production server. It keeps development behavior enabled: on-demand transforms, error overlays, the HMR websocket, unminified output, and dependency pre-bundling caches.

Expose the Server to Your LAN

By default, the dev server only listens locally. To test on a phone or another device:

bash
pnpm vite --host 0.0.0.0

For teams that do this often:

json
{
  "scripts": {
    "dev:host": "vite --host 0.0.0.0"
  }
}

Fixed Ports for Automation

If the default port is busy, Vite tries the next port. That is convenient locally, but bad for proxy setup, screenshots, and backend templates:

bash
pnpm vite --port 5173 --strictPort

--strictPort means "fail if this port is unavailable" instead of silently switching to 5174.

vite build: Production Build

bash
pnpm build

The production build validates the HTML entry, the full import graph, static assets, dynamic imports, chunking, and build-only plugin hooks.

Dev passing does not guarantee build passing:

Works in dev, fails in buildWhy dev may miss it
A build-only plugin throwsIt may be skipped by apply: "build" in dev
Dynamic import cannot be statically analyzedThe route may not be requested during dev
Node-only code leaks into browser outputBuild scans the full graph
Path casing differsmacOS may be case-insensitive, CI/Linux is not

For docs and examples in this repo, anything touching plugins, build, or assets should be verified with pnpm build:web or the relevant example build.

vite preview: Preview Built Output

vite preview serves an existing build output. It does not rebuild:

bash
pnpm build
pnpm preview

It validates that dist/ can be served as static files, base is correct, JS/CSS/assets load, and dynamic import chunks are requested from production paths.

Do not treat vite preview as a production server. It is for local verification, not caching, TLS, logging, auth, or multi-instance deployment.

--mode: Choose an Environment Mode

Mode controls which .env files Vite loads and the value of import.meta.env.MODE:

bash
vite dev --mode staging
vite build --mode staging

A typical setup:

text
.env
.env.local
.env.staging
.env.production

vite build --mode staging loads .env and then .env.staging; mode-specific variables win on conflicts.

--mode is not NODE_ENV. vite build --mode development is still a production build, but import.meta.env.MODE becomes "development" and .env.development is loaded. The next chapter, 1.5 · Env Variables and Modes, covers the details.

--debug and DEBUG=vite:*

When startup is slow, dependencies keep re-optimizing, or HMR fails, turn on logs before guessing:

bash
pnpm vite --debug
pnpm vite --debug deps
pnpm vite --debug hmr

Or use namespaces:

bash
DEBUG=vite:deps pnpm dev
DEBUG=vite:resolve pnpm dev
DEBUG=vite:hmr pnpm dev
NamespaceUseful for
vite:depsdependency pre-bundling and cache invalidation
vite:resolvealiases, conditional exports, path resolution
vite:hmrHMR boundaries and circular dependencies

These logs are noisy. Use them while diagnosing, not in the default dev script.

--force: Rebuild Optimized Dependencies

Vite caches optimized dependencies. If you linked local packages, changed overrides, or the browser still loads stale optimized output:

bash
pnpm vite --force

Use it when a workspace package changes module format, dependency overrides change, or node_modules/.vite looks out of sync with the lockfile.

Do not add --force to the default script. It sacrifices startup speed and should stay a repair command.

json
{
  "scripts": {
    "dev": "vite",
    "dev:host": "vite --host 0.0.0.0",
    "build": "vite build",
    "build:staging": "vite build --mode staging",
    "preview": "vite preview --strictPort",
    "debug:deps": "DEBUG=vite:deps vite",
    "debug:hmr": "DEBUG=vite:hmr vite"
  }
}

The rule is simple: keep the default path short, make risky commands explicit, split debug commands out, and keep build separate from preview.

Check Yourself

  1. What is the difference between create-vite and the vite CLI?
  2. Why is --strictPort useful in CI or screenshot tests?
  3. Is vite build --mode staging a development build?
  4. When should you use --force, and why should it not be permanent?
bash
# Design package scripts for:
# - normal local development
# - LAN development for mobile testing
# - staging build
# - strict preview of built output
# - dependency pre-bundling debug logs