withastro/astro · critical · AstroError
The server entrypoint doesn't have a handler. Are you sure t
Error message
The server entrypoint doesn't have a handler. Are you sure this is the right file?
What it means
Thrown by the @astrojs/node preview server when the dynamically imported server entry module has no `handler` export. The preview server needs `handler` to forward HTTP requests to the built Astro app; a module without it is almost certainly the wrong file.
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 toView on GitHub (pinned to d081033d5f)
Solutions
- Re-run `astro build` so the adapter regenerates `dist/server/entry.mjs` with the correct `handler` export.
- Verify nothing overrides `adapter.serverEntrypoint`; the default `@astrojs/node/server.js` is what emits `handler`.
- If you customize the server entry, ensure it does `export const handler = createHandler(...)` (or `export { handler }`).
Example fix
// before: custom server.ts exporting `export default app` // after: custom server.ts export const handler = app; export default app;
Defensive patterns
Strategy: try-catch
Validate before calling
const mod = await import(serverEntryUrl);
if (typeof mod.handler !== 'function') {
throw new Error(`Refusing to start preview: ${serverEntryUrl} has no handler export`);
} Type guard
function isServerModule(m: unknown): m is { handler: (req: unknown, res: unknown) => void } {
return !!m && typeof (m as any).handler === 'function';
} Try / catch
try { await import(entry); } catch (e) {
if (/doesn't have a handler/) { /* re-run build or fix entry */ }
else throw e;
} Prevention
- Always run `astro build` before `astro preview`.
- Do not override the adapter's `serverEntrypoint` unless your custom entry exports `handler`.
- Pin the @astrojs/node version so the entry shape stays stable.
When it happens
Trigger: Running `astro preview` when the built `dist/server/entry.mjs` (the configured `serverEntrypoint`) does not export a function named `handler`. This happens if the adapter/server entry path was overridden, or a custom build emitted a differently-shaped entry.
Common situations: Pointing the preview at a hand-rolled server file. A bundler config that renamed or wrapped the default export. Mixing adapter versions where an older server entry used a different export name.
Related errors
- The server entrypoint ${fileURLToPath(preview.serverEntrypoi
- Body size limit exceeded: received more than ${limit} bytes
- Astro couldn't find the correct page to render, probably bec
- Astro couldn't find the correct page to render, probably bec
- AdapterSupportOutputMismatch
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/48c0d9eff6bccdba.
Report an issue: GitHub.