evanw/esbuild · error

Output directory must be contained in serve directory

Error message

Output directory %q must be contained in serve directory %q

What it means

When both servedir and outdir are specified, the output directory must be contained within the serve directory. This error fires when the computed relative path from servedir to outdir starts with '..' (i.e. outdir is outside servedir), because the serve mode would not be able to serve the build output.

Solutions

  1. Set outdir to be a subdirectory of servedir
  2. Alternatively, set servedir to a parent directory that contains outdir
  3. Ensure the relative path from servedir to outdir does not start with '..'

Example fix

// before
esbuild.serve({ servedir: 'public' }) // outdir: '../build'
// after
esbuild.serve({ servedir: 'public' }) // outdir: 'public/build'
Defensive patterns

Strategy: validation

Validate before calling

import path from 'path'
function validateOutdirContained(servedir, outdir) {
  const rel = path.relative(path.resolve(servedir), path.resolve(outdir))
  if (rel === '..' || rel.startsWith('..')) {
    throw new Error(`outdir must be inside servedir, but relative path is: ${rel}`)
  }
}
validateOutdirContained(serveOptions.servedir, buildOptions.outdir)

Prevention

When it happens

Trigger: Setting outdir to a path that is a sibling or ancestor of servedir rather than a descendant, e.g. servedir='./public' and outdir='../build'.

Common situations: Misconfiguring the directory relationship, common when migrating from a non-serve build setup where outdir was set independently of servedir.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/b7b593a50f58efad. Report an issue: GitHub.

Appendix: source

Thrown at pkg/api/serve_other.go:813

		if ctx.args.options.WriteToStdout {
			what := "entry points"
			if len(ctx.args.entryPoints) == 1 {
				what = "an entry point"
			}
			return ServeResult{}, fmt.Errorf("Cannot serve %s without an output path", what)
		}

		// Compute the output path prefix
		if serveOptions.Servedir != "" && ctx.args.options.AbsOutputDir != "" {
			// Make sure the output directory is contained in the "servedir" directory
			relPath, ok := ctx.realFS.Rel(serveOptions.Servedir, ctx.args.options.AbsOutputDir)
			if !ok {
				return ServeResult{}, fmt.Errorf(
					"Cannot compute relative path from %q to %q\n", serveOptions.Servedir, ctx.args.options.AbsOutputDir)
			}
			relPath = strings.ReplaceAll(relPath, "\\", "/") // Fix paths on Windows
			if relPath == ".." || strings.HasPrefix(relPath, "../") {
				return ServeResult{}, fmt.Errorf(
					"Output directory %q must be contained in serve directory %q",
					prettyPrintPath(ctx.realFS, ctx.args.options.AbsOutputDir),
					prettyPrintPath(ctx.realFS, serveOptions.Servedir),
				)
			}
			if relPath != "." {
				outdirPathPrefix = relPath
			}
		}
	}

	// Determine the host
	var listener net.Listener
	network := "tcp4"
	host := "0.0.0.0"
	hostIsIP := true
	if serveOptions.Host != "" {
		host = serveOptions.Host

View on GitHub (pinned to f6058f8364)