remix-run/react-router · error · Error

Prerender: Request failed for ${prerenderPath}: ${error.mess

Error message

Prerender: Request failed for ${prerenderPath}: ${error.message}

What it means

Thrown by `defaultHandleError` for any prerender request error that is NOT an abort (i.e. `request.signal.aborted` is false). This is the catch-all for exceptions raised by loaders, renderers, or `entry.server.tsx` during the prerender pass. The error message forwards the underlying `error.message` for the failing path.

Source

Thrown at packages/react-router-dev/vite/plugins/prerender.ts:446

  return [
    {
      path: `${prerenderPath}/index.html`,
      contents: await response.text(),
    },
  ];
}

function defaultHandleError(request: Request, error: Error): void {
  const prerenderPath = new URL(request.url).pathname;

  if (request.signal?.aborted) {
    throw new Error(
      `Prerender: Request timed out for ${prerenderPath}: ${error.message}`,
    );
  }

  throw new Error(
    `Prerender: Request failed for ${prerenderPath}: ${error.message}`,
  );
}

/**
 * Issue prerender requests via `node:http` rather than the global `fetch`.
 *
 * Node's built-in `fetch` (undici) keeps a global dispatcher that pools
 * keep-alive sockets. On Windows, exiting the build process with multiple
 * pooled sockets to the Vite preview server still open triggers a libuv
 * assertion (`!(handle->flags & UV_HANDLE_CLOSING)` in `src/win/async.c`)
 * during teardown of the dispatcher's internal async handle. Closing or
 * destroying the dispatcher does not clear the bad state.
 *
 * `node:http` without an explicit Agent closes each connection cleanly, so we
 * use it here to avoid the assertion. Manual redirect handling is preserved.
 */
async function nodeHttpFetch(

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Reproduce by running `react-router build` with `--logLevel info` to capture the original stack trace from the preview server.
  2. Wrap flaky loader logic in try/catch and return deterministic fallbacks at build time.
  3. Validate the shape of data returned by loaders before rendering.
  4. Provide a custom `handleError` in your prerender config to log full context (Sentry, etc.) instead of only `error.message`.

Example fix

// before: loader throws on missing data
export const loader = () => {
  return JSON.parse(cachedBody).items.map(i => i.name);
};
// after
export const loader = () => {
  try {
    return JSON.parse(cachedBody).items.map(i => i.name);
  } catch {
    return [];
  }
};
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate loader output shape before render
const assertShape = (data: unknown) => {
  if (typeof data !== "object" || data === null || !("items" in data)) {
    throw new Error("loader returned unexpected shape");
  }
};

Try / catch

// Provide a custom handleError to log full context
export const prerender = {
  handleError(request, error) {
    console.error(request.url, error.stack);
    // swallow during build if acceptable, else rethrow
  },
};

Prevention

When it happens

Trigger: A loader throws a synchronous or asynchronous exception (network error, parse error, null deref); the renderer throws because of bad data shape; `entry.server.tsx` raises; an upstream dependency errors mid-request without aborting the signal.

Common situations: Loaders that throw on malformed data; missing env vars causing `undefined` access; type mismatches between loader output and component props; third-party SDK throwing during build.

Related errors


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/2a8bbbd21c65607c. Report an issue: GitHub.