withastro/astro · error · AstroError

The server entrypoint doesn't have a handler. Are you sure…

Error message

The server entrypoint doesn't have a handler. Are you sure this is the right file?

What it means

astro preview with the Node adapter dynamically imports the built SSR entry (preview.serverEntrypoint, normally dist/server/entry.mjs) and requires a named handler export that the adapter's server build produces. If the module imports fine but handler is not a function, preview refuses to start because it has nothing to serve requests with.

Solutions

  1. Delete dist/ and re-run astro build with the Node adapter configured (output: 'server') so the real entry with a named handler is regenerated
  2. If you use a custom server entry, export the handler: export const handler = (req, res) => app(req, res)
  3. Verify preview.serverEntrypoint / outDir were not redirected to a non-adapter file

Example fix

// before (custom entry)
export default app;
// after
export const handler = (req, res) => app(req, res);
Defensive patterns

Strategy: validation

Validate before calling

import { pathToFileURL } from 'node:url';
const entry = 'dist/server/entry.mjs';
const mod: any = await import(pathToFileURL(entry).href);
if (typeof mod.handler !== 'function') {
  throw new Error(`${entry} has no handler export; rebuild with the node adapter`);
}

Type guard

type ServerModule = { handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void> };
function isServerModule(mod: unknown): mod is ServerModule {
  return typeof (mod as any)?.handler === 'function';
}

Prevention

When it happens

Trigger: The server entry was overwritten or replaced (custom build step, post-processing script) so it default-exports instead of exporting handler; previewing an outDir built by a different setup; a stale or partially overwritten dist where the entry no longer matches the adapter's expected shape.

Common situations: Custom bundling/patching of dist/server/entry.mjs; switching adapters or output modes without cleaning dist; entry templates that export app instead of handler.

Related errors


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

Appendix: source

Thrown at packages/integrations/node/src/preview.ts:18

import { fileURLToPath } from 'node:url';
import type { CreatePreviewServer } from 'astro';
import { AstroError } from 'astro/errors';
import { logListeningOn } from './log-listening-on.js';
import { createServer } from './standalone.js';

type ServerModule = typeof import('./server.js');
type MaybeServerModule = Partial<ServerModule>;

const createPreviewServer: CreatePreviewServer = async (preview) => {
	let ssrHandler: ServerModule['handler'];
	try {
		process.env.ASTRO_NODE_AUTOSTART = 'disabled';
		const ssrModule: MaybeServerModule = await import(preview.serverEntrypoint.toString());
		if (typeof ssrModule.handler === 'function') {
			ssrHandler = ssrModule.handler;
		} else {
			throw new AstroError(
				`The server entrypoint doesn't have a handler. Are you sure this is the right file?`,
			);
		}
	} catch (err) {
		if (
			(err as any).code === 'ERR_MODULE_NOT_FOUND' &&
			(err as any).url === preview.serverEntrypoint.href
		) {
			throw new AstroError(
				`The server entrypoint ${fileURLToPath(
					preview.serverEntrypoint,
				)} does not exist. Have you ran a build yet?`,
			);
		} else {
			throw err;
		}
	}
	// If the user didn't specify a host, it will already have been defaulted to

View on GitHub (pinned to 52e6c34790)