remix-run/react-router · error · Error

Failed to clone server response

Error message

Failed to clone server response

What it means

Before building the HTML response, the RSC SSR server clones the handler's `Response` (`detectRedirectResponse`) and decodes that clone to detect redirects and errors thrown during render. If the clone has no body, the original response's stream was already used or disturbed — `Response.clone()` of a consumed stream yields an empty body — and this error throws. It is the classic Web Streams footfall: a response body can only be read once.

Source

Thrown at packages/react-router/lib/rsc/server.ssr.tsx:198

          deepestRenderedBoundaryId = boundaryId;
        },
      },
      formState: {
        get() {
          return payloadPromise.then((payload) =>
            payload.type === "render" ? payload.formState : undefined,
          );
        },
      },
    }) as DecodedPayload;
  };

  let renderRedirect: { status: number; location: string } | undefined;
  let renderError: unknown;

  try {
    if (!detectRedirectResponse.body) {
      throw new Error("Failed to clone server response");
    }
    const payload = (await createFromReadableStream(
      detectRedirectResponse.body,
    )) as RSCPayload;
    if (
      serverResponse.status === SINGLE_FETCH_REDIRECT_STATUS &&
      payload.type === "redirect"
    ) {
      if (hasInvalidProtocol(payload.location)) {
        throw new Error("Invalid redirect location");
      }

      const headers = new Headers(serverResponse.headers);
      headers.delete("Content-Encoding");
      headers.delete("Content-Length");
      headers.delete("Content-Type");
      headers.delete("X-Remix-Response");
      headers.set("Location", payload.location);

View on GitHub (pinned to 7aea711dd1)

Solutions

  1. Clone before you read: `const clone = response.clone(); await clone.text(); return response;` — never return the instance you read from.
  2. Or reconstruct: read the body once and return `new Response(text, response)` with the original status/headers.
  3. Remove body-reading debug logs from middleware in production paths.
  4. If using an adapter, upgrade it — older adapters consumed the stream before the RSC pipeline could clone it.

Example fix

// before (middleware consumes the body)
const body = await response.text();
logger.debug(body);
return response;

// after (clone before reading)
const clone = response.clone();
logger.debug(await clone.text());
return response;
Defensive patterns

Strategy: validation

Validate before calling

// middleware that must inspect the body: clone first
const inspect = response.clone();
const text = await inspect.text();
log(text);
return response; // original still readable

Type guard

const isReadableResponse = (res: Response): boolean =>
  res.body != null && !res.body.locked;

Prevention

When it happens

Trigger: Custom middleware or `handleRequest` code calling `await response.text()`/`.json()` and then returning the same Response; passing a Response whose body lock was released via `body.cancel()`; adapters that tee/read the stream before handing it back to the framework.

Common situations: Logging response bodies in middleware during debugging; auth middleware inspecting responses before returning them; wrapping the framework handler with timing/audit layers that consume the body.

Related errors


AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18). Data as JSON: /api/errors/9e5bcb10d395d56b. Report an issue: GitHub.