cypress-io/cypress · error · Error

Missing vite dev server port.

Error message

Missing vite dev server port.

What it means

Thrown by @cypress/vite-dev-server after `server.listen()` resolves, when `server.config.server.port` is falsy. Vite normally assigns a port (random if 0 was requested); a missing port means the server did not bind or did not report its bound port back into config. Cypress needs a concrete port to point the browser at the dev server, so it refuses to proceed.

Source

Thrown at npm/vite-dev-server/src/devServer.ts:45

  let majorVersion: number | undefined = undefined

  if (vite.version) {
    majorVersion = semverMajor(vite.version)
    debug(`Found vite version v${majorVersion}`)
  } else {
    debug(`vite version not found`)
  }

  debug('Creating Vite Server')
  const server = await devServer.create(config, vite)

  debug('Vite server created')

  await server.listen()
  const { port } = server.config.server

  if (!port) {
    throw new Error('Missing vite dev server port.')
  }

  debug('Successfully launched the vite server on port', port)

  // Warm up the support file (always) and every spec (run mode only),
  // then waitForRequestsIdle, so Vite's deps optimizer has fully processed
  // any node_modules imports they pull in before the browser fetches
  // them. Skipping this can race a mid-test optimizer re-bundle and
  // surface "Failed to fetch dynamically imported module".
  //
  // Per-spec warmup is required: preprocessor or auto-import plugins can
  // inject node_modules imports during transform that Vite's static deps
  // scanner doesn't see, so the optimizer would otherwise first discover
  // them when the browser fetches the spec.
  //
  // In open mode (`isTextTerminal === false`), we skip the per-spec
  // warmup. The user picks specs interactively and is unlikely to run
  // every spec in the suite, so warming all of them up front would pay

View on GitHub (pinned to 0d85fdc912)

Solutions

  1. In your viteConfig passed to devServer, set an explicit `server.port` (e.g. 5173) instead of 0.
  2. Ensure no Vite plugin in your config overwrites server.port after listen.
  3. Update Vite to a version known to populate config.server.port after listen (Vite 5/6/7 supported per the package's matrix).
  4. If you need an ephemeral port, read the actual bound port from `server.httpServer.address()` in a custom plugin rather than relying on config.server.port.

Example fix

// before
devServer: { bundler: 'vite', viteConfig: { server: { port: 0 } } }
// after
devServer: { bundler: 'vite', viteConfig: { server: { port: 5173, strictPort: true } } }
Defensive patterns

Strategy: validation

Validate before calling

// choose a concrete port before starting
const PORT = 5173
devServer: { bundler: 'vite', viteConfig: { server: { port: PORT, strictPort: true } } }

Type guard

const hasConcretePort = (cfg: any): boolean => typeof cfg?.server?.port === 'number' && cfg.server.port > 0

Prevention

When it happens

Trigger: Calling devServer.create then server.listen() where the resulting server.config.server.port is undefined/0/null. Occurs when the user's viteConfig sets `server.port: 0` without `strictPort`, when a plugin clears the port, when listen fails silently, or when a Vite version reports the bound port elsewhere than config.server.port.

Common situations: A user vite.config that sets `server: { port: 0 }` (request OS-assigned port) but a Vite version that does not backfill the chosen port into config; strictPort conflicts; a misbehaving plugin that mutates server config; running on a restricted environment where listen did not actually bind.

Related errors


AI-assisted analysis of cypress-io/cypress@0d85fdc912 (2026-08-12). Data as JSON: /api/errors/a8f47eee81e09290. Report an issue: GitHub.