remix-run/react-router · error

Prerender: Request failed for

Error message

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

What it means

When a prerender request throws before a response is produced (network error, DNS failure, connection reset to the preview server, aborted for a non-timeout reason), the default `handleError` rethrows with the path and underlying `error.message`. Unlike the timeout twin, this indicates the request itself failed — the preview server died, the connection dropped, or fetch-level machinery errored — rather than the page being slow.

Solutions

  1. Read `error.message` — `ECONNRESET`/`ECONNREFUSED` points at the preview server; reproduce with `vite preview` and curl the same path
  2. Reduce memory pressure: fewer concurrent prerenders, smaller pages, or `--max-old-space-size` increase for the build process
  3. Check that nothing kills the preview server during the build (watchers, supervisors, Windows firewall prompts)
  4. Retry the build once to distinguish transient socket issues from deterministic failures

Example fix

// before — default handleError rethrows everything
prerender({});

// after — tolerate transient socket errors with one retry
prerender({
  handleError: async (request, error, { retry }) => {
    if (/ECONNRESET|ECONNREFUSED/.test(error.message)) return retry();
    throw error;
  },
});
Defensive patterns

Strategy: retry

Validate before calling

// Health-check the preview server before issuing prerender requests
const ping = await fetch(base);
if (!ping.ok) throw new Error('Preview server unhealthy — aborting prerender');

Type guard

const isTransientNetworkError = (e: Error): boolean =>
  /ECONNRESET|ECONNREFUSED|EPIPE|socket hang up/i.test(e.message);

Try / catch

try {
  await prerender({});
} catch (e) {
  if (e instanceof Error && isTransientNetworkError(e)) {
    await prerender({}); // single retry for transient socket failures
  } else throw e;
}

Prevention

When it happens

Trigger: The Vite preview server crashing or restarting mid-prerender; `ECONNREFUSED`/`ECONNRESET` because the server exited early; custom fetch adapters (the plugin can use node:http instead of global fetch) misconfigured; proxy or firewall interference on localhost.

Common situations: Preview server running out of memory while rendering many paths; antivirus/firewall blocking loopback connections on Windows; wrapper tooling killing the server on first output.

Related errors


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

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