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
- Set outdir to be a subdirectory of servedir
- Alternatively, set servedir to a parent directory that contains outdir
- 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
- Ensure outdir is a subdirectory of servedir
- Use path.relative() to verify the relationship before starting serve
- Set servedir to a common parent if outdir cannot be moved
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
- Cannot compute relative path from
- Invalid fallback path
- Invalid serve path
- The working directory
- Cannot serve without an output path
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.HostView on GitHub (pinned to f6058f8364)