[PR #919] fix(middleware): align cookies and external rewrites #950

Closed
opened 2026-05-06 13:11:11 +02:00 by BreizhHardware · 0 comments

Original Pull Request: https://github.com/cloudflare/vinext/pull/919

State: closed
Merged: Yes


What this changes

Middleware cookie writes made through NextResponse.cookies.set() are now mirrored into x-middleware-set-cookie and merged back into the request cookie store, so cookies() can observe middleware-set values in the same render.

External middleware rewrites now preserve the full absolute destination and proxy the request instead of collapsing the URL to pathname + search. This covers App Router dev/prod, Pages Router generated entry handling, Pages production, deploy output, and the dev external proxy path.

This also moves middleware matcher/execution/rewrite behavior out of generated entry strings and into normal runtime modules. Codegen now describes app shape and wires the user middleware module into shared helpers; it no longer owns matcher tokenization, middleware dispatch, waitUntil handling, internal-header filtering, or external middleware rewrite proxy behavior.

Why

Next.js mirrors middleware cookie writes into x-middleware-set-cookie and later merges that internal header into the request store before rendering. Without that internal header, auth and session flows that set a cookie in middleware and read it in the same render observe undefined.

Next.js also treats cross-origin middleware rewrites as external proxy destinations. vinext was stripping the origin from middleware rewrite URLs, which made NextResponse.rewrite("https://api.example.com/...") route locally and often return a local 404.

The codegen extraction is to prevent the exact class of drift this PR exposed: generated Pages/App entries had duplicated middleware filtering and rewrite logic that could diverge from the runtime path.

Next.js references

Approach

  • Add middleware-aware NextResponse cookie handling that syncs Set-Cookie into x-middleware-set-cookie.
  • Merge x-middleware-set-cookie into the request cookie context while preserving existing request-header override behavior.
  • Preserve external middleware rewrite URLs through middleware execution and route them through proxyExternalRequest.
  • Apply middleware request-header overrides before proxying external middleware rewrites.
  • Strip internal x-middleware-* headers, including x-middleware-set-cookie, from client responses and external upstream requests.
  • Preserve upstream proxy response status for external middleware rewrites, including when middleware uses a non-200 rewrite status.
  • Replace duplicated generated middleware matcher/execution code with shared server/middleware-matcher.ts, server/middleware-runtime.ts, and server/app-middleware.ts helpers.
  • Keep generated App/Pages entries responsible for route/app-shape data and thin helper wiring only.

Validation

  • vp test run tests/shims.test.ts -t "resolveMiddlewareHandler|matchPattern|matchesMiddleware|middleware bypass|runMiddleware"
  • vp test run tests/entry-templates.test.ts
  • rm -rf tests/fixtures/pages-basic/dist && vp test run tests/pages-router.test.ts -t "produces SSR server entry via vite build --ssr|runMiddleware preserves internal middleware cookie headers on rewrites|runMiddleware handles rewrite|runMiddleware preserves external middleware rewrite destinations|runMiddleware strips internal cookie headers from custom responses|runMiddleware handles redirects|runMiddleware passes waitUntil promises"
  • vp test run tests/app-router.test.ts -t "external middleware rewrite|middleware rewrite|header override|x-vinext-mw-ctx|delegates middleware rewrite handling"
  • vp check
  • vp run knip --no-progress
  • vp run vinext#build
  • Commit hook reran tests/entry-templates.test.ts, vp check --fix, knip --no-progress, and package rebuild.

Risks / follow-ups

External middleware rewrites intentionally preserve the upstream proxy response status. That matches the Next.js external proxy path, where the middleware rewrite status is not carried into proxyRequest.

**Original Pull Request:** https://github.com/cloudflare/vinext/pull/919 **State:** closed **Merged:** Yes --- ## What this changes Middleware cookie writes made through `NextResponse.cookies.set()` are now mirrored into `x-middleware-set-cookie` and merged back into the request cookie store, so `cookies()` can observe middleware-set values in the same render. External middleware rewrites now preserve the full absolute destination and proxy the request instead of collapsing the URL to `pathname + search`. This covers App Router dev/prod, Pages Router generated entry handling, Pages production, deploy output, and the dev external proxy path. This also moves middleware matcher/execution/rewrite behavior out of generated entry strings and into normal runtime modules. Codegen now describes app shape and wires the user middleware module into shared helpers; it no longer owns matcher tokenization, middleware dispatch, waitUntil handling, internal-header filtering, or external middleware rewrite proxy behavior. ## Why Next.js mirrors middleware cookie writes into `x-middleware-set-cookie` and later merges that internal header into the request store before rendering. Without that internal header, auth and session flows that set a cookie in middleware and read it in the same render observe `undefined`. Next.js also treats cross-origin middleware rewrites as external proxy destinations. vinext was stripping the origin from middleware rewrite URLs, which made `NextResponse.rewrite("https://api.example.com/...")` route locally and often return a local 404. The codegen extraction is to prevent the exact class of drift this PR exposed: generated Pages/App entries had duplicated middleware filtering and rewrite logic that could diverge from the runtime path. ## Next.js references - [`NextResponse` mirrors cookie mutations into `x-middleware-set-cookie`](https://github.com/vercel/next.js/blob/16e5f9e685188751f1f4fb085f6e9e729f409950/packages/next/src/server/web/spec-extension/response.ts#L49-L66) - [`mergeMiddlewareCookies` merges `x-middleware-set-cookie` into request cookies before `cookies()` reads them](https://github.com/vercel/next.js/blob/16e5f9e685188751f1f4fb085f6e9e729f409950/packages/next/src/server/async-storage/request-store.ts#L79-L106) - [`resolve-routes` applies middleware request-header overrides and keeps `x-middleware-set-cookie` internal](https://github.com/vercel/next.js/blob/16e5f9e685188751f1f4fb085f6e9e729f409950/packages/next/src/server/lib/router-utils/resolve-routes.ts#L640-L707) - [`resolve-routes` treats protocol rewrite destinations as finished external rewrites](https://github.com/vercel/next.js/blob/16e5f9e685188751f1f4fb085f6e9e729f409950/packages/next/src/server/lib/router-utils/resolve-routes.ts#L710-L723) - [`router-server` proxies finished protocol destinations through `proxyRequest`](https://github.com/vercel/next.js/blob/16e5f9e685188751f1f4fb085f6e9e729f409950/packages/next/src/server/lib/router-server.ts#L499-L507) - [Next.js e2e coverage for external middleware rewrites with request body and middleware headers](https://github.com/vercel/next.js/blob/16e5f9e685188751f1f4fb085f6e9e729f409950/test/e2e/middleware-rewrites/test/index.test.ts#L64-L101) ## Approach - Add middleware-aware `NextResponse` cookie handling that syncs `Set-Cookie` into `x-middleware-set-cookie`. - Merge `x-middleware-set-cookie` into the request cookie context while preserving existing request-header override behavior. - Preserve external middleware rewrite URLs through middleware execution and route them through `proxyExternalRequest`. - Apply middleware request-header overrides before proxying external middleware rewrites. - Strip internal `x-middleware-*` headers, including `x-middleware-set-cookie`, from client responses and external upstream requests. - Preserve upstream proxy response status for external middleware rewrites, including when middleware uses a non-200 rewrite status. - Replace duplicated generated middleware matcher/execution code with shared `server/middleware-matcher.ts`, `server/middleware-runtime.ts`, and `server/app-middleware.ts` helpers. - Keep generated App/Pages entries responsible for route/app-shape data and thin helper wiring only. ## Validation - `vp test run tests/shims.test.ts -t "resolveMiddlewareHandler|matchPattern|matchesMiddleware|middleware bypass|runMiddleware"` - `vp test run tests/entry-templates.test.ts` - `rm -rf tests/fixtures/pages-basic/dist && vp test run tests/pages-router.test.ts -t "produces SSR server entry via vite build --ssr|runMiddleware preserves internal middleware cookie headers on rewrites|runMiddleware handles rewrite|runMiddleware preserves external middleware rewrite destinations|runMiddleware strips internal cookie headers from custom responses|runMiddleware handles redirects|runMiddleware passes waitUntil promises"` - `vp test run tests/app-router.test.ts -t "external middleware rewrite|middleware rewrite|header override|x-vinext-mw-ctx|delegates middleware rewrite handling"` - `vp check` - `vp run knip --no-progress` - `vp run vinext#build` - Commit hook reran `tests/entry-templates.test.ts`, `vp check --fix`, `knip --no-progress`, and package rebuild. ## Risks / follow-ups External middleware rewrites intentionally preserve the upstream proxy response status. That matches the Next.js external proxy path, where the middleware rewrite status is not carried into `proxyRequest`.
BreizhHardware 2026-05-06 13:11:11 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#950
No description provided.