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

  1. Run `vite build` (or your build script) before `vite preview` so the outDir is populated.
  2. Verify config.environments.client.build.outDir / build.outDir matches the directory your build actually writes to.
  3. If previewing custom output, implement a configurePreviewServer plugin so Vite does not enforce the default clientOutDir check.
  4. 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

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


AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03). Data as JSON: /data/errors/03e2e8ea89093d4e.json. Report an issue: GitHub.