How to handle “TypeError: Request with a GET or HEAD method cannot have a body" in Cloudflare Workers
While working on a Cloudflare Workers application, I tried sending a link in LINE app to see what kind of link preview it would generate. To my surprise, it didn’t show any link preview at all. Even though other platforms like iMessage were working just fine.
I investigated the logs and found a mysterious error.
LINE crawled my link with a bot with the user agent facebookexternalhit/1.1;line-poker/1.0
For some reason my application responded with a 500 status code and the following error:
{
"message": [
"TypeError: Request with a GET or HEAD method cannot have a body."
],
"level": "error",
"timestamp": 1749796895631
},
{
"message": ["Error: Unexpected Server Error"],
"level": "error",
"timestamp": 1749796895631
}
These don’t correspond to any of my code. After doing a series of tests, I was able to generate a stack trace and map the error to the stripIndexFunction inside of React Router v7, which looks roughly like this:
function stripIndexParam(request: Request) {
let init: RequestInit = {
method: request.method,
body: request.body,
headers: request.headers,
signal: request.signal,
};
return new Request(url.href, init); // error happening here
}
I also found a GitHub Issue with some official advice.
For a little background here: The fetch() and Request API spec say that GET requests cannot have a body. But, the HTTP spec actually does allow GET requests to have a body, and people do use this…The Workers Runtime, as a compromise, permits bodied GET requests to proxy through, but does not allow a bodied GET to be constructed directly from JavaScript…
If we had full control over this code, the solution would probably be to change how the new Request is created inside of stripIndexParam , but we don’t have control over that and it’s not a big enough issue to fork React Router over either…
As a compromise, I decided that I don’t have any need for body on GET requests at all in my application, so I added an extra wrapper to strip the body before handing off the request to the React Router request handler. Originally, I was passing export default hono . My new wrapper now looks like this:
export default {
fetch(req, env, ctx) {
const isGetOrHead = req.method === 'GET' || req.method === 'HEAD';
const newReq = new Request(req, {
...(isGetOrHead ? { body: null } : {}),
});
return hono.fetch(newReq, env, ctx);
},
} satisfies ExportedHandler;
And link previews are working again!