withastro/astro · error · AstroError

RewriteWithBodyUsed

RewriteWithBodyUsed

Error message

`Astro.rewrite()` cannot be used if the request body has already been read. If you need to read the body, first clone the request.

What it means

Astro.rewrite() builds the new internal Request by transferring the original request's method, headers, and unread body stream into it. Once the body has been consumed (e.g. by request.json()/text()/formData()), the stream is used up and cannot be forwarded, so copyRequest throws RewriteWithBodyUsed instead of silently producing a rewrite with an empty body. The message tells you the fix: read from a clone, since Request.clone() gives an independent copy of the stream.

Solutions

  1. Clone the request before reading the body: const cloned = context.request.clone(); read from cloned, then rewrite with the original request.
  2. If you must read and forward the same payload, construct the rewrite Request yourself with a re-serialized body (new Request(url, { method, headers, body: JSON.stringify(data) })).
  3. If the body is not actually needed after inspection, remove the body-reading code so the stream stays untouched.

Example fix

// before
export const onRequest = async (context, next) => {
  const user = await context.request.json(); // consumes the body
  if (!user.ok) return context.rewrite('/login');
  return next();
};

// after
export const onRequest = async (context, next) => {
  const cloned = context.request.clone();
  const user = await cloned.json(); // original stream still unread
  if (!user.ok) return context.rewrite('/login');
  return next();
};
Defensive patterns

Strategy: validation

Validate before calling

export function canRewrite(request: Request): boolean {
  return !request.bodyUsed;
}

// in middleware:
if (!canRewrite(context.request)) {
  // body already consumed; do not call context.rewrite()
  return Response.json({ error: 'request already consumed' }, { status: 400 });
}

Try / catch

import { AstroError } from 'astro/errors';

try {
  return context.rewrite(target);
} catch (err) {
  if (err instanceof AstroError && err.code === 'RewriteWithBodyUsed') {
    // recover by forwarding the payload manually
    return context.rewrite(new Request(target, {
      method: context.request.method,
      headers: context.request.headers,
      body: capturedBody, // the data you read earlier
    }));
  }
  throw err;
}

Prevention

When it happens

Trigger: In a middleware, calling any body-reading API on context.request (await request.json(), .text(), .formData(), .arrayBuffer(), or reading request.body directly) and afterwards returning context.rewrite(new Request('/other-route')) or otherwise triggering copyRequest with that consumed request. Also hit when a helper inspects the POST payload for logging/auth and then the middleware chain rewrites.

Common situations: Auth middleware that parses a JSON login payload before rewriting to a route; request-logging middleware that dumps bodies; upgrading a redirect-based middleware to Astro.rewrite() in Astro 4.x/5.x; API-gateway style front controllers.

Related errors


AI-assisted analysis of withastro/astro@157c500c38 (2026-08-27). Data as JSON: /api/errors/71dfe809a60297be. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/core/routing/rewrite.ts:154

/**
 * Utility function that creates a new `Request` with a new URL from an old `Request`.
 *
 * @param newUrl The new `URL`
 * @param oldRequest The old `Request`
 * @param isPrerendered It needs to be the flag of the previous routeData, before the rewrite
 * @param logger
 * @param routePattern
 */
export function copyRequest(
	newUrl: URL,
	oldRequest: Request,
	isPrerendered: boolean,
	logger: AstroLogger,
	routePattern: string,
): Request {
	const canHaveBody = oldRequest.method !== 'GET' && oldRequest.method !== 'HEAD';
	if (canHaveBody && oldRequest.bodyUsed) {
		throw new AstroError(AstroErrorData.RewriteWithBodyUsed);
	}
	return createRequest({
		url: newUrl,
		method: oldRequest.method,
		body: canHaveBody ? oldRequest.body : undefined,
		isPrerendered,
		logger,
		headers: isPrerendered ? {} : oldRequest.headers,
		routePattern,
		init: {
			referrer: oldRequest.referrer,
			referrerPolicy: oldRequest.referrerPolicy,
			mode: oldRequest.mode,
			credentials: oldRequest.credentials,
			cache: oldRequest.cache,
			redirect: oldRequest.redirect,
			integrity: oldRequest.integrity,
			signal: oldRequest.signal,

View on GitHub (pinned to 157c500c38)