Skip to content

feat: make vinext fully compatible with clerk middleware#126

Open
nbardy wants to merge 1 commit intocloudflare:mainfrom
nbardy:fix-clerk-middleware
Open

feat: make vinext fully compatible with clerk middleware#126
nbardy wants to merge 1 commit intocloudflare:mainfrom
nbardy:fix-clerk-middleware

Conversation

@nbardy
Copy link

@nbardy nbardy commented Feb 26, 2026

Description

This PR makes vinext fully compatible with @clerk/nextjs by implementing the NextFetchEvent.waitUntil background task API across the middleware and Worker layers.

Dependency: This PR pairs with clerk/javascript#7954, which fixes ESM compatibility in @clerk/nextjs itself (replacing require() calls that crash in Vite/Workers). That PR makes Clerk's package loadable in ESM runtimes. This PR provides the runtime API surface Clerk needs once it loads. Both are required for end-to-end Clerk support on Cloudflare Workers via vinext.

What changed

  • Pass NextFetchEvent to middleware: Construct the event with a waitUntil array and pass it as the second argument to middlewareFn. This is the standard Next.js middleware signature — Clerk uses event.waitUntil() to schedule session sync and telemetry as background tasks.
  • Bubble up promises: Collect waitUntilPromises from the middleware result and attach them to the Response object via a non-enumerable __vinextWaitUntil property so they survive the routing pipeline.
  • Cloudflare Worker integration (App Router & Pages Router): Add ctx: ExecutionContext to the generated Worker fetch signature, collect promises from both runMiddleware and __vinextWaitUntil, and delegate to Cloudflare's native ctx.waitUntil(). This ensures background work survives the serverless response lifecycle.
  • Update checks: vinext check now reports @clerk/nextjs as supported.
  • Tests: Add app-router.test.ts coverage verifying event.waitUntil is 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/nextjs loads cleanly in Vite/Workers. What vinext needs to do is provide the correct API surface — specifically, a real NextFetchEvent with a functioning waitUntil(). 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:

  1. AsyncLocalStorage (ALS): Risky in streaming responses. The primary execution thread finishes when the ReadableStream is returned; the ALS scope can tear down before background tasks are safely handed off to Cloudflare.
  2. Structured return types ({ response, tasks }): Changes the core router return type from Response to a tuple — massive blast radius across every caching, proxy, and error boundary layer.
  3. Global WeakMap keyed to Request: Fragile because the framework frequently clones requests when manipulating headers and URLs, which drops the reference.

Attaching promises directly to the Response as 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

API Status
NextFetchEvent + event.waitUntil() ✅ This PR
NextResponse.next({ request: { headers } }) / x-middleware-request-* ✅ Already implemented
req.cookies / res.cookies.set() ✅ Already implemented
req.nextUrl / NextURL ✅ Already implemented
NextResponse.redirect() early return ✅ Already implemented
ESM-safe @clerk/nextjs package clerk/javascript#7954

@nbardy nbardy force-pushed the fix-clerk-middleware branch 4 times, most recently from 04daac0 to 7f5034d Compare February 26, 2026 17:05
@nbardy nbardy changed the title fix: pass NextFetchEvent to middleware and support Clerk waitUntil feat: make vinext fully compatible with clerk middleware Feb 27, 2026
…es to Cloudflare Worker ctx

- Instantiates `NextFetchEvent` with `waitUntil` shim and passes to `middlewareFn`
- Extracts `_waitUntilPromises` from the middleware result and attaches them to the `Response` object via `__vinextWaitUntil`
- Updates the generated Cloudflare Worker `fetch` signature to include `ctx: ExecutionContext`
- Iterates over `__vinextWaitUntil` on the final response and delegates to Cloudflare's native `ctx.waitUntil(promise)`
- Enables background tasks (like telemetry and session sync in `@clerk/nextjs`) to survive the worker response lifecycle
- Updates `vinext check` to explicitly report `@clerk/nextjs` as supported
- Adds `app-router.test.ts` assertion to verify `waitUntil` injection
@nbardy nbardy force-pushed the fix-clerk-middleware branch from 7f5034d to 0cbbe57 Compare March 2, 2026 07:55
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@126

commit: 47ffca3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant