remix-run/react-router · error

Prerender: Request timed out for

Error message

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

What it means

Each prerender request is issued with an `AbortSignal.timeout(...)` (default 10000ms, configurable via the prerender options `timeout`). The default `handleError` inspects `request.signal?.aborted`: if the abort fired, it rethrows as a timeout so you can distinguish slow pages from broken ones. Timeouts usually mean the page's loaders or render are genuinely slow (external API latency, large pages, cold caches) rather than broken.

Solutions

  1. Raise the per-request budget: pass a larger `timeout` (ms) in the prerender options
  2. Profile and speed up the slow loader(s): parallelize fetches, add caching, trim payload sizes
  3. Increase `concurrency` carefully or reduce prerendered paths to shorten overall wall time
  4. If a path can never finish in time, remove it from prerender and render it on demand

Example fix

// before
prerender({ /* default timeout 10000ms */ });

// after
prerender({ timeout: 60000 });
Defensive patterns

Strategy: retry

Validate before calling

// Time a representative path before choosing a timeout budget
const start = performance.now();
await fetch(`${base}${slowestPath}`);
const needed = performance.now() - start;
await prerender({ timeout: Math.ceil(needed * 2) });

Type guard

const isTimeoutError = (e: Error): boolean =>
  /^Prerender: Request timed out/.test(e.message);

Try / catch

try {
  await prerender({ timeout: 10000 });
} catch (e) {
  if (e instanceof Error && isTimeoutError(e)) {
    await prerender({ timeout: 60000 }); // one deliberate retry with a bigger budget
  } else throw e;
}

Prevention

When it happens

Trigger: A prerendered path whose combined loaders + render exceed the timeout (default 10s) — slow third-party fetches, N+1 queries, huge HTML; running CI on constrained/slow machines; setting `timeout` too low in prerender options.

Common situations: Blog/store pages aggregating many API calls at build time; rate-limited APIs slowing builds; large path lists where concurrency compounds latency.

Understand the failure class

Related errors


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

Appendix: 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 6beaca3952)