Summary
In an App Router route handler (route.ts), passing the incoming request to any fetch-based handler library that re-wraps it via new Request(request, init) crashes on Cloudflare Workers. This is a very common pattern — oRPC's RPCHandler (its BodyLimitPlugin does new Request(request, { body, duplex })), Hono's adapter, etc.
On workerd the failure surfaces as:
TypeError: Invalid URL: [object Request]
logged by vinext as [vinext] Route handler error: ... and returned to the client as an empty 500.
Root cause
createTrackedAppRouteRequest() (packages/vinext/src/server/app-route-handler-runtime.ts) hands the route handler a Proxy wrapping a NextRequest, to track dynamic request access.
A Proxy is not a genuine Request. When a handler library does new Request(theProxy, init):
- On workerd,
new Request() cannot read the native internal slots of a Proxy, so it coerces the argument to a string URL. String(proxy) is "[object Request]" (via Symbol.toStringTag), so the re-wrapped request's url becomes "[object Request]". The library's subsequent new URL(request.url) then throws TypeError: Invalid URL: [object Request].
- On Node (undici) the same call throws
TypeError: Cannot read private member #state from an object whose class did not declare it.
.url on the proxy itself is fine (the getter returns the correct string); the breakage only happens when the request is re-wrapped into a new Request, which fetch handler libraries routinely do.
Minimal reproduction (workerd)
// worker.mjs — run with `wrangler dev`
import { createTrackedAppRouteRequest } from "vinext/dist/server/app-route-handler-runtime.js";
export default {
async fetch(request) {
const tracked = createTrackedAppRouteRequest(request, {}).request;
// Exactly what oRPC's BodyLimitPlugin / Hono / etc. do internally:
const rewrapped = new Request(tracked, { body: tracked.body, duplex: "half" });
return Response.json({ url: rewrapped.url }); // -> TypeError: Invalid URL: [object Request]
},
};
POST any body to it → 500, worker log shows TypeError: Invalid URL: [object Request].
The same new Request(tracked, init) throws Cannot read private member #state under Node's undici, so it also reproduces in a plain Node unit test.
Impact
Any App Router route handler that delegates to oRPC / Hono / other fetch-router libraries returns 500 for every request on Workers. In our app this silently broke all mutations (record, like, bookmark, …) after migrating to vinext, since every one goes through a single oRPC /api/rpc route handler.
Suggested fix
Return a real Request from createTrackedAppRouteRequest() in the common "auto" mode — install dynamic-access tracking via own accessor properties on the actual NextRequest instead of a Proxy, so the object stays a genuine, re-wrappable Request. (Native-slot reads during new Request(req, init) bypass JS getters, so tracking is unaffected.) The value-substituting static-generation modes (force-static, error) can keep using the Proxy since those requests are not meant to be re-wrapped.
I've opened a PR implementing this with regression tests.
Environment
- vinext
1.0.0-beta.0, @vinext/cloudflare 1.0.0-beta.0
- wrangler
4.105.0, compatibility_flags: ["nodejs_compat"]
- Reproduced on workerd (wrangler dev / production) and Node v26 (undici)
Summary
In an App Router route handler (
route.ts), passing the incomingrequestto any fetch-based handler library that re-wraps it vianew Request(request, init)crashes on Cloudflare Workers. This is a very common pattern — oRPC'sRPCHandler(itsBodyLimitPlugindoesnew Request(request, { body, duplex })), Hono's adapter, etc.On workerd the failure surfaces as:
logged by vinext as
[vinext] Route handler error: ...and returned to the client as an empty500.Root cause
createTrackedAppRouteRequest()(packages/vinext/src/server/app-route-handler-runtime.ts) hands the route handler aProxywrapping aNextRequest, to track dynamic request access.A
Proxyis not a genuineRequest. When a handler library doesnew Request(theProxy, init):new Request()cannot read the native internal slots of a Proxy, so it coerces the argument to a string URL.String(proxy)is"[object Request]"(viaSymbol.toStringTag), so the re-wrapped request'surlbecomes"[object Request]". The library's subsequentnew URL(request.url)then throwsTypeError: Invalid URL: [object Request].TypeError: Cannot read private member #state from an object whose class did not declare it..urlon the proxy itself is fine (the getter returns the correct string); the breakage only happens when the request is re-wrapped into a newRequest, which fetch handler libraries routinely do.Minimal reproduction (workerd)
POSTany body to it →500, worker log showsTypeError: Invalid URL: [object Request].The same
new Request(tracked, init)throwsCannot read private member #stateunder Node's undici, so it also reproduces in a plain Node unit test.Impact
Any App Router route handler that delegates to oRPC / Hono / other fetch-router libraries returns
500for every request on Workers. In our app this silently broke all mutations (record, like, bookmark, …) after migrating to vinext, since every one goes through a single oRPC/api/rpcroute handler.Suggested fix
Return a real
RequestfromcreateTrackedAppRouteRequest()in the common"auto"mode — install dynamic-access tracking via own accessor properties on the actualNextRequestinstead of aProxy, so the object stays a genuine, re-wrappableRequest. (Native-slot reads duringnew Request(req, init)bypass JS getters, so tracking is unaffected.) The value-substituting static-generation modes (force-static,error) can keep using the Proxy since those requests are not meant to be re-wrapped.I've opened a PR implementing this with regression tests.
Environment
1.0.0-beta.0,@vinext/cloudflare1.0.0-beta.04.105.0,compatibility_flags: ["nodejs_compat"]