vinext 0.0.29 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/check.d.ts.map +1 -1
  2. package/dist/check.js +11 -7
  3. package/dist/check.js.map +1 -1
  4. package/dist/cloudflare/kv-cache-handler.d.ts.map +1 -1
  5. package/dist/cloudflare/kv-cache-handler.js +44 -30
  6. package/dist/cloudflare/kv-cache-handler.js.map +1 -1
  7. package/dist/entries/app-rsc-entry.d.ts +1 -1
  8. package/dist/entries/app-rsc-entry.d.ts.map +1 -1
  9. package/dist/entries/app-rsc-entry.js +208 -164
  10. package/dist/entries/app-rsc-entry.js.map +1 -1
  11. package/dist/entries/pages-server-entry.d.ts.map +1 -1
  12. package/dist/entries/pages-server-entry.js +151 -100
  13. package/dist/entries/pages-server-entry.js.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +10 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/routing/app-router.d.ts.map +1 -1
  18. package/dist/routing/app-router.js +43 -29
  19. package/dist/routing/app-router.js.map +1 -1
  20. package/dist/server/app-router-entry.js +1 -1
  21. package/dist/server/app-router-entry.js.map +1 -1
  22. package/dist/server/dev-server.d.ts +2 -1
  23. package/dist/server/dev-server.d.ts.map +1 -1
  24. package/dist/server/dev-server.js +163 -163
  25. package/dist/server/dev-server.js.map +1 -1
  26. package/dist/server/image-optimization.d.ts.map +1 -1
  27. package/dist/server/image-optimization.js +23 -11
  28. package/dist/server/image-optimization.js.map +1 -1
  29. package/dist/server/isr-cache.d.ts.map +1 -1
  30. package/dist/server/isr-cache.js +8 -3
  31. package/dist/server/isr-cache.js.map +1 -1
  32. package/dist/server/metadata-routes.d.ts.map +1 -1
  33. package/dist/server/metadata-routes.js +56 -18
  34. package/dist/server/metadata-routes.js.map +1 -1
  35. package/dist/server/middleware-codegen.d.ts.map +1 -1
  36. package/dist/server/middleware-codegen.js +37 -4
  37. package/dist/server/middleware-codegen.js.map +1 -1
  38. package/dist/server/middleware.d.ts.map +1 -1
  39. package/dist/server/middleware.js +50 -6
  40. package/dist/server/middleware.js.map +1 -1
  41. package/dist/server/pages-i18n.d.ts +50 -0
  42. package/dist/server/pages-i18n.d.ts.map +1 -0
  43. package/dist/server/pages-i18n.js +152 -0
  44. package/dist/server/pages-i18n.js.map +1 -0
  45. package/dist/shims/cache-runtime.d.ts +3 -0
  46. package/dist/shims/cache-runtime.d.ts.map +1 -1
  47. package/dist/shims/cache-runtime.js +22 -5
  48. package/dist/shims/cache-runtime.js.map +1 -1
  49. package/dist/shims/cache.d.ts +3 -0
  50. package/dist/shims/cache.d.ts.map +1 -1
  51. package/dist/shims/cache.js +21 -12
  52. package/dist/shims/cache.js.map +1 -1
  53. package/dist/shims/constants.d.ts.map +1 -1
  54. package/dist/shims/constants.js +0 -1
  55. package/dist/shims/constants.js.map +1 -1
  56. package/dist/shims/fetch-cache.d.ts +14 -0
  57. package/dist/shims/fetch-cache.d.ts.map +1 -1
  58. package/dist/shims/fetch-cache.js +102 -37
  59. package/dist/shims/fetch-cache.js.map +1 -1
  60. package/dist/shims/head-state.d.ts +4 -0
  61. package/dist/shims/head-state.d.ts.map +1 -1
  62. package/dist/shims/head-state.js +14 -11
  63. package/dist/shims/head-state.js.map +1 -1
  64. package/dist/shims/head.d.ts +4 -2
  65. package/dist/shims/head.d.ts.map +1 -1
  66. package/dist/shims/head.js +162 -52
  67. package/dist/shims/head.js.map +1 -1
  68. package/dist/shims/headers.d.ts +8 -1
  69. package/dist/shims/headers.d.ts.map +1 -1
  70. package/dist/shims/headers.js +21 -29
  71. package/dist/shims/headers.js.map +1 -1
  72. package/dist/shims/i18n-context.d.ts +27 -0
  73. package/dist/shims/i18n-context.d.ts.map +1 -0
  74. package/dist/shims/i18n-context.js +57 -0
  75. package/dist/shims/i18n-context.js.map +1 -0
  76. package/dist/shims/i18n-state.d.ts +20 -0
  77. package/dist/shims/i18n-state.d.ts.map +1 -0
  78. package/dist/shims/i18n-state.js +53 -0
  79. package/dist/shims/i18n-state.js.map +1 -0
  80. package/dist/shims/image.d.ts +2 -0
  81. package/dist/shims/image.d.ts.map +1 -1
  82. package/dist/shims/image.js +14 -6
  83. package/dist/shims/image.js.map +1 -1
  84. package/dist/shims/internal/utils.d.ts +1 -0
  85. package/dist/shims/internal/utils.d.ts.map +1 -1
  86. package/dist/shims/internal/utils.js.map +1 -1
  87. package/dist/shims/link.d.ts.map +1 -1
  88. package/dist/shims/link.js +27 -11
  89. package/dist/shims/link.js.map +1 -1
  90. package/dist/shims/metadata.d.ts +22 -22
  91. package/dist/shims/metadata.d.ts.map +1 -1
  92. package/dist/shims/metadata.js +34 -32
  93. package/dist/shims/metadata.js.map +1 -1
  94. package/dist/shims/navigation-state.d.ts +14 -0
  95. package/dist/shims/navigation-state.d.ts.map +1 -1
  96. package/dist/shims/navigation-state.js +33 -15
  97. package/dist/shims/navigation-state.js.map +1 -1
  98. package/dist/shims/navigation.d.ts.map +1 -1
  99. package/dist/shims/navigation.js +39 -22
  100. package/dist/shims/navigation.js.map +1 -1
  101. package/dist/shims/request-context.d.ts.map +1 -1
  102. package/dist/shims/request-context.js +9 -0
  103. package/dist/shims/request-context.js.map +1 -1
  104. package/dist/shims/request-state-types.d.ts +11 -0
  105. package/dist/shims/request-state-types.d.ts.map +1 -0
  106. package/dist/shims/request-state-types.js +2 -0
  107. package/dist/shims/request-state-types.js.map +1 -0
  108. package/dist/shims/router-state.d.ts +11 -0
  109. package/dist/shims/router-state.d.ts.map +1 -1
  110. package/dist/shims/router-state.js +10 -8
  111. package/dist/shims/router-state.js.map +1 -1
  112. package/dist/shims/router.d.ts +4 -0
  113. package/dist/shims/router.d.ts.map +1 -1
  114. package/dist/shims/router.js +117 -21
  115. package/dist/shims/router.js.map +1 -1
  116. package/dist/shims/server.d.ts +8 -1
  117. package/dist/shims/server.d.ts.map +1 -1
  118. package/dist/shims/server.js +52 -6
  119. package/dist/shims/server.js.map +1 -1
  120. package/dist/shims/unified-request-context.d.ts +66 -0
  121. package/dist/shims/unified-request-context.d.ts.map +1 -0
  122. package/dist/shims/unified-request-context.js +116 -0
  123. package/dist/shims/unified-request-context.js.map +1 -0
  124. package/dist/utils/domain-locale.d.ts +18 -0
  125. package/dist/utils/domain-locale.d.ts.map +1 -0
  126. package/dist/utils/domain-locale.js +64 -0
  127. package/dist/utils/domain-locale.js.map +1 -0
  128. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"app-rsc-entry.d.ts","sourceRoot":"","sources":["../../src/entries/app-rsc-entry.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,cAAc,EACf,MAAM,0BAA0B,CAAC;AA2BlC;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE;QACT,WAAW,EAAE,WAAW,EAAE,CAAC;QAC3B,UAAU,EAAE,WAAW,EAAE,CAAC;QAC1B,QAAQ,EAAE,WAAW,EAAE,CAAC;KACzB,CAAC;IACF,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,4GAA4G;IAC5G,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,mGAAmG;IACnG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,EAC9B,cAAc,CAAC,EAAE,iBAAiB,EAAE,EACpC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,EAC/B,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,OAAO,EACvB,MAAM,CAAC,EAAE,eAAe,EACxB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC,MAAM,CAsyFR"}
1
+ {"version":3,"file":"app-rsc-entry.d.ts","sourceRoot":"","sources":["../../src/entries/app-rsc-entry.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAkDtE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE;QACT,WAAW,EAAE,WAAW,EAAE,CAAC;QAC3B,UAAU,EAAE,WAAW,EAAE,CAAC;QAC1B,QAAQ,EAAE,WAAW,EAAE,CAAC;KACzB,CAAC;IACF,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,4GAA4G;IAC5G,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,4EAA4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,mGAAmG;IACnG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,EAC9B,cAAc,CAAC,EAAE,iBAAiB,EAAE,EACpC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,EAC/B,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,OAAO,EACvB,MAAM,CAAC,EAAE,eAAe,EACxB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC,MAAM,CA+zFR"}
@@ -10,7 +10,7 @@
10
10
  import fs from "node:fs";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { generateDevOriginCheckCode } from "../server/dev-origin-check.js";
13
- import { generateSafeRegExpCode, generateMiddlewareMatcherCode, generateNormalizePathCode, generateRouteMatchNormalizationCode, } from "../server/middleware-codegen.js";
13
+ import { generateMiddlewareMatcherCode, generateNormalizePathCode, generateSafeRegExpCode, generateRouteMatchNormalizationCode, } from "../server/middleware-codegen.js";
14
14
  import { isProxyFile } from "../server/middleware.js";
15
15
  // Pre-computed absolute paths for generated-code imports. The virtual RSC
16
16
  // entry can't use relative imports (it has no real file location), so we
@@ -19,6 +19,28 @@ const configMatchersPath = fileURLToPath(new URL("../config/config-matchers.js",
19
19
  const requestPipelinePath = fileURLToPath(new URL("../server/request-pipeline.js", import.meta.url)).replace(/\\/g, "/");
20
20
  const requestContextShimPath = fileURLToPath(new URL("../shims/request-context.js", import.meta.url)).replace(/\\/g, "/");
21
21
  const routeTriePath = fileURLToPath(new URL("../routing/route-trie.js", import.meta.url)).replace(/\\/g, "/");
22
+ // Canonical order of HTTP method handlers supported by route.ts modules.
23
+ const ROUTE_HANDLER_HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
24
+ // Runtime helpers injected into the generated RSC entry so OPTIONS/Allow handling
25
+ // logic stays alongside the route handler pipeline.
26
+ const routeHandlerHelperCode = String.raw `
27
+ // Duplicated from the build-time constant above via JSON.stringify.
28
+ const ROUTE_HANDLER_HTTP_METHODS = ${JSON.stringify(ROUTE_HANDLER_HTTP_METHODS)};
29
+
30
+ function collectRouteHandlerMethods(handler) {
31
+ const methods = ROUTE_HANDLER_HTTP_METHODS.filter((method) => typeof handler[method] === "function");
32
+ if (methods.includes("GET") && !methods.includes("HEAD")) {
33
+ methods.push("HEAD");
34
+ }
35
+ return methods;
36
+ }
37
+
38
+ function buildRouteHandlerAllowHeader(exportedMethods) {
39
+ const allow = new Set(exportedMethods);
40
+ allow.add("OPTIONS");
41
+ return Array.from(allow).sort().join(", ");
42
+ }
43
+ `;
22
44
  /**
23
45
  * Generate the virtual RSC entry module.
24
46
  *
@@ -166,14 +188,37 @@ ${slotEntries.join(",\n")}
166
188
  // For static metadata files, read the file content at code-generation time
167
189
  // and embed it as base64. This ensures static metadata files work on runtimes
168
190
  // without filesystem access (e.g., Cloudflare Workers).
191
+ //
192
+ // For metadata routes in dynamic segments (e.g., /blog/[slug]/opengraph-image),
193
+ // generate patternParts so the runtime can use matchPattern() instead of strict
194
+ // equality — the same matching used for intercept routes.
169
195
  const metaRouteEntries = effectiveMetaRoutes.map((mr) => {
196
+ // Convert dynamic segments in servedUrl to matchPattern format.
197
+ // Keep in sync with routing/app-router.ts patternParts generation.
198
+ // [param] → :param
199
+ // [...param] → :param+
200
+ // [[...param]] → :param*
201
+ const patternParts = mr.isDynamic && mr.servedUrl.includes("[")
202
+ ? JSON.stringify(mr.servedUrl
203
+ .split("/")
204
+ .filter(Boolean)
205
+ .map((seg) => {
206
+ if (seg.startsWith("[[...") && seg.endsWith("]]"))
207
+ return ":" + seg.slice(5, -2) + "*";
208
+ if (seg.startsWith("[...") && seg.endsWith("]"))
209
+ return ":" + seg.slice(4, -1) + "+";
210
+ if (seg.startsWith("[") && seg.endsWith("]"))
211
+ return ":" + seg.slice(1, -1);
212
+ return seg;
213
+ }))
214
+ : null;
170
215
  if (mr.isDynamic) {
171
216
  return ` {
172
217
  type: ${JSON.stringify(mr.type)},
173
218
  isDynamic: true,
174
219
  servedUrl: ${JSON.stringify(mr.servedUrl)},
175
220
  contentType: ${JSON.stringify(mr.contentType)},
176
- module: ${getImportVar(mr.filePath)},
221
+ module: ${getImportVar(mr.filePath)},${patternParts ? `\n patternParts: ${patternParts},` : ""}
177
222
  }`;
178
223
  }
179
224
  // Static: read file and embed as base64
@@ -203,7 +248,7 @@ import {
203
248
  import { AsyncLocalStorage } from "node:async_hooks";
204
249
  import { createElement, Suspense, Fragment } from "react";
205
250
  import { setNavigationContext as _setNavigationContextOrig, getNavigationContext as _getNavigationContext } from "next/navigation";
206
- import { setHeadersContext, headersContextFromRequest, getDraftModeCookieHeader, getAndClearPendingCookies, consumeDynamicUsage, markDynamicUsage, runWithHeadersContext, applyMiddlewareRequestHeaders, getHeadersContext, setHeadersAccessPhase } from "next/headers";
251
+ import { setHeadersContext, headersContextFromRequest, getDraftModeCookieHeader, getAndClearPendingCookies, consumeDynamicUsage, markDynamicUsage, applyMiddlewareRequestHeaders, getHeadersContext, setHeadersAccessPhase } from "next/headers";
207
252
  import { NextRequest, NextFetchEvent } from "next/server";
208
253
  import { ErrorBoundary, NotFoundBoundary } from "vinext/error-boundary";
209
254
  import { LayoutSegmentProvider } from "vinext/layout-segment-context";
@@ -213,18 +258,19 @@ ${instrumentationPath ? `import * as _instrumentation from ${JSON.stringify(inst
213
258
  ${effectiveMetaRoutes.length > 0 ? `import { sitemapToXml, robotsToText, manifestToJson } from ${JSON.stringify(fileURLToPath(new URL("../server/metadata-routes.js", import.meta.url)).replace(/\\/g, "/"))};` : ""}
214
259
  import { requestContextFromRequest, normalizeHost, matchRedirect, matchRewrite, matchHeaders, isExternalUrl, proxyExternalRequest, sanitizeDestination } from ${JSON.stringify(configMatchersPath)};
215
260
  import { validateCsrfOrigin, validateImageUrl, guardProtocolRelativeUrl, hasBasePath, stripBasePath, normalizeTrailingSlash, processMiddlewareHeaders } from ${JSON.stringify(requestPipelinePath)};
216
- import { _consumeRequestScopedCacheLife, _runWithCacheState, getCacheHandler } from "next/cache";
217
- import { runWithExecutionContext as _runWithExecutionContext, getRequestExecutionContext as _getRequestExecutionContext } from ${JSON.stringify(requestContextShimPath)};
218
- import { getCollectedFetchTags, runWithFetchCache } from "vinext/fetch-cache";
261
+ import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache";
262
+ import { getRequestExecutionContext as _getRequestExecutionContext } from ${JSON.stringify(requestContextShimPath)};
263
+ import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache";
219
264
  import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from ${JSON.stringify(routeTriePath)};
220
- import { runWithPrivateCache as _runWithPrivateCache } from "vinext/cache-runtime";
221
265
  // Import server-only state module to register ALS-backed accessors.
222
- import { runWithNavigationContext as _runWithNavigationContext } from "vinext/navigation-state";
266
+ import "vinext/navigation-state";
267
+ import { runWithRequestContext as _runWithUnifiedCtx, createRequestContext as _createUnifiedCtx } from "vinext/unified-request-context";
223
268
  import { reportRequestError as _reportRequestError } from "vinext/instrumentation";
224
269
  import { getSSRFontLinks as _getSSRFontLinks, getSSRFontStyles as _getSSRFontStylesGoogle, getSSRFontPreloads as _getSSRFontPreloadsGoogle } from "next/font/google";
225
270
  import { getSSRFontStyles as _getSSRFontStylesLocal, getSSRFontPreloads as _getSSRFontPreloadsLocal } from "next/font/local";
226
271
  function _getSSRFontStyles() { return [..._getSSRFontStylesGoogle(), ..._getSSRFontStylesLocal()]; }
227
272
  function _getSSRFontPreloads() { return [..._getSSRFontPreloadsGoogle(), ..._getSSRFontPreloadsLocal()]; }
273
+ ${routeHandlerHelperCode}
228
274
 
229
275
  // ALS used to suppress the expected "Invalid hook call" dev warning when
230
276
  // layout/page components are probed outside React's render cycle. Patching
@@ -667,7 +713,7 @@ async function renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, req
667
713
  // that run during stream consumption to see null headers/navigation context and throw,
668
714
  // resulting in missing provider context on the client (e.g. next-intl useTranslations fails
669
715
  // with "context from NextIntlClientProvider was not found").
670
- // Context is cleared naturally when the ALS scope from runWithHeadersContext unwinds.
716
+ // Context is cleared naturally when the ALS scope from runWithRequestContext unwinds.
671
717
  return new Response(rscStream, {
672
718
  status: statusCode,
673
719
  headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
@@ -817,7 +863,7 @@ async function renderErrorBoundaryPage(route, error, isRscRequest, request, matc
817
863
  // that run during stream consumption to see null headers/navigation context and throw,
818
864
  // resulting in missing provider context on the client (e.g. next-intl useTranslations fails
819
865
  // with "context from NextIntlClientProvider was not found").
820
- // Context is cleared naturally when the ALS scope from runWithHeadersContext unwinds.
866
+ // Context is cleared naturally when the ALS scope from runWithRequestContext unwinds.
821
867
  return new Response(rscStream, {
822
868
  status: 200,
823
869
  headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
@@ -1335,60 +1381,50 @@ export default async function handler(request, ctx) {
1335
1381
  await __ensureInstrumentation();
1336
1382
  `
1337
1383
  : ""}
1338
- // Wrap the entire request in nested AsyncLocalStorage.run() scopes to ensure
1339
- // per-request isolation for all state modules. Each runWith*() creates an
1340
- // ALS scope that propagates through all async continuations (including RSC
1341
- // streaming), preventing state leakage between concurrent requests on
1342
- // Cloudflare Workers and other concurrent runtimes.
1343
- //
1344
- // runWithExecutionContext stores the Workers ExecutionContext (ctx) in ALS so
1345
- // that KVCacheHandler._putInBackground can register background KV puts with
1346
- // ctx.waitUntil() without needing ctx passed at construction time.
1384
+ // Wrap the entire request in a single unified ALS scope for per-request
1385
+ // isolation. All state modules (headers, navigation, cache, fetch-cache,
1386
+ // execution-context) read from this store via isInsideUnifiedScope().
1347
1387
  const headersCtx = headersContextFromRequest(request);
1348
- const _run = () => runWithHeadersContext(headersCtx, () =>
1349
- _runWithNavigationContext(() =>
1350
- _runWithCacheState(() =>
1351
- _runWithPrivateCache(() =>
1352
- runWithFetchCache(async () => {
1353
- const __reqCtx = requestContextFromRequest(request);
1354
- // Per-request container for middleware state. Passed into
1355
- // _handleRequest which fills in .headers and .status;
1356
- // avoids module-level variables that race on Workers.
1357
- const _mwCtx = { headers: null, status: null };
1358
- const response = await _handleRequest(request, __reqCtx, _mwCtx);
1359
- // Apply custom headers from next.config.js to non-redirect responses.
1360
- // Skip redirects (3xx) because Response.redirect() creates immutable headers,
1361
- // and Next.js doesn't apply custom headers to redirects anyway.
1362
- if (response && response.headers && !(response.status >= 300 && response.status < 400)) {
1363
- if (__configHeaders.length) {
1364
- const url = new URL(request.url);
1365
- let pathname;
1366
- try { pathname = __normalizePath(__normalizePathnameForRouteMatch(url.pathname)); } catch { pathname = url.pathname; }
1367
- ${bp ? `if (pathname.startsWith(${JSON.stringify(bp)})) pathname = pathname.slice(${JSON.stringify(bp)}.length) || "/";` : ""}
1368
- const extraHeaders = matchHeaders(pathname, __configHeaders, __reqCtx);
1369
- for (const h of extraHeaders) {
1370
- // Use append() for headers where multiple values must coexist
1371
- // (Vary, Set-Cookie). Using set() on these would destroy
1372
- // existing values like "Vary: RSC, Accept" which are critical
1373
- // for correct CDN caching behavior.
1374
- const lk = h.key.toLowerCase();
1375
- if (lk === "vary" || lk === "set-cookie") {
1376
- response.headers.append(h.key, h.value);
1377
- } else if (!response.headers.has(lk)) {
1378
- // Middleware headers take precedence: skip config keys already
1379
- // set by middleware so middleware headers always win.
1380
- response.headers.set(h.key, h.value);
1381
- }
1382
- }
1383
- }
1384
- }
1385
- return response;
1386
- })
1387
- )
1388
- )
1389
- )
1390
- );
1391
- return ctx ? _runWithExecutionContext(ctx, _run) : _run();
1388
+ const __uCtx = _createUnifiedCtx({
1389
+ headersContext: headersCtx,
1390
+ executionContext: ctx ?? _getRequestExecutionContext() ?? null,
1391
+ });
1392
+ return _runWithUnifiedCtx(__uCtx, async () => {
1393
+ _ensureFetchPatch();
1394
+ const __reqCtx = requestContextFromRequest(request);
1395
+ // Per-request container for middleware state. Passed into
1396
+ // _handleRequest which fills in .headers and .status;
1397
+ // avoids module-level variables that race on Workers.
1398
+ const _mwCtx = { headers: null, status: null };
1399
+ const response = await _handleRequest(request, __reqCtx, _mwCtx);
1400
+ // Apply custom headers from next.config.js to non-redirect responses.
1401
+ // Skip redirects (3xx) because Response.redirect() creates immutable headers,
1402
+ // and Next.js doesn't apply custom headers to redirects anyway.
1403
+ if (response && response.headers && !(response.status >= 300 && response.status < 400)) {
1404
+ if (__configHeaders.length) {
1405
+ const url = new URL(request.url);
1406
+ let pathname;
1407
+ try { pathname = __normalizePath(__normalizePathnameForRouteMatch(url.pathname)); } catch { pathname = url.pathname; }
1408
+ ${bp ? `if (pathname.startsWith(${JSON.stringify(bp)})) pathname = pathname.slice(${JSON.stringify(bp)}.length) || "/";` : ""}
1409
+ const extraHeaders = matchHeaders(pathname, __configHeaders, __reqCtx);
1410
+ for (const h of extraHeaders) {
1411
+ // Use append() for headers where multiple values must coexist
1412
+ // (Vary, Set-Cookie). Using set() on these would destroy
1413
+ // existing values like "Vary: RSC, Accept" which are critical
1414
+ // for correct CDN caching behavior.
1415
+ const lk = h.key.toLowerCase();
1416
+ if (lk === "vary" || lk === "set-cookie") {
1417
+ response.headers.append(h.key, h.value);
1418
+ } else if (!response.headers.has(lk)) {
1419
+ // Middleware headers take precedence: skip config keys already
1420
+ // set by middleware so middleware headers always win.
1421
+ response.headers.set(h.key, h.value);
1422
+ }
1423
+ }
1424
+ }
1425
+ }
1426
+ return response;
1427
+ });
1392
1428
  }
1393
1429
 
1394
1430
  async function _handleRequest(request, __reqCtx, _mwCtx) {
@@ -1487,7 +1523,9 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1487
1523
  const nextRequest = mwRequest instanceof NextRequest ? mwRequest : new NextRequest(mwRequest);
1488
1524
  const mwFetchEvent = new NextFetchEvent({ page: cleanPathname });
1489
1525
  const mwResponse = await middlewareFn(nextRequest, mwFetchEvent);
1490
- mwFetchEvent.drainWaitUntil();
1526
+ const _mwWaitUntil = mwFetchEvent.drainWaitUntil();
1527
+ const _mwExecCtx = _getRequestExecutionContext();
1528
+ if (_mwExecCtx && typeof _mwExecCtx.waitUntil === "function") { _mwExecCtx.waitUntil(_mwWaitUntil); }
1491
1529
  if (mwResponse) {
1492
1530
  // Check for x-middleware-next (continue)
1493
1531
  if (mwResponse.headers.get("x-middleware-next") === "1") {
@@ -1512,6 +1550,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1512
1550
  if (rewriteUrl) {
1513
1551
  const rewriteParsed = new URL(rewriteUrl, request.url);
1514
1552
  cleanPathname = rewriteParsed.pathname;
1553
+ // Carry over query params from the rewrite URL so that
1554
+ // searchParams props, useSearchParams(), and navigation context
1555
+ // reflect the rewrite destination, not the original request.
1556
+ url.search = rewriteParsed.search;
1515
1557
  // Capture custom status code from rewrite (e.g. NextResponse.rewrite(url, { status: 403 }))
1516
1558
  if (mwResponse.status !== 200) {
1517
1559
  _mwCtx.status = mwResponse.status;
@@ -1606,45 +1648,53 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1606
1648
  // Skip — the base servedUrl is not served when generateSitemaps exists
1607
1649
  continue;
1608
1650
  }
1609
- if (cleanPathname === metaRoute.servedUrl) {
1610
- if (metaRoute.isDynamic) {
1611
- // Dynamic metadata route — call the default export and serialize
1612
- const metaFn = metaRoute.module.default;
1613
- if (typeof metaFn === "function") {
1614
- const result = await metaFn();
1615
- let body;
1616
- // If it's already a Response (e.g., ImageResponse), return directly
1617
- if (result instanceof Response) return result;
1618
- // Serialize based on type
1619
- if (metaRoute.type === "sitemap") body = sitemapToXml(result);
1620
- else if (metaRoute.type === "robots") body = robotsToText(result);
1621
- else if (metaRoute.type === "manifest") body = manifestToJson(result);
1622
- else body = JSON.stringify(result);
1623
- return new Response(body, {
1624
- headers: { "Content-Type": metaRoute.contentType },
1625
- });
1626
- }
1627
- } else {
1628
- // Static metadata file decode from embedded base64 data
1629
- try {
1630
- const binary = atob(metaRoute.fileDataBase64);
1631
- const bytes = new Uint8Array(binary.length);
1632
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1633
- return new Response(bytes, {
1634
- headers: {
1635
- "Content-Type": metaRoute.contentType,
1636
- "Cache-Control": "public, max-age=0, must-revalidate",
1637
- },
1638
- });
1639
- } catch {
1640
- return new Response("Not Found", { status: 404 });
1641
- }
1651
+ // Match metadata route — use pattern matching for dynamic segments,
1652
+ // strict equality for static paths.
1653
+ var _metaParams = null;
1654
+ if (metaRoute.patternParts) {
1655
+ var _metaUrlParts = cleanPathname.split("/").filter(Boolean);
1656
+ _metaParams = matchPattern(_metaUrlParts, metaRoute.patternParts);
1657
+ if (!_metaParams) continue;
1658
+ } else if (cleanPathname !== metaRoute.servedUrl) {
1659
+ continue;
1660
+ }
1661
+ if (metaRoute.isDynamic) {
1662
+ // Dynamic metadata route call the default export and serialize
1663
+ const metaFn = metaRoute.module.default;
1664
+ if (typeof metaFn === "function") {
1665
+ const result = await metaFn({ params: makeThenableParams(_metaParams || {}) });
1666
+ let body;
1667
+ // If it's already a Response (e.g., ImageResponse), return directly
1668
+ if (result instanceof Response) return result;
1669
+ // Serialize based on type
1670
+ if (metaRoute.type === "sitemap") body = sitemapToXml(result);
1671
+ else if (metaRoute.type === "robots") body = robotsToText(result);
1672
+ else if (metaRoute.type === "manifest") body = manifestToJson(result);
1673
+ else body = JSON.stringify(result);
1674
+ return new Response(body, {
1675
+ headers: { "Content-Type": metaRoute.contentType },
1676
+ });
1677
+ }
1678
+ } else {
1679
+ // Static metadata file — decode from embedded base64 data
1680
+ try {
1681
+ const binary = atob(metaRoute.fileDataBase64);
1682
+ const bytes = new Uint8Array(binary.length);
1683
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1684
+ return new Response(bytes, {
1685
+ headers: {
1686
+ "Content-Type": metaRoute.contentType,
1687
+ "Cache-Control": "public, max-age=0, must-revalidate",
1688
+ },
1689
+ });
1690
+ } catch {
1691
+ return new Response("Not Found", { status: 404 });
1642
1692
  }
1643
1693
  }
1644
1694
  }
1645
1695
 
1646
1696
  // Set navigation context for Server Components.
1647
- // Note: Headers context is already set by runWithHeadersContext in the handler wrapper.
1697
+ // Note: Headers context is already set by runWithRequestContext in the handler wrapper.
1648
1698
  setNavigationContext({
1649
1699
  pathname: cleanPathname,
1650
1700
  searchParams: url.searchParams,
@@ -1784,7 +1834,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1784
1834
  // Collect cookies set during the action synchronously (before stream is consumed).
1785
1835
  // Do NOT clear headers/navigation context here — the RSC stream is consumed lazily
1786
1836
  // by the client, and async server components that run during consumption need the
1787
- // context to still be live. The AsyncLocalStorage scope from runWithHeadersContext
1837
+ // context to still be live. The AsyncLocalStorage scope from runWithRequestContext
1788
1838
  // handles cleanup naturally when all async continuations complete.
1789
1839
  const actionPendingCookies = getAndClearPendingCookies();
1790
1840
  const actionDraftCookie = getDraftModeCookieHeader();
@@ -1869,15 +1919,15 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1869
1919
  const handler = route.routeHandler;
1870
1920
  const method = request.method.toUpperCase();
1871
1921
  const revalidateSeconds = typeof handler.revalidate === "number" && handler.revalidate > 0 ? handler.revalidate : null;
1922
+ if (typeof handler["default"] === "function" && process.env.NODE_ENV === "development") {
1923
+ console.error(
1924
+ "[vinext] Detected default export in route handler " + route.pattern + ". Export a named export for each HTTP method instead.",
1925
+ );
1926
+ }
1872
1927
 
1873
1928
  // Collect exported HTTP methods for OPTIONS auto-response and Allow header
1874
- const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
1875
- const exportedMethods = HTTP_METHODS.filter((m) => typeof handler[m] === "function");
1876
- // If GET is exported, HEAD is implicitly supported
1877
- if (exportedMethods.includes("GET") && !exportedMethods.includes("HEAD")) {
1878
- exportedMethods.push("HEAD");
1879
- }
1880
- const hasDefault = typeof handler["default"] === "function";
1929
+ const exportedMethods = collectRouteHandlerMethods(handler);
1930
+ const allowHeaderForOptions = buildRouteHandlerAllowHeader(exportedMethods);
1881
1931
 
1882
1932
  // Route handlers need the same middleware header/status merge behavior as
1883
1933
  // page responses. This keeps middleware response headers visible on API
@@ -1904,18 +1954,16 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
1904
1954
 
1905
1955
  // OPTIONS auto-implementation: respond with Allow header and 204
1906
1956
  if (method === "OPTIONS" && typeof handler["OPTIONS"] !== "function") {
1907
- const allowMethods = hasDefault ? HTTP_METHODS : exportedMethods;
1908
- if (!allowMethods.includes("OPTIONS")) allowMethods.push("OPTIONS");
1909
1957
  setHeadersContext(null);
1910
1958
  setNavigationContext(null);
1911
1959
  return attachRouteHandlerMiddlewareContext(new Response(null, {
1912
1960
  status: 204,
1913
- headers: { "Allow": allowMethods.join(", ") },
1961
+ headers: { "Allow": allowHeaderForOptions },
1914
1962
  }));
1915
1963
  }
1916
1964
 
1917
1965
  // HEAD auto-implementation: run GET handler and strip body
1918
- let handlerFn = handler[method] || handler["default"];
1966
+ let handlerFn = handler[method];
1919
1967
  let isAutoHead = false;
1920
1968
  if (method === "HEAD" && typeof handler["HEAD"] !== "function" && typeof handler["GET"] === "function") {
1921
1969
  handlerFn = handler["GET"];
@@ -2017,7 +2065,6 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2017
2065
  setNavigationContext(null);
2018
2066
  return attachRouteHandlerMiddlewareContext(new Response(null, {
2019
2067
  status: 405,
2020
- headers: { Allow: exportedMethods.join(", ") },
2021
2068
  }));
2022
2069
  }
2023
2070
 
@@ -2137,57 +2184,54 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2137
2184
  // user request — to prevent user-specific cookies/auth headers from leaking
2138
2185
  // into content that is cached and served to all subsequent users.
2139
2186
  const __revalHeadCtx = { headers: new Headers(), cookies: new Map() };
2140
- const __revalResult = await runWithHeadersContext(__revalHeadCtx, () =>
2141
- _runWithNavigationContext(() =>
2142
- _runWithCacheState(() =>
2143
- _runWithPrivateCache(() =>
2144
- runWithFetchCache(async () => {
2145
- setNavigationContext({ pathname: cleanPathname, searchParams: url.searchParams, params });
2146
- const __revalElement = await buildPageElement(route, params, undefined, url.searchParams);
2147
- const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2148
- const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError });
2149
- // Tee RSC stream: one for SSR, one to capture rscData
2150
- const [__revalRscForSsr, __revalRscForCapture] = __revalRscStream.tee();
2151
- // Capture rscData bytes in parallel with SSR
2152
- const __rscDataPromise = (async () => {
2153
- const __rscReader = __revalRscForCapture.getReader();
2154
- const __rscChunks = [];
2155
- let __rscTotal = 0;
2156
- for (;;) {
2157
- const { done, value } = await __rscReader.read();
2158
- if (done) break;
2159
- __rscChunks.push(value);
2160
- __rscTotal += value.byteLength;
2161
- }
2162
- const __rscBuf = new Uint8Array(__rscTotal);
2163
- let __rscOff = 0;
2164
- for (const c of __rscChunks) { __rscBuf.set(c, __rscOff); __rscOff += c.byteLength; }
2165
- return __rscBuf.buffer;
2166
- })();
2167
- const __revalFontData = { links: _getSSRFontLinks(), styles: _getSSRFontStyles(), preloads: _getSSRFontPreloads() };
2168
- const __revalSsrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2169
- const __revalHtmlStream = await __revalSsrEntry.handleSsr(__revalRscForSsr, _getNavigationContext(), __revalFontData);
2170
- setHeadersContext(null);
2171
- setNavigationContext(null);
2172
- // Collect the full HTML string from the stream
2173
- const __revalReader = __revalHtmlStream.getReader();
2174
- const __revalDecoder = new TextDecoder();
2175
- const __revalChunks = [];
2176
- for (;;) {
2177
- const { done, value } = await __revalReader.read();
2178
- if (done) break;
2179
- __revalChunks.push(__revalDecoder.decode(value, { stream: true }));
2180
- }
2181
- __revalChunks.push(__revalDecoder.decode());
2182
- const __freshHtml = __revalChunks.join("");
2183
- const __freshRscData = await __rscDataPromise;
2184
- const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2185
- return { html: __freshHtml, rscData: __freshRscData, tags: __pageTags };
2186
- })
2187
- )
2188
- )
2189
- )
2190
- );
2187
+ const __revalUCtx = _createUnifiedCtx({
2188
+ headersContext: __revalHeadCtx,
2189
+ executionContext: _getRequestExecutionContext(),
2190
+ });
2191
+ const __revalResult = await _runWithUnifiedCtx(__revalUCtx, async () => {
2192
+ _ensureFetchPatch();
2193
+ setNavigationContext({ pathname: cleanPathname, searchParams: url.searchParams, params });
2194
+ const __revalElement = await buildPageElement(route, params, undefined, url.searchParams);
2195
+ const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern);
2196
+ const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError });
2197
+ // Tee RSC stream: one for SSR, one to capture rscData
2198
+ const [__revalRscForSsr, __revalRscForCapture] = __revalRscStream.tee();
2199
+ // Capture rscData bytes in parallel with SSR
2200
+ const __rscDataPromise = (async () => {
2201
+ const __rscReader = __revalRscForCapture.getReader();
2202
+ const __rscChunks = [];
2203
+ let __rscTotal = 0;
2204
+ for (;;) {
2205
+ const { done, value } = await __rscReader.read();
2206
+ if (done) break;
2207
+ __rscChunks.push(value);
2208
+ __rscTotal += value.byteLength;
2209
+ }
2210
+ const __rscBuf = new Uint8Array(__rscTotal);
2211
+ let __rscOff = 0;
2212
+ for (const c of __rscChunks) { __rscBuf.set(c, __rscOff); __rscOff += c.byteLength; }
2213
+ return __rscBuf.buffer;
2214
+ })();
2215
+ const __revalFontData = { links: _getSSRFontLinks(), styles: _getSSRFontStyles(), preloads: _getSSRFontPreloads() };
2216
+ const __revalSsrEntry = await import.meta.viteRsc.loadModule("ssr", "index");
2217
+ const __revalHtmlStream = await __revalSsrEntry.handleSsr(__revalRscForSsr, _getNavigationContext(), __revalFontData);
2218
+ setHeadersContext(null);
2219
+ setNavigationContext(null);
2220
+ // Collect the full HTML string from the stream
2221
+ const __revalReader = __revalHtmlStream.getReader();
2222
+ const __revalDecoder = new TextDecoder();
2223
+ const __revalChunks = [];
2224
+ for (;;) {
2225
+ const { done, value } = await __revalReader.read();
2226
+ if (done) break;
2227
+ __revalChunks.push(__revalDecoder.decode(value, { stream: true }));
2228
+ }
2229
+ __revalChunks.push(__revalDecoder.decode());
2230
+ const __freshHtml = __revalChunks.join("");
2231
+ const __freshRscData = await __rscDataPromise;
2232
+ const __pageTags = __pageCacheTags(cleanPathname, getCollectedFetchTags());
2233
+ return { html: __freshHtml, rscData: __freshRscData, tags: __pageTags };
2234
+ });
2191
2235
  // Write HTML and RSC to their own keys independently — no races
2192
2236
  await Promise.all([
2193
2237
  __isrSet(__isrHtmlKey(cleanPathname), { kind: "APP_PAGE", html: __revalResult.html, rscData: undefined, headers: undefined, postponed: undefined, status: 200 }, __revalSecs, __revalResult.tags),
@@ -2296,7 +2340,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2296
2340
  const interceptStream = renderToReadableStream(interceptElement, { onError: interceptOnError });
2297
2341
  // Do NOT clear headers/navigation context here — the RSC stream is consumed lazily
2298
2342
  // by the client, and async server components that run during consumption need the
2299
- // context to still be live. The AsyncLocalStorage scope from runWithHeadersContext
2343
+ // context to still be live. The AsyncLocalStorage scope from runWithRequestContext
2300
2344
  // handles cleanup naturally when all async continuations complete.
2301
2345
  return new Response(interceptStream, {
2302
2346
  headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" },
@@ -2531,7 +2575,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
2531
2575
  // NOTE: Do NOT clear headers/navigation context here!
2532
2576
  // The RSC stream is consumed lazily - components render when chunks are read.
2533
2577
  // If we clear context now, headers()/cookies() will fail during rendering.
2534
- // Context will be cleared when the next request starts (via runWithHeadersContext).
2578
+ // Context will be cleared when the next request starts (via runWithRequestContext).
2535
2579
  const responseHeaders = { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" };
2536
2580
  // Include matched route params so the client can hydrate useParams()
2537
2581
  if (params && Object.keys(params).length > 0) {