withastro/astro · error · Error
Failed to prerender
Error message
Failed to prerender ${request.url}: ${prerenderError} What it means
When the Cloudflare adapter prerenders a URL, the workerd handler reports exceptions through an `x-astro-prerender-error` response header, because the Vite preview server may strip the error body. The Node side checks that header first and rethrows the remote error wrapped in this message. Non-2xx statuses alone are not errors here — only the header marks failure, since pages like a custom 404 may intentionally return non-200 while prerendering.
Solutions
- Read the prerenderError text — it is the original exception message/stack from inside workerd.
- Reproduce by running `wrangler dev` against the built worker (or `astro dev` with the adapter) and opening the same URL.
- Replace or guard Node-only APIs with web-standard equivalents available in workerd.
- Mark the failing page as server-rendered (`export const prerender = false`) if it cannot be built statically.
- Provide required environment variables/secrets for the build if the error mentions a missing binding or env var.
Example fix
// before — page crashes under workerd at build
import { readFileSync } from 'node:fs';
const data = JSON.parse(readFileSync('data.json', 'utf-8'));
// after — static import bundled for any runtime
import data from './data.json'; Defensive patterns
Strategy: try-catch
Try / catch
try {
await build({ root: './' });
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
const m = msg.match(/Failed to prerender (\S+): (.*)/s);
if (m) {
console.error(`Page ${m[1]} failed under workerd:`, m[2]);
// mark for exclusion or fix, then re-run
process.exitCode = 1;
} else throw e;
} Prevention
- Develop against the workerd runtime (wrangler dev / adapter dev proxy), not bare Node, when targeting Cloudflare.
- Guard environment-specific APIs behind feature checks so prerender falls back gracefully.
- Declare pages that need live bindings/secrets as `prerender = false`.
When it happens
Trigger: `astro build` with @astrojs/cloudflare prerendering a page whose render throws inside workerd: an uncaught exception in frontmatter, component code, or middleware for that exact request.url.
Common situations: Pages using Node-only APIs (fs, crypto internals, native modules) that work in `astro dev` but fail under workerd; a database/NPC client initialized at render time failing in the build sandbox; accessing request headers/env vars missing during build.
Related errors
- Failed to get static paths from the Cloudflare prerender…
- the prerender server responded
- Failed to get static images from the Cloudflare prerender…
- Could not optimize " " with the Cloudflare IMAGES binding (…
- Failed to start the Cloudflare prerender server. The…
AI-assisted analysis of withastro/astro@e294953aa8 (2026-09-09).
Data as JSON: /api/errors/ea7fb30939f4aec3.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/cloudflare/src/prerenderer.ts:275
url: request.url,
routeData: serializeRouteData(routeData, trailingSlash),
collectMetadata,
};
const response = await fetch(`${serverUrl}${PRERENDER_ENDPOINT}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
redirect: 'manual',
});
// Check for prerender errors surfaced by the workerd handler via header
// (the response body may be stripped by the Vite preview server).
// Only the header marks a failure: pages may intentionally return
// non-2xx responses while prerendering (e.g. a custom 404 page).
const prerenderError = response.headers.get('x-astro-prerender-error');
if (prerenderError) {
throw new Error(`Failed to prerender ${request.url}: ${prerenderError}`);
}
if (collectMetadata) {
return readFramedPrerenderResponse(response);
}
return response;
},
collectStaticImages:
hasBuildImageService || hasBindingImageService
? async (): Promise<AssetsGlobalStaticImagesList> => {
const response = await fetch(`${serverUrl}${STATIC_IMAGES_ENDPOINT}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {View on GitHub (pinned to e294953aa8)