remix-run/react-router · critical · Error
Missing body in server response
Error message
Missing body in server response
What it means
In the RSC SSR server pipeline, once a request is identified as a normal document (HTML) request — not a data, manifest, or RSC-action request, and not one where the handler already returned an RSC resource response — the returned server `Response` must have a body so the server can decode it, detect redirects, and build the HTML shell. A bodyless response at this point is treated as a programming error in the request handler and throws.
Source
Thrown at packages/react-router/lib/rsc/server.ssr.tsx:127
hydrate?: boolean;
nonce?: string;
}): Promise<Response> {
const url = new URL(request.url);
const isDataRequest = isReactServerRequest(url);
const respondWithRSCPayload =
isDataRequest ||
isManifestRequest(url) ||
request.headers.has("rsc-action-id");
if (
respondWithRSCPayload ||
serverResponse.headers.get("React-Router-Resource") === "true"
) {
return serverResponse;
}
if (!serverResponse.body) {
throw new Error("Missing body in server response");
}
const detectRedirectResponse = serverResponse.clone();
let serverResponseB: Response | null = null;
if (hydrate) {
serverResponseB = serverResponse.clone();
}
const body = serverResponse.body;
let buffer: Uint8Array[] | undefined;
let streamControllers: ReadableStreamDefaultController<Uint8Array>[] = [];
const createStream = () => {
if (!buffer) {
buffer = [];
return body.pipeThrough(View on GitHub (pinned to 7aea711dd1)
Solutions
- Make every document-request code path return a Response with a real body (HTML string or stream), or use the framework's redirect utilities instead of hand-built bodyless responses.
- If you must return an empty status (e.g., 204), scope it to non-document requests or set `React-Router-Resource: true` handling appropriately so it bypasses the HTML pipeline.
- Ensure middleware returns a fresh Response when it has consumed the original body.
- Log `response.status`/`response.body` in your handler before returning to find the offending branch.
Example fix
// before (custom handleRequest)
if (isBlocked) {
return new Response(null, { status: 204 });
}
// after
if (isBlocked) {
return new Response("<html><body>Blocked</body></html>", {
status: 403,
headers: { "content-type": "text/html" },
});
} Defensive patterns
Strategy: validation
Validate before calling
// in a custom handleRequest, before returning
if (!response.body && !request.headers.has("rsc-action-id")) {
return new Response("<html><body>Empty response</body></html>", {
status: 200,
headers: { "content-type": "text/html" },
});
}
return response; Type guard
const hasBody = (res: Response): boolean => res.body != null;
Prevention
- Never return bodyless Responses from document-request handlers or middleware.
- Use the framework's redirect helpers instead of hand-constructed 3xx Responses.
- Unit-test custom middleware paths to assert every branch returns a bodied Response.
When it happens
Trigger: A custom `handleRequest`/middleware returning `new Response(null, { status: 204 })` or any `Response` with a null body for document requests; returning the same `Response` object after its stream was already consumed elsewhere; redirect helpers misused so a bodyless 3xx reaches the document pipeline.
Common situations: Writing custom framework-mode server entries or middleware that short-circuit with empty responses; adapting express/cloudflare adapters that construct bare `Response` objects; middleware that returns `next()` results after reading them.
Related errors
- Failed to clone server response
- Prerender (data): Received a ${response.status} status code
- Invalid redirect location
- You cannot call `runClientMiddleware()` from a static handle
- No response body
AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18).
Data as JSON: /api/errors/d9a809ffa0e86c82.
Report an issue: GitHub.