withastro/astro · error · Error

[@astrojs/node] Could not find the server directory

Error message

[@astrojs/node] Could not find the server directory "${serverFolder}" by walking up from "${import.meta.url}". This can happen when the server entry point is bundled into a single file (e.g. with esbuild) so that import.meta.url no longer contains the original "${serverFolder}" path segment. When bundling the server entry, make sure the output path contains a "${serverFolder}" directory segment, or avoid bundling the server entry entirely.

What it means

At runtime the Node adapter walks up from import.meta.url (shared.ts) until it finds a directory whose name equals basename(options.server) (the build.server folder, default 'server'); it uses that anchor to compute the relative path from the server bundle to the client assets. If the server entry was re-bundled into a file whose path no longer contains that segment, the walk reaches the filesystem root and throws with this explanation.

Solutions

  1. Emit the bundle inside the server folder so the path keeps the segment, e.g. dist/server/bundle.mjs
  2. Avoid re-bundling the server entry entirely and run dist/server/entry.mjs as built
  3. If you rename the folder, keep a path segment matching basename(config.build.server) in the final output location

Example fix

# before
esbuild dist/server/entry.mjs --bundle --outfile=dist/bundle.mjs
# after
esbuild dist/server/entry.mjs --bundle --outfile=dist/server/bundle.mjs
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
const serverFolder = path.basename(new URL(config.build.server).pathname); // default 'server'
const outSegments = path.resolve(outfile).split(path.sep);
if (!outSegments.includes(serverFolder)) {
  throw new Error(`bundle output must keep a '${serverFolder}' path segment`);
}

Prevention

When it happens

Trigger: Post-processing dist/server/entry.mjs with esbuild/webpack/pkg/etc. so the output lands outside a directory named 'server'; copying the bundled entry to another location before running it; renaming build.server without keeping the matching segment in the deployed output path.

Common situations: Deploy pipelines that bundle the server output into a single file at a path like dist/bundle.mjs; Docker images that flatten the build output; custom esbuild steps inherited from other adapters.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/8e0cbddd03bba46a. Report an issue: GitHub.

Appendix: source

Thrown at packages/integrations/node/src/shared.ts:37

export function resolveClientDir(options: Options) {
	// options.client and options.server are file:// URLs set at build time
	// e.g., "file:///project/dist/client/" and "file:///project/dist/server/"
	const clientURLRaw = new URL(options.client);
	const serverURLRaw = new URL(options.server);

	// Calculate relative path from server to client (e.g., "../client")
	// This relative path is stable regardless of where the build output is deployed
	const rel = path.relative(url.fileURLToPath(serverURLRaw), url.fileURLToPath(clientURLRaw));

	// Find the server entry folder by walking up from this file's location
	// We need to find the actual runtime location, not the build-time paths
	const serverFolder = path.basename(options.server);
	let serverEntryFolderURL = path.dirname(import.meta.url);
	let previous = '';
	while (!serverEntryFolderURL.endsWith(serverFolder)) {
		// Guard against infinite loop
		if (serverEntryFolderURL === previous) {
			throw new Error(
				`[@astrojs/node] Could not find the server directory "${serverFolder}" ` +
					`by walking up from "${import.meta.url}". This can happen when the server ` +
					`entry point is bundled into a single file (e.g. with esbuild) so that ` +
					`import.meta.url no longer contains the original "${serverFolder}" path segment. ` +
					`When bundling the server entry, make sure the output path contains a ` +
					`"${serverFolder}" directory segment, or avoid bundling the server entry entirely.`,
			);
		}
		previous = serverEntryFolderURL;
		serverEntryFolderURL = path.dirname(serverEntryFolderURL);
	}

	// Resolve the client directory by applying the relative path to the runtime server location
	const serverEntryURL = serverEntryFolderURL + '/entry.mjs';
	const clientURL = new URL(appendForwardSlash(rel), serverEntryURL);
	return url.fileURLToPath(clientURL);
}

View on GitHub (pinned to 52e6c34790)