Fix: Cannot Use Astro.rewrite After Request Body Has Been Read in Astro

Error message:
Cannot use Astro.rewrite after the request body has been read.
Middleware & Endpoints 2025-01-25

What Causes This Error?

This error occurs when you try to use Astro.rewrite() after already reading the request body. Once the body is consumed (via request.json(), request.text(), etc.), it cannot be re-read for the rewritten request.

The Problem

// src/middleware.ts
export const onRequest = defineMiddleware(async (context, next) => {
  // ❌ Reading body first
  const body = await context.request.json();

  if (body.redirect) {
    // ❌ Can't rewrite - body already consumed
    return context.rewrite('/other-page');
  }

  return next();
});

The Fix

Check Before Reading Body

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware(async (context, next) => {
  // ✅ Check headers/URL first (doesn't consume body)
  if (context.url.searchParams.get('redirect')) {
    return context.rewrite('/other-page');
  }

  // Read body only when needed
  if (context.request.method === 'POST') {
    const body = await context.request.json();
    context.locals.body = body;
  }

  return next();
});

Clone Request Before Reading

export const onRequest = defineMiddleware(async (context, next) => {
  // ✅ Clone the request first
  const clonedRequest = context.request.clone();
  const body = await clonedRequest.json();

  if (body.redirect) {
    // Original request body is still available
    return context.rewrite('/other-page');
  }

  return next();
});

Common Scenarios

Conditional Routing Based on Body

// ❌ Problem pattern
export const onRequest = defineMiddleware(async (context, next) => {
  const body = await context.request.json();
  if (body.type === 'special') {
    return context.rewrite('/special-handler');
  }
  return next();
});

// ✅ Clone first
export const onRequest = defineMiddleware(async (context, next) => {
  if (context.request.method === 'POST') {
    const clone = context.request.clone();
    const body = await clone.json();

    if (body.type === 'special') {
      return context.rewrite('/special-handler');
    }
  }
  return next();
});

Using Headers Instead

// ✅ Check headers (doesn't consume body)
export const onRequest = defineMiddleware((context, next) => {
  const contentType = context.request.headers.get('content-type');
  const customHeader = context.request.headers.get('x-route-to');

  if (customHeader === 'special') {
    return context.rewrite('/special-handler');
  }

  return next();
});

Using URL Parameters

// ✅ Check URL params (doesn't consume body)
export const onRequest = defineMiddleware((context, next) => {
  const routeTo = context.url.searchParams.get('route');

  if (routeTo) {
    return context.rewrite(`/${routeTo}`);
  }

  return next();
});

Store Body for Later Use

export const onRequest = defineMiddleware(async (context, next) => {
  // Clone and parse once
  if (context.request.method === 'POST') {
    const clone = context.request.clone();
    try {
      context.locals.parsedBody = await clone.json();
    } catch {
      context.locals.parsedBody = null;
    }
  }

  // Now can check body and still rewrite
  if (context.locals.parsedBody?.redirect) {
    return context.rewrite(context.locals.parsedBody.redirect);
  }

  return next();
});

In Page/Endpoint

---
// ✅ Check conditions before reading body
if (Astro.url.searchParams.get('redirect')) {
  return Astro.rewrite('/target');
}

// Now safe to read body
const body = await Astro.request.json();
---

Using redirect() Instead

// If you don't need to preserve the request
export const onRequest = defineMiddleware(async (context, next) => {
  const body = await context.request.json();

  if (body.redirect) {
    // ✅ redirect() doesn't need original body
    return context.redirect('/other-page');
  }

  return next();
});

Difference: rewrite vs redirect

rewrite(): Internal routing, same request continues
- Needs access to original request/body
- URL in browser doesn't change
- POST body forwarded to new route

redirect(): HTTP redirect response
- New request from browser
- URL in browser changes
- POST body is lost

Quick Checklist

  • Clone request before reading body if rewrite is possible
  • Check headers/URL params before body
  • Use redirect() if body forwarding isn’t needed
  • Store parsed body in locals for reuse
  • Decide rewrite path before consuming body