vitejs/vite · error · Error
The directory "${clientOutDir}" does not exist. Did you buil
Error message
The directory "${clientOutDir}" does not exist. Did you build your project? What it means
preview() at packages/vite/src/node/preview.ts:153 checks that the client output directory exists before serving it. The throw is guarded by three conditions: the dir is missing, no plugin implements configurePreviewServer, and the process was launched as the `vite preview` CLI command. If any of those is false Vite stays silent (e.g. programmatic callers may serve their own files).
Source
Thrown at packages/vite/src/node/preview.ts:153
inlineConfig,
'serve',
'production',
'production',
true,
)
const clientOutDir = config.environments.client.build.outDir
const distDir = path.resolve(config.root, clientOutDir)
if (
!fs.existsSync(distDir) &&
// error if no plugins implement `configurePreviewServer`
config.plugins.every((plugin) => !plugin.configurePreviewServer) &&
// error if called in CLI only. programmatic usage could access `httpServer`
// and affect file serving
process.argv[1]?.endsWith(path.normalize('bin/vite.js')) &&
process.argv[2] === 'preview'
) {
throw new Error(
`The directory "${clientOutDir}" does not exist. Did you build your project?`,
)
}
const httpsOptions = await resolveHttpsConfig(config.preview.https)
const app = connect() as Connect.Server
const httpServer = await resolveHttpServer(app, httpsOptions)
setClientErrorHandler(httpServer, config.logger)
const options = config.preview
const logger = config.logger
const closeHttpServer = createServerCloseFn(httpServer)
// Promise used by `server.close()` to ensure `closeServer()` is only called once
let closeServerPromise: Promise<void> | undefined
const closeServer = async () => {
teardownSIGTERMListener(closeServerAndExit)View on GitHub (pinned to 89620f09af)
Solutions
- Run `vite build` (or your build script) before `vite preview` so the outDir is populated.
- Verify config.environments.client.build.outDir / build.outDir matches the directory your build actually writes to.
- If previewing custom output, implement a configurePreviewServer plugin so Vite does not enforce the default clientOutDir check.
- Invoke preview programmatically (not via the CLI) if you intend to serve files yourself and want to skip the guard.
Example fix
// before: preview runs with nothing built // $ vite preview // after: build first, then preview // $ vite build && vite preview
Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from 'node:fs'
import { resolveConfig } from 'vite'
const cfg = await resolveConfig({}, 'serve', 'production')
const outDir = cfg.environments.client.build.outDir
if (!existsSync(outDir)) {
throw new Error(`Run \`vite build\` first; ${outDir} is missing`)
} Prevention
- Always pair `vite preview` with a preceding `vite build` in scripts and CI.
- Keep build.outDir consistent across build and preview configs.
- If serving custom output, implement configurePreviewServer to opt out of the default check.
When it happens
Trigger: Running `vite preview` (or `npx vite preview`) before `vite build`; build wrote to a different outDir than config.environments.client.build.outDir points at; outDir was deleted between build and preview; a custom config sets a non-default build.outDir that was never populated.
Common situations: CI/ deploy script that calls preview without a prior build step; switching build.outDir (e.g. to 'dist/static') in only one place; running preview against a freshly cloned repo with no build artifacts; renaming or cleaning the dist folder manually.
Related errors
- No environment found
- Either "build.lib.entry" or the top-level "input" option is
- rolldownOptions.input should not be an html file when buildi
- When "build.cssCodeSplit: false" is set, "rolldownOptions.in
- Vite does not support "rolldownOptions.output.file". Please
AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03).
Data as JSON: /data/errors/03e2e8ea89093d4e.json.
Report an issue: GitHub.