evanw/esbuild · error

Cannot compute relative path from %q to %q

Error message

Cannot compute relative path from %q to %q

What it means

Returned by internalContext.Serve when ctx.realFS.Rel(servedir, AbsOutputDir) fails (ok=false). esbuild needs to compute the output directory relative to the serve directory to correctly URL-map served files; if the filesystem layer cannot relativize the two paths (e.g. cross-volume, or a path resolution failure), it gives up with this error. This is distinct from the 'must be contained' error — here the rel computation itself failed.

Source

Thrown at pkg/api/serve_other.go:791

	// Stuff related to the output directory only matters if there are entry points
	outdirPathPrefix := ""
	if len(ctx.args.entryPoints) > 0 {
		// Don't allow serving when builds are written to stdout
		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

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Place the output directory on the same volume/drive as the serve directory.
  2. Verify both Servedir and outdir exist and are absolute before calling Serve().
  3. On Windows, match drive letters / UNC hosts for both paths.
  4. If outdir is auto-derived, override it to live under servedir.

Example fix

// before (cross-volume)
ctx.Serve({ Servedir: '/mnt/www' }); // outdir on /dev/shm/build

// after (same volume)
mv /dev/shm/build /mnt/www/build   // or set outdir under servedir
ctx = await api.Context({ ..., outdir: '/mnt/www/build' });
ctx.Serve({ Servedir: '/mnt/www' });
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function sameVolume(a, b) { /* compare volume root of resolved paths */ }
if (Servedir && outdir && !sameVolume(Servedir, outdir)) throw new Error('cross-volume');

Try / catch

try { await ctx.Serve({ Servedir }); } catch (e) { if (/Cannot compute relative path/.test(e.message)) { /* move outdir under servedir, same volume */ } throw e; }

Prevention

When it happens

Trigger: Serve() is called with a Servedir and the context has an AbsOutputDir, but realFS.Rel(Servedir, AbsOutputDir) returns ok=false. Typically because the two paths are on different volumes/drives or one cannot be canonicalized, echoing the Rel failure in internal/fs/filepath.go.

Common situations: On Windows, servedir on C:\ and outdir on D:\; one path on a network mount and the other local; a path that fails to absolutize (nonexistent dir) feeding into Rel; cross-device builds where output is written to a separate mount.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/248a88498d73b8bb.json. Report an issue: GitHub.