remix-run/react-router · error

Prerender (html): Received a

Error message

Prerender (html): Received a ${response.status} status code from `entry.server.tsx` while prerendering the `${pathname}` path.
${html}

What it means

During HTML prerendering, React Router requests each document path from the built server. Redirect responses are handled separately (they emit a meta-refresh page), but after that branch any non-200 document response is fatal, because a static file whose content is an error page would be deployed as if it were the real page. The response HTML is appended to the message so the failing page (often the error boundary output) is visible.

Solutions

  1. Read the HTML in the error message to identify the error boundary content and underlying exception
  2. Fix or guard the loader/middleware so the path renders 200 at build time (env vars, external API guards, cached data)
  3. Remove intentionally-protected or erroring paths from the `prerender` config
  4. If the redirect branch is expected, make sure the loader returns a proper 3xx redirect rather than an error page

Example fix

// before (app/routes/docs.$.tsx)
export async function loader({ params }: Route.LoaderArgs) {
  let doc = await fetch(`https://cms.example.com/${params['*']}`);
  if (!doc.ok) return new Response('missing', { status: 404 });
  return doc.json();
}

// after — exclude missing docs from prerender
// (react-router.config.ts)
export default {
  ssr: true,
  async prerender({ getStaticPaths }) {
    let paths = await getStaticPaths();
    return paths.filter(async (p) => !p.startsWith('/docs/deprecated/'));
  },
};
Defensive patterns

Strategy: validation

Validate before calling

// Validate every prerender path against a running build before the real build
for (const p of PRERENDER_PATHS) {
  const res = await fetch(`http://localhost:4173${p}`);
  if (!res.ok && !(res.status >= 300 && res.status < 400)) {
    console.warn(`Skipping ${p} — preview returned ${res.status}`);
  }
}

Try / catch

try {
  await build();
} catch (e) {
  if (e instanceof Error && /^Prerender \(html\)/.test(e.message)) {
    // the message embeds the error-page HTML — locate the failing loader from it
  }
}

Prevention

When it happens

Trigger: A prerendered document path where rendering throws and the framework renders an error document with a 500 status; a loader returning a non-redirect error status for that path; middleware rejecting the request; entry.server.tsx returning a custom non-200 response for that route.

Common situations: Prerendering marketing/blog pages whose loaders fetch a CMS that fails or 404s in CI; auth-gated routes mistakenly listed in `prerender`; a root-level middleware returning 401 for all requests without the right headers.

Related errors


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

Appendix: source

Thrown at packages/react-router-dev/vite/plugin.ts:2659

          // A short delay causes Google to interpret the redirect as temporary.
          // https://developers.google.com/search/docs/crawling-indexing/301-redirects#metarefresh
          let delay = response.status === 302 ? 2 : 0;
          let escapedLocation = escapeHtml(location ?? "");
          let escapedPathname = escapeHtml(pathname);
          html = `<!doctype html>
<head>
<title>Redirecting to: ${escapedLocation}</title>
<meta http-equiv="refresh" content="${delay};url=${escapedLocation}">
<meta name="robots" content="noindex">
</head>
<body>
	<a href="${escapedLocation}">
    Redirecting from <code>${escapedPathname}</code> to <code>${escapedLocation}</code>
  </a>
</body>
</html>`;
        } else if (response.status !== 200) {
          throw new Error(
            `Prerender (html): Received a ${response.status} status code from ` +
              `\`entry.server.tsx\` while prerendering the \`${pathname}\` ` +
              `path.\n${html}`,
          );
        }

        return [
          {
            path: `${pathname}/index.html`,
            contents: html,
          },
        ];
      },
      logFile(outputPath, metadata) {
        invariant(viteConfig);
        invariant(metadata);
        // SPA fallback logging is handled in finalize after we know the final filename
        if (metadata.type === "spa") {

View on GitHub (pinned to 6beaca3952)