mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #126] [MERGED] fix: wire ctx.waitUntil for middleware fetch event background tasks #332
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#332
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/126
Author: @nbardy
Created: 2/26/2026
Status: ✅ Merged
Merged: 3/28/2026
Merged by: @james-elicx
Base:
main← Head:fix-clerk-middleware📝 Commits (10+)
0cbbe57fix: pass NextFetchEvent to middleware and bubble up waitUntil promises to Cloudflare Worker ctxa8ee44dMerge remote-tracking branch 'origin/main' into fix-clerk-middlewaref07f226fix: address review feedback for waitUntil/NextFetchEvent support0f4e82dfix: move waitUntil settlement before early returns in prod-serverbf04872Merge main into fix-clerk-middleware: resolve conflicts, fix waitUntil plumbing3a9975efix(middleware): remove double drainWaitUntil, include waitUntilPromises on error path6fd374bfix: remove dead __vinextWaitUntil read code with no corresponding setterced210efix: remove no-op .catch on Promise.allSettled, strengthen waitUntil redirect testa98855ftest: add waitUntil continue:true path coveragea8517dbfix: settle waitUntil promises in Pages Router dev server (index.ts)📊 Changes
11 files changed (+125 additions, -17 deletions)
View changed files
📝
packages/vinext/src/deploy.ts(+7 -0)📝
packages/vinext/src/entries/app-rsc-entry.ts(+8 -4)📝
packages/vinext/src/entries/pages-server-entry.ts(+2 -0)📝
packages/vinext/src/index.ts(+6 -0)📝
packages/vinext/src/server/middleware.ts(+8 -7)📝
packages/vinext/src/server/prod-server.ts(+7 -0)📝
packages/vinext/src/shims/server.ts(+4 -0)📝
tests/__snapshots__/entry-templates.test.ts.snap(+10 -4)📝
tests/app-router.test.ts(+7 -0)📝
tests/fixtures/app-basic/middleware.ts(+12 -2)📝
tests/shims.test.ts(+54 -0)📄 Description
Description
This PR makes
vinextfully compatible with@clerk/nextjsby implementing theNextFetchEvent.waitUntilbackground task API across the middleware and Worker layers.What changed
NextFetchEventto middleware: Construct the event with awaitUntilarray and pass it as the second argument tomiddlewareFn. This is the standard Next.js middleware signature — Clerk usesevent.waitUntil()to schedule session sync and telemetry as background tasks.waitUntilPromisesfrom the middleware result and attach them to theResponseobject via a non-enumerable__vinextWaitUntilproperty so they survive the routing pipeline.ctx: ExecutionContextto the generated Workerfetchsignature, collect promises from bothrunMiddlewareand__vinextWaitUntil, and delegate to Cloudflare's nativectx.waitUntil(). This ensures background work survives the serverless response lifecycle.vinext checknow reports@clerk/nextjsas supported.app-router.test.tscoverage verifyingevent.waitUntilis injected correctly.Architectural note: from workaround to native API support
The original framing of this work was "making Clerk not crash." The paired Clerk PR changes that picture: once Clerk's ESM imports are fixed,
@clerk/nextjsloads cleanly in Vite/Workers. What vinext needs to do is provide the correct API surface — specifically, a realNextFetchEventwith a functioningwaitUntil(). This is not a shim to paper over bugs; it is the standard Next.js middleware contract that Clerk (and any other middleware library) legitimately depends on.Architectural Decision: The 'Bubble Up' Pattern
Background promises need to travel from the middleware layer up to the Cloudflare Worker's
ctx.waitUntil(). This PR uses a hidden property (Object.defineProperty(response, '__vinextWaitUntil', ...)).Alternatives considered:
ReadableStreamis returned; the ALS scope can tear down before background tasks are safely handed off to Cloudflare.{ response, tasks }): Changes the core router return type fromResponseto a tuple — massive blast radius across every caching, proxy, and error boundary layer.WeakMapkeyed toRequest: Fragile because the framework frequently clones requests when manipulating headers and URLs, which drops the reference.Attaching promises directly to the
Responseas a non-enumerable property lets them travel safely up the execution chain, survive streaming, and require zero signature changes across the broader codebase.Clerk API contact points
NextFetchEvent+event.waitUntil()NextResponse.next({ request: { headers } })/x-middleware-request-*req.cookies/res.cookies.set()req.nextUrl/NextURLNextResponse.redirect()early return@clerk/nextjspackage🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.