remix-run/react-router · error · Error

Prerender: Request timed out for ${prerenderPath}: ${error.m

Error message

Prerender: Request timed out for ${prerenderPath}: ${error.message}

What it means

Thrown by `defaultHandleError` when a prerender request errors and its `request.signal` is `aborted`. The abort path indicates the request exceeded the prerender timeout (configured via `prerender.concurrency`/per-request timeout) and Vite cancelled it. The error message surfaces the underlying abort reason via `error.message`.

Source

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

  if (!response.ok) {
    throw new Error(
      `Prerender: Request failed for ${prerenderPath}: ${response.status} ${response.statusText}`,
    );
  }

  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.

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Add a timeout to any outbound fetch in loaders (e.g. `AbortSignal.timeout(2000)`).
  2. Cache loader responses during the build so repeated prerenders reuse data.
  3. Tune `prerender.concurrency` downward to reduce contention.
  4. Profile slow loaders and precompute their data into static files consumed at build time.

Example fix

// before: loader fetches with no timeout
export const loader = () => fetch("https://slow-upstream/feed");
// after
export const loader = () =>
  fetch("https://slow-upstream/feed", {
    signal: AbortSignal.timeout(3000),
  });
Defensive patterns

Strategy: retry

Validate before calling

// Bound every outbound fetch in loaders
export const loader = () =>
  fetch(url, { signal: AbortSignal.timeout(3000) });

Try / catch

try {
  await prerender(cfg);
} catch (e) {
  if (/Request timed out/.test(String(e.message))) {
    // tune timeout/concurrency and retry once
    await prerender({ ...cfg, prerender: { ...cfg.prerender, concurrency: 1 } });
  } else throw e;
}

Prevention

When it happens

Trigger: A loader or renderer takes longer than the configured prerender timeout; an outbound fetch in a loader hangs (no timeout of its own); a slow CMS/database call during build; `prerender.concurrency` too high so requests queue past the per-request deadline.

Common situations: Large dynamic routes that fetch heavy data on each prerender; loaders calling external APIs without a timeout; CI environments with cold upstreams; concurrency set higher than the upstream can serve.

Understand the failure class

Related errors


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