How to Fix: TypeError: Response.clone: Body has already been consumed.

6 min read

This error means a Fetch API Response body was read once, and then some code tried to read or clone it again. In a Next.js app, this usually appears when middleware, server utilities, logging, auth, or proxy code touches the same response stream more than once.

If you are reproducing this from the linked repository, the key idea is simple: response bodies are streams. Once consumed by json(), text(), arrayBuffer(), or similar readers, they cannot be consumed again unless you cloned the response before the first read.

Understanding the Root Cause

The browser and the server-side Fetch runtime treat a Response body as a one-time readable stream. Internally, when code calls methods like await response.json(), the stream becomes locked and then disturbed. After that, any later call such as:

await response.json();
response.clone();

or:

const a = await response.text();
const b = await response.json();

will fail, because the body is already gone.

The specific error TypeError: Response.clone: Body has already been consumed happens when clone() is called after something already read the stream. Cloning works by creating another readable branch of the original response body, but that is only possible while the original body is still untouched.

In Next.js, this can be surprisingly easy to trigger because multiple layers may inspect the same response:

  • Middleware or proxy logic
  • Authentication wrappers
  • Error logging that reads the body for diagnostics
  • Route handlers that parse the response and also return or re-clone it
  • Shared utility functions that assume they own the response

A common hidden pattern looks like this:

async function fetchData() {
  const response = await fetch('/api/data');

  console.log(await response.text());

  return response.clone();
}

This fails because the debug log already consumed the body. The bug is not in clone() itself. The bug is the order of operations.

Step-by-Step Solution

The fix is to ensure each response body is consumed exactly once, or cloned before any consumer reads it.

1. Find every place the response is being read

Search for these methods around the failing code path:

.json()
.text()
.arrayBuffer()
.formData()
.blob()
.clone()

Also check wrapper functions, helpers, middleware, and logging code. Often the response is consumed in a utility you forgot about.

2. Choose a single ownership model

Decide which layer is responsible for parsing the body.

Good pattern: one function fetches and parses, then returns parsed data.

export async function getUserData() {
  const response = await fetch('https://example.com/api/user');

  if (!response.ok) {
    throw new Error('Failed to fetch user data');
  }

  return response.json();
}

Then callers do not touch the raw response again.

3. If you truly need two consumers, clone first

If one branch needs logging and another branch needs actual parsing, clone immediately.

const response = await fetch('https://example.com/api/user');
const responseForLogging = response.clone();

const logBody = await responseForLogging.text();
console.log(logBody);

const data = await response.json();

This works because the clone was created before either stream was consumed.

4. Do not mix parsed-body access patterns

Avoid reading text first and then trying to parse JSON from the same response.

Incorrect:

const response = await fetch('https://example.com/api/user');
const raw = await response.text();
const data = await response.json();

Correct:

const response = await fetch('https://example.com/api/user');
const raw = await response.text();
const data = JSON.parse(raw);

Or just:

const response = await fetch('https://example.com/api/user');
const data = await response.json();

5. Fix wrapper utilities that accidentally consume the response

A very common issue in Next.js projects is a helper like this:

export async function fetchWithDebug(url) {
  const response = await fetch(url);

  if (!response.ok) {
    console.error(await response.text());
  }

  return response;
}

If the caller later does await response.json(), it may break when the error branch consumed the body.

Refactor it like this:

export async function fetchWithDebug(url) {
  const response = await fetch(url);

  if (!response.ok) {
    const copy = response.clone();
    console.error(await copy.text());
  }

  return response;
}

6. When returning a new Next.js response, build it from data, not a consumed body

In route handlers, you may read upstream JSON and then return a fresh response.

export async function GET() {
  const upstream = await fetch('https://example.com/api/user');
  const data = await upstream.json();

  return Response.json(data);
}

This is usually safer than trying to re-read or re-clone the original response later in the pipeline.

Typical Fix Patterns

Pattern 1: Logging plus parsing

Broken:

const response = await fetch(url);
console.log(await response.text());
const data = await response.json();

Fixed:

const response = await fetch(url);
const copy = response.clone();

console.log(await copy.text());
const data = await response.json();

Pattern 2: Middleware/helper consumes body before caller

Broken:

async function apiClient(url) {
  const response = await fetch(url);
  await response.text();
  return response;
}

const response = await apiClient(url);
const data = await response.json();

Fixed:

async function apiClient(url) {
  const response = await fetch(url);
  return response;
}

const response = await apiClient(url);
const data = await response.json();

Pattern 3: Need both raw payload and object

Best approach: read once, derive everything from that result.

const response = await fetch(url);
const raw = await response.text();
const data = JSON.parse(raw);

return { raw, data };

This avoids multiple body reads entirely.

Common Edge Cases

  • Error handlers that call response.text() before throwing, while another layer later tries to inspect the same response.
  • Server-side logging tools that wrap fetch and consume bodies for observability.
  • Conditional parsing where one branch reads text and another reads JSON from the same response object.
  • Redirect or auth flows where a helper clones too late after a previous branch already consumed the stream.
  • Retry utilities that cache and replay the same response object instead of issuing a new request.
  • Custom abstractions that return a raw Response and parsed data together, but parse internally without documenting that the raw response body is no longer usable.

Another subtle case is checking a body in development only:

if (process.env.NODE_ENV === 'development') {
  console.log(await response.text());
}

return response;

This can make the bug appear only locally. The fix is still the same: clone first, or avoid reading the body there.

FAQ

Why does response.clone() fail even though I call it right before parsing?

Because something else likely consumed the body earlier. The failing line is not always the original cause. Check logging, wrappers, middleware, and helper functions that may have already called text() or json().

Can I reset a consumed response body?

No. A Response body stream is not rewindable. Once consumed, you must use data you already stored, or make the request again.

Should I always clone every response to be safe?

No. Cloning every response adds overhead and usually hides poor ownership boundaries. The better design is to let one layer own the response body and return parsed data or a newly constructed response when needed.

The reliable fix for this GitHub issue is to audit the request path, identify the first body consumer, and either remove that extra read or move clone() before it. Once you enforce a single-reader rule for each Response, this error disappears consistently.

For the reproduction linked in the issue, focus on where the response body is touched indirectly. In most cases, the visible failing clone() call is just the symptom; the actual bug is an earlier body consumption in a helper, middleware layer, or debug path.

Leave a Reply

Your email address will not be published. Required fields are marked *