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
localsfor reuse - Decide rewrite path before consuming body