vinext 1.1.0 → 1.2.0

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 (234) hide show
  1. package/README.md +21 -9
  2. package/dist/build/client-build-config.d.ts +10 -0
  3. package/dist/build/client-build-config.js +18 -2
  4. package/dist/build/pages-client-assets-module.d.ts +8 -0
  5. package/dist/build/pages-client-assets-module.js +21 -1
  6. package/dist/build/prerender-paths.js +5 -3
  7. package/dist/build/run-prerender.js +1 -1
  8. package/dist/build/ssr-manifest.d.ts +2 -0
  9. package/dist/build/ssr-manifest.js +6 -2
  10. package/dist/check.js +7 -6
  11. package/dist/cli.js +2 -1
  12. package/dist/client/client-redirect-matcher.d.ts +14 -0
  13. package/dist/client/client-redirect-matcher.js +37 -0
  14. package/dist/client/client-redirects.d.ts +27 -0
  15. package/dist/client/client-redirects.js +28 -0
  16. package/dist/client/client-rewrites.d.ts +2 -1
  17. package/dist/client/client-rewrites.js +1 -1
  18. package/dist/config/config-matchers.d.ts +26 -11
  19. package/dist/config/config-matchers.js +78 -81
  20. package/dist/config/destination-params.d.ts +47 -0
  21. package/dist/config/destination-params.js +116 -0
  22. package/dist/config/next-config.d.ts +7 -0
  23. package/dist/config/next-config.js +3 -0
  24. package/dist/entries/app-rsc-entry.d.ts +4 -0
  25. package/dist/entries/app-rsc-entry.js +20 -6
  26. package/dist/entries/pages-client-entry.js +3 -1
  27. package/dist/entries/pages-server-entry.js +13 -37
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +134 -43
  30. package/dist/init-cloudflare.d.ts +9 -0
  31. package/dist/init-cloudflare.js +107 -31
  32. package/dist/init-platform.d.ts +4 -0
  33. package/dist/init-platform.js +27 -2
  34. package/dist/init.d.ts +1 -0
  35. package/dist/init.js +67 -3
  36. package/dist/plugins/ast-scope.d.ts +7 -0
  37. package/dist/plugins/ast-scope.js +59 -3
  38. package/dist/plugins/cache-flight-codec.d.ts +1 -1
  39. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  40. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  41. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  42. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  43. package/dist/plugins/middleware-server-only.d.ts +5 -4
  44. package/dist/plugins/middleware-server-only.js +8 -5
  45. package/dist/plugins/pages-node-externals.js +1 -1
  46. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  47. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  48. package/dist/plugins/strip-server-exports.js +262 -98
  49. package/dist/plugins/transform-result.d.ts +9 -5
  50. package/dist/plugins/transitive-externals.js +1 -1
  51. package/dist/plugins/typeof-window.d.ts +2 -2
  52. package/dist/plugins/use-cache-callable.js +8 -2
  53. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  54. package/dist/plugins/worker-server-only-imports.js +31 -0
  55. package/dist/plugins/worker-use-server.d.ts +25 -0
  56. package/dist/plugins/worker-use-server.js +129 -0
  57. package/dist/server/api-handler.d.ts +1 -0
  58. package/dist/server/api-handler.js +2 -1
  59. package/dist/server/app-browser-action-result.d.ts +1 -1
  60. package/dist/server/app-browser-action-result.js +9 -2
  61. package/dist/server/app-browser-navigation-controller.js +1 -1
  62. package/dist/server/app-browser-server-action-client.js +1 -1
  63. package/dist/server/app-middleware.d.ts +11 -0
  64. package/dist/server/app-middleware.js +17 -2
  65. package/dist/server/app-page-cache.d.ts +1 -1
  66. package/dist/server/app-page-cache.js +1 -1
  67. package/dist/server/app-page-dispatch.d.ts +3 -1
  68. package/dist/server/app-page-dispatch.js +4 -3
  69. package/dist/server/app-page-ppr-runtime.js +1 -1
  70. package/dist/server/app-page-render-observation.js +1 -1
  71. package/dist/server/app-page-request.js +53 -15
  72. package/dist/server/app-page-route-wiring.js +2 -2
  73. package/dist/server/app-pages-bridge.d.ts +1 -1
  74. package/dist/server/app-pages-bridge.js +2 -3
  75. package/dist/server/app-prerender-endpoints.js +9 -6
  76. package/dist/server/app-request-stage-dispatch.js +6 -2
  77. package/dist/server/app-request-stage-independent-entry.js +25 -5
  78. package/dist/server/app-response-stage-entry.js +10 -2
  79. package/dist/server/app-route-handler-cache.d.ts +1 -0
  80. package/dist/server/app-route-handler-cache.js +1 -0
  81. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  82. package/dist/server/app-route-handler-dispatch.js +5 -2
  83. package/dist/server/app-route-handler-execution.d.ts +1 -0
  84. package/dist/server/app-route-handler-execution.js +1 -0
  85. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  86. package/dist/server/app-route-handler-runtime.js +45 -24
  87. package/dist/server/app-router-entry.js +24 -5
  88. package/dist/server/app-rsc-cache-busting.js +1 -1
  89. package/dist/server/app-rsc-errors.js +20 -16
  90. package/dist/server/app-rsc-handler.d.ts +7 -0
  91. package/dist/server/app-rsc-handler.js +85 -35
  92. package/dist/server/app-rsc-response-stage.js +4 -2
  93. package/dist/server/app-segment-config.d.ts +2 -1
  94. package/dist/server/app-server-action-execution.d.ts +2 -1
  95. package/dist/server/app-server-action-execution.js +61 -22
  96. package/dist/server/app-ssr-entry.js +2 -1
  97. package/dist/server/app-worker-stages.d.ts +5 -0
  98. package/dist/server/app-worker-stages.js +1 -1
  99. package/dist/server/cacheability-request.js +2 -2
  100. package/dist/server/client-build-files.d.ts +16 -0
  101. package/dist/server/client-build-files.js +36 -0
  102. package/dist/server/client-reuse-manifest.js +1 -1
  103. package/dist/server/dev-server.js +38 -27
  104. package/dist/server/dev-stack-sourcemap.js +18 -23
  105. package/dist/server/document-initial-head.d.ts +0 -11
  106. package/dist/server/document-initial-head.js +1 -1
  107. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  108. package/dist/server/forwarded-middleware-context.js +22 -0
  109. package/dist/server/headers.d.ts +4 -1
  110. package/dist/server/headers.js +4 -1
  111. package/dist/server/http-conditional.d.ts +11 -0
  112. package/dist/server/http-conditional.js +10 -1
  113. package/dist/server/http-error-responses.d.ts +1 -1
  114. package/dist/server/image-optimization.d.ts +38 -0
  115. package/dist/server/image-optimization.js +93 -4
  116. package/dist/server/isr-cache.d.ts +10 -0
  117. package/dist/server/isr-cache.js +41 -6
  118. package/dist/server/metadata-route-response.js +9 -8
  119. package/dist/server/middleware-matcher.d.ts +2 -0
  120. package/dist/server/middleware-matcher.js +23 -2
  121. package/dist/server/middleware-runtime.d.ts +13 -0
  122. package/dist/server/middleware-runtime.js +33 -20
  123. package/dist/server/middleware.d.ts +1 -1
  124. package/dist/server/middleware.js +3 -1
  125. package/dist/server/multi-stage.d.ts +11 -0
  126. package/dist/server/normalize-path.d.ts +0 -13
  127. package/dist/server/normalize-path.js +1 -34
  128. package/dist/server/pages-api-route.d.ts +3 -2
  129. package/dist/server/pages-api-route.js +4 -3
  130. package/dist/server/pages-cache-tag.d.ts +12 -0
  131. package/dist/server/pages-cache-tag.js +19 -0
  132. package/dist/server/pages-client-assets.d.ts +2 -0
  133. package/dist/server/pages-data-route.d.ts +10 -0
  134. package/dist/server/pages-data-route.js +19 -2
  135. package/dist/server/pages-document-asset-props.d.ts +5 -1
  136. package/dist/server/pages-document-asset-props.js +13 -5
  137. package/dist/server/pages-document-splice.d.ts +19 -0
  138. package/dist/server/pages-document-splice.js +55 -0
  139. package/dist/server/pages-i18n.d.ts +12 -1
  140. package/dist/server/pages-i18n.js +18 -4
  141. package/dist/server/pages-node-compat.d.ts +16 -5
  142. package/dist/server/pages-node-compat.js +20 -12
  143. package/dist/server/pages-page-data.d.ts +32 -24
  144. package/dist/server/pages-page-data.js +50 -120
  145. package/dist/server/pages-page-handler.d.ts +0 -6
  146. package/dist/server/pages-page-handler.js +188 -126
  147. package/dist/server/pages-page-response.d.ts +12 -6
  148. package/dist/server/pages-page-response.js +41 -34
  149. package/dist/server/pages-request-pipeline.d.ts +10 -2
  150. package/dist/server/pages-request-pipeline.js +37 -18
  151. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  152. package/dist/server/pages-request-stage-entry.js +71 -12
  153. package/dist/server/pages-response-stage-entry.js +4 -0
  154. package/dist/server/pages-revalidate.js +1 -1
  155. package/dist/server/pages-router-entry.js +4 -3
  156. package/dist/server/prerender-manifest.d.ts +3 -3
  157. package/dist/server/prerender-manifest.js +4 -3
  158. package/dist/server/prerender-work-unit-setup.js +4 -0
  159. package/dist/server/prod-server.d.ts +12 -0
  160. package/dist/server/prod-server.js +199 -69
  161. package/dist/server/proper-error.d.ts +4 -0
  162. package/dist/server/proper-error.js +31 -0
  163. package/dist/server/request-pipeline.d.ts +4 -1
  164. package/dist/server/request-pipeline.js +3 -2
  165. package/dist/server/revalidation-request.d.ts +1 -0
  166. package/dist/server/revalidation-request.js +1 -1
  167. package/dist/server/rsc-stream-hints.js +31 -2
  168. package/dist/server/seed-cache.d.ts +1 -0
  169. package/dist/server/seed-cache.js +5 -4
  170. package/dist/server/server-reference-loader.d.ts +4 -0
  171. package/dist/server/server-reference-loader.js +29 -0
  172. package/dist/server/socket-error-backstop.d.ts +9 -1
  173. package/dist/server/socket-error-backstop.js +46 -17
  174. package/dist/server/static-file-signal.d.ts +15 -4
  175. package/dist/server/static-file-signal.js +46 -12
  176. package/dist/server/worker-image-optimization.d.ts +34 -0
  177. package/dist/server/worker-image-optimization.js +86 -0
  178. package/dist/server/worker-stages.d.ts +1 -1
  179. package/dist/server/worker-stages.js +2 -2
  180. package/dist/server/worker-utils.d.ts +8 -3
  181. package/dist/server/worker-utils.js +12 -7
  182. package/dist/shims/app-router-scroll.js +4 -1
  183. package/dist/shims/app-router-tree-context.d.ts +5 -0
  184. package/dist/shims/app-router-tree-context.js +27 -0
  185. package/dist/shims/cache-callable-runtime.d.ts +4 -2
  186. package/dist/shims/cache-callable-runtime.js +21 -12
  187. package/dist/shims/cache-handler.js +1 -9
  188. package/dist/shims/cache-runtime.d.ts +6 -1
  189. package/dist/shims/cache-runtime.js +2 -1
  190. package/dist/shims/cache.d.ts +2 -2
  191. package/dist/shims/cache.js +40 -13
  192. package/dist/shims/cdn-cache.d.ts +8 -0
  193. package/dist/shims/cdn-cache.js +17 -1
  194. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  195. package/dist/shims/disable-smooth-scroll.js +36 -0
  196. package/dist/shims/document-placeholders.d.ts +19 -0
  197. package/dist/shims/document-placeholders.js +45 -0
  198. package/dist/shims/document.js +21 -5
  199. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  200. package/dist/shims/dynamic-preload-chunks.js +13 -9
  201. package/dist/shims/dynamic.d.ts +1 -1
  202. package/dist/shims/dynamic.js +60 -8
  203. package/dist/shims/fetch-cache-state.d.ts +66 -0
  204. package/dist/shims/fetch-cache-state.js +92 -0
  205. package/dist/shims/fetch-cache.d.ts +3 -67
  206. package/dist/shims/fetch-cache.js +160 -125
  207. package/dist/shims/head.d.ts +10 -6
  208. package/dist/shims/head.js +15 -13
  209. package/dist/shims/image.js +2 -1
  210. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  211. package/dist/shims/internal/work-store-scope.js +24 -0
  212. package/dist/shims/navigation.js +31 -5
  213. package/dist/shims/request-context.d.ts +2 -0
  214. package/dist/shims/request-state-types.d.ts +2 -1
  215. package/dist/shims/router.js +12 -20
  216. package/dist/shims/server.d.ts +16 -0
  217. package/dist/shims/server.js +24 -4
  218. package/dist/shims/unified-request-context.d.ts +1 -1
  219. package/dist/shims/url-utils.d.ts +2 -0
  220. package/dist/shims/url-utils.js +3 -1
  221. package/dist/utils/client-runtime-metadata.js +1 -1
  222. package/dist/utils/hash.d.ts +7 -0
  223. package/dist/utils/hash.js +11 -1
  224. package/dist/utils/package-name.d.ts +6 -0
  225. package/dist/utils/package-name.js +12 -1
  226. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  227. package/dist/utils/plugin-rsc-version.js +15 -0
  228. package/dist/utils/prerender-output-paths.js +15 -4
  229. package/dist/utils/public-file-path.d.ts +10 -2
  230. package/dist/utils/public-file-path.js +13 -9
  231. package/dist/utils/public-routes.js +2 -2
  232. package/dist/utils/react-version.d.ts +3 -0
  233. package/dist/utils/react-version.js +1 -1
  234. package/package.json +7 -3
@@ -78,6 +78,93 @@ function resolveDevImageRedirect(requestUrl, allowedWidths = [...DEFAULT_DEVICE_
78
78
  if (resolved.origin !== requestUrl.origin) return null;
79
79
  return resolved.pathname + resolved.search;
80
80
  }
81
+ const IMAGE_OPTIMIZATION_SIGNAL = Symbol.for("vinext.image-optimization-signal");
82
+ /**
83
+ * Mark the App Router's image endpoint response once the request has passed
84
+ * middleware and rewrites. A host that serves images itself (the Node
85
+ * production server) replaces the response with the image; other hosts send
86
+ * the response unchanged. The symbol carries the signal across the built RSC
87
+ * module boundary, so response headers cannot forge it.
88
+ */
89
+ function markImageOptimizationSignal(response, signal) {
90
+ Object.defineProperty(response, IMAGE_OPTIMIZATION_SIGNAL, { value: signal });
91
+ return response;
92
+ }
93
+ /** Return the signal of a framework-created image endpoint response. */
94
+ function readImageOptimizationSignal(response) {
95
+ const signal = Reflect.get(response, IMAGE_OPTIMIZATION_SIGNAL);
96
+ if (typeof signal !== "object" || signal === null) return null;
97
+ const { middlewareHeaders, requestHeaders, search } = signal;
98
+ return middlewareHeaders instanceof Headers && (requestHeaders === null || requestHeaders instanceof Headers) && typeof search === "string" ? {
99
+ middlewareHeaders,
100
+ requestHeaders,
101
+ search
102
+ } : null;
103
+ }
104
+ const IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER = "x-vinext-stage-image-optimization";
105
+ function isHeaderEntries(value) {
106
+ return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
107
+ }
108
+ /**
109
+ * Carry a signal through the response-stage transport, which drops the
110
+ * symbol. The signal rides in the body of the bodiless carrier, with the
111
+ * carrier's status in the token-authenticated marker.
112
+ */
113
+ function serializeImageOptimizationSignalForTransport(response, token) {
114
+ const signal = readImageOptimizationSignal(response);
115
+ if (signal === null) return response;
116
+ const headers = new Headers(response.headers);
117
+ headers.set(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER, `${token}:${response.status}`);
118
+ return new Response(JSON.stringify({
119
+ middlewareHeaders: [...signal.middlewareHeaders],
120
+ requestHeaders: signal.requestHeaders ? [...signal.requestHeaders] : null,
121
+ search: signal.search
122
+ }), { headers });
123
+ }
124
+ /** Restore and consume a signal returned by the trusted response-stage wrapper. */
125
+ async function restoreImageOptimizationSignalFromTransport(response, token) {
126
+ const marker = response.headers.get(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER);
127
+ if (marker === null || response.status < 200 || response.status > 599) return response;
128
+ const headers = new Headers(response.headers);
129
+ headers.delete(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER);
130
+ const prefix = `${token}:`;
131
+ const status = marker.startsWith(prefix) ? Number(marker.slice(prefix.length)) : NaN;
132
+ if (!Number.isInteger(status) || status < 200 || status > 599) {
133
+ const body = response.status === 204 || response.status === 205 || response.status === 304 ? null : response.body;
134
+ return new Response(body, {
135
+ headers,
136
+ status: response.status,
137
+ statusText: response.statusText
138
+ });
139
+ }
140
+ const { middlewareHeaders, requestHeaders, search } = JSON.parse(await response.text()) ?? {};
141
+ if (!isHeaderEntries(middlewareHeaders) || requestHeaders !== null && !isHeaderEntries(requestHeaders) || typeof search !== "string") throw new Error("Invalid image optimization signal from the response stage");
142
+ return markImageOptimizationSignal(new Response(null, {
143
+ headers,
144
+ status
145
+ }), {
146
+ middlewareHeaders: new Headers(middlewareHeaders),
147
+ requestHeaders: requestHeaders ? new Headers(requestHeaders) : null,
148
+ search
149
+ });
150
+ }
151
+ /**
152
+ * Whether a local source's pipeline status carries the file's bytes. A no-body
153
+ * success such as 204 serves no file, which Next.js rejects as an invalid
154
+ * image.
155
+ */
156
+ function isImageSourceBodyStatus(status) {
157
+ return status >= 200 && status < 300 && status !== 204 && status !== 205;
158
+ }
159
+ /**
160
+ * Body Next.js answers, with a 400, for a local source that yields no image:
161
+ * a missing file, or anything middleware or routing answers in its place.
162
+ */
163
+ const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
164
+ function invalidImageSourceResponse() {
165
+ return new Response(INVALID_IMAGE_SOURCE_MESSAGE, { status: 400 });
166
+ }
167
+ const RECURSIVE_IMAGE_URL_RE = /\/_(?:next|vinext)\/image(?:$|\/)/;
81
168
  /**
82
169
  * Parse and validate image optimization query parameters.
83
170
  * Returns null if the request is malformed.
@@ -113,7 +200,9 @@ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFA
113
200
  if (!normalizedUrl.startsWith("/") || normalizedUrl.startsWith("//")) return null;
114
201
  try {
115
202
  const base = "https://localhost";
116
- if (new URL(normalizedUrl, base).origin !== base) return null;
203
+ const resolved = new URL(normalizedUrl, base);
204
+ if (resolved.origin !== base) return null;
205
+ if (RECURSIVE_IMAGE_URL_RE.test(decodeURIComponent(resolved.pathname))) return null;
117
206
  } catch {
118
207
  return null;
119
208
  }
@@ -226,7 +315,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
226
315
  if (!params) return badRequestResponse();
227
316
  const { imageUrl, width, quality } = params;
228
317
  const source = await handlers.fetchAsset(imageUrl, request);
229
- if (!source.ok || !source.body) return new Response("Image not found", { status: 404 });
318
+ if (!source.ok || !source.body) return invalidImageSourceResponse();
230
319
  const format = negotiateImageFormat(request.headers.get("Accept"));
231
320
  const sourceContentType = source.headers.get("Content-Type");
232
321
  if (!isSafeImageContentType(sourceContentType, imageConfig?.dangerouslyAllowSVG)) return new Response("The requested resource is not an allowed image type", { status: 400 });
@@ -252,7 +341,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
252
341
  } catch (e) {
253
342
  console.error("[vinext] Image fallback error, refetching source image:", e);
254
343
  const refetchedSource = await handlers.fetchAsset(imageUrl, request);
255
- if (!refetchedSource.ok || !refetchedSource.body) return new Response("Image not found", { status: 404 });
344
+ if (!refetchedSource.ok || !refetchedSource.body) return invalidImageSourceResponse();
256
345
  if (!isSafeImageContentType(refetchedSource.headers.get("Content-Type"), imageConfig?.dangerouslyAllowSVG)) return new Response("The requested resource is not an allowed image type", { status: 400 });
257
346
  return createPassthroughImageResponse(refetchedSource, imageConfig);
258
347
  }
@@ -307,4 +396,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
307
396
  }, allowedWidths, imageConfig);
308
397
  }
309
398
  //#endregion
310
- export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams, resolveDevImageRedirect, setImageOptimizer };
399
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
@@ -112,7 +112,17 @@ export declare function pagesIsrCacheKey(pathname: string, buildId?: string, i18
112
112
  * Cloudflare KV key under its 512-byte limit for long pathnames.
113
113
  */
114
114
  export declare function appIsrCacheKey(pathname: string, suffix: string, buildId?: string | undefined): string;
115
+ /**
116
+ * Compute the ISR key for a PPR fallback shell artifact, such as the HTML for
117
+ * `/en/[slug]`. Shells get their own namespace: a request for the literal path
118
+ * `/en/%5Bslug%5D` decodes to the same pathname, and its render must not be
119
+ * stored where every unknown `[slug]` reads its shell. The namespace is a
120
+ * prefix of its own rather than a part after the build ID, which can contain
121
+ * `:` and so could spell it.
122
+ */
123
+ export declare function appIsrFallbackShellKey(pathname: string, suffix: "html" | "rsc", buildId?: string | undefined): string;
115
124
  export declare function appIsrHtmlKey(pathname: string): string;
125
+ export declare function appIsrFallbackShellHtmlKey(pathname: string): string;
116
126
  /**
117
127
  * Build the ISR cache key for an RSC payload.
118
128
  *
@@ -4,12 +4,26 @@ import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { isInterceptionMatchedUrlPath } from "./normalize-path.js";
5
5
  import { reportRequestError } from "./instrumentation.js";
6
6
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
7
- import { fnv1a64 } from "../utils/hash.js";
8
7
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
9
8
  import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./app-rsc-render-mode.js";
10
9
  import { getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret } from "./revalidation-request.js";
10
+ import { createHash } from "node:crypto";
11
11
  //#region src/server/isr-cache.ts
12
12
  /**
13
+ * ISR (Incremental Static Regeneration) cache layer.
14
+ *
15
+ * Wraps the pluggable CacheHandler with stale-while-revalidate semantics:
16
+ * - Fresh hit: serve immediately
17
+ * - Stale hit: serve immediately + trigger background regeneration
18
+ * - Miss: render synchronously, cache, serve
19
+ *
20
+ * Background regeneration is deduped — only one regeneration per cache key
21
+ * runs at a time, preventing thundering herd on popular pages.
22
+ *
23
+ * This layer works with any CacheHandler backend (memory, Redis, KV, etc.)
24
+ * because it only uses the standard get/set interface.
25
+ */
26
+ /**
13
27
  * Get a cache entry with staleness information.
14
28
  *
15
29
  * Returns { value, isStale: false } for fresh entries,
@@ -151,12 +165,19 @@ function buildAppPageCacheValue(html, rscData, status, renderObservation, header
151
165
  function normalizeCachePathname(pathname) {
152
166
  return pathname === "/" ? "/" : pathname.replace(/\/$/, "");
153
167
  }
168
+ /**
169
+ * Digest a request-controlled key part. It must be collision resistant: a
170
+ * crafted value that shared a weaker hash with another would share its entry.
171
+ */
172
+ function digestKeyPart(value) {
173
+ return createHash("sha256").update(value).digest("hex");
174
+ }
154
175
  function buildCacheKey(prefix, pathname, suffix) {
155
176
  const normalized = normalizeCachePathname(pathname);
156
177
  const suffixPart = suffix ? `:${suffix}` : "";
157
178
  const key = `${prefix}:${normalized}${suffixPart}`;
158
179
  if (key.length <= 200) return key;
159
- return `${prefix}:__hash:${fnv1a64(normalized)}${suffixPart}`;
180
+ return `${prefix}:__hash:${digestKeyPart(normalized)}${suffixPart}`;
160
181
  }
161
182
  /**
162
183
  * Compute an ISR cache key for a given router type and pathname.
@@ -179,9 +200,23 @@ function pagesIsrCacheKey(pathname, buildId, i18nCacheVariant) {
179
200
  function appIsrCacheKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
180
201
  return buildCacheKey(buildId ? `app:v2:${buildId}` : "app:v2", pathname, suffix);
181
202
  }
203
+ /**
204
+ * Compute the ISR key for a PPR fallback shell artifact, such as the HTML for
205
+ * `/en/[slug]`. Shells get their own namespace: a request for the literal path
206
+ * `/en/%5Bslug%5D` decodes to the same pathname, and its render must not be
207
+ * stored where every unknown `[slug]` reads its shell. The namespace is a
208
+ * prefix of its own rather than a part after the build ID, which can contain
209
+ * `:` and so could spell it.
210
+ */
211
+ function appIsrFallbackShellKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
212
+ return buildCacheKey(buildId ? `app-fallback-shell:v1:${buildId}` : "app-fallback-shell:v1", pathname, suffix);
213
+ }
182
214
  function appIsrHtmlKey(pathname) {
183
215
  return appIsrCacheKey(pathname, "html");
184
216
  }
217
+ function appIsrFallbackShellHtmlKey(pathname) {
218
+ return appIsrFallbackShellKey(pathname, "html");
219
+ }
185
220
  function normalizeInterceptionContextForCacheKey(interceptionContext) {
186
221
  return isInterceptionMatchedUrlPath(interceptionContext) ? interceptionContext : null;
187
222
  }
@@ -200,9 +235,9 @@ function appIsrRscKey(pathname, mountedSlotsHeader, renderMode = APP_RSC_RENDER_
200
235
  const normalizedMountedSlotsHeader = normalizeMountedSlotsHeader(mountedSlotsHeader);
201
236
  const sourceVariant = interceptionContext === void 0 || interceptionContext === null ? null : normalizeInterceptionContextForCacheKey(interceptionContext);
202
237
  const variant = [
203
- sourceVariant ? `source:${fnv1a64(sourceVariant)}` : null,
204
- interceptionId ? `selector:${fnv1a64(interceptionId)}` : null,
205
- normalizedMountedSlotsHeader ? `slots:${fnv1a64(normalizedMountedSlotsHeader)}` : null,
238
+ sourceVariant ? `source:${digestKeyPart(sourceVariant)}` : null,
239
+ interceptionId ? `selector:${digestKeyPart(interceptionId)}` : null,
240
+ normalizedMountedSlotsHeader ? `slots:${digestKeyPart(normalizedMountedSlotsHeader)}` : null,
206
241
  getRscRenderModeCacheVariant(renderMode)
207
242
  ].filter((part) => part !== null).join(":");
208
243
  return appIsrCacheKey(pathname, variant ? `rsc:${variant}` : "rsc");
@@ -211,4 +246,4 @@ function appIsrRouteKey(pathname) {
211
246
  return appIsrCacheKey(pathname, pathname !== "/" && pathname.endsWith("/") ? "route:trailing-slash" : "route");
212
247
  }
213
248
  //#endregion
214
- export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
249
+ export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrFallbackShellHtmlKey, appIsrFallbackShellKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
@@ -3,6 +3,7 @@ import { closeAfterResponse, createRequestContext, runWithRequestContext } from
3
3
  import { decodeMatchedParams } from "../routing/utils.js";
4
4
  import { getRequestExecutionContext } from "../shims/request-context.js";
5
5
  import { VINEXT_METADATA_ROUTE_CACHE_HEADER } from "./headers.js";
6
+ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
6
7
  import { notFoundResponse } from "./http-error-responses.js";
7
8
  import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
8
9
  import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy } from "./cache-control.js";
@@ -215,7 +216,7 @@ async function writeRenderedMetadataRoute(options, route, routeKey, rendered, pr
215
216
  async function runMetadataRouteRegeneration(options, route, render, routeKey, previousEntry, executionContext) {
216
217
  const requestContext = createRequestContext({ executionContext });
217
218
  try {
218
- await runWithRequestContext(requestContext, async () => {
219
+ await runWithRequestContext(requestContext, () => runInWorkStoreScope(async () => {
219
220
  ensureFetchPatch();
220
221
  beginMetadataRouteCacheability(route);
221
222
  setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
@@ -225,7 +226,7 @@ async function runMetadataRouteRegeneration(options, route, render, routeKey, pr
225
226
  } finally {
226
227
  rendered.response.body?.cancel().catch(() => {});
227
228
  }
228
- });
229
+ }));
229
230
  } finally {
230
231
  await closeAfterResponse(requestContext);
231
232
  }
@@ -512,6 +513,12 @@ async function handleMetadataRouteRequest(options) {
512
513
  decodeMatchedParams(runtimeParams);
513
514
  match.params = runtimeParams;
514
515
  }
516
+ const render = async () => {
517
+ setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
518
+ return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
519
+ };
520
+ const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
521
+ if (cached.response) return cached.response;
515
522
  if (route.isDynamic && isImageMetadataRoute(route) && functions.dynamicParams === false) {
516
523
  const validationParams = { ...rawParams };
517
524
  canonicalizeAppPageParams(validationParams);
@@ -526,12 +533,6 @@ async function handleMetadataRouteRequest(options) {
526
533
  });
527
534
  if (dynamicParamsResponse) return dynamicParamsResponse;
528
535
  }
529
- const render = async () => {
530
- setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
531
- return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
532
- };
533
- const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
534
- if (cached.response) return cached.response;
535
536
  const rendered = await render();
536
537
  await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
537
538
  return rendered.response;
@@ -7,6 +7,8 @@ export type MiddlewareLocaleMatchContext = {
7
7
  defaultLocale: string;
8
8
  } | {
9
9
  kind: "internal" | "literal";
10
+ } | {
11
+ kind: "next-data";
10
12
  };
11
13
  export declare function matchesMiddleware(pathname: string, matcher: MatcherConfig | undefined, request?: Request, i18nConfig?: NextI18nConfig | null, localeContext?: MiddlewareLocaleMatchContext): boolean;
12
14
  /**
@@ -49,6 +49,7 @@ function matchesMiddlewarePathname(pathname, matcher, i18nConfig, localeContext)
49
49
  return false;
50
50
  }
51
51
  function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
52
+ if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, pattern, i18nConfig, true);
52
53
  if (!i18nConfig) return matchPattern(pathname, pattern);
53
54
  if (localeContext) {
54
55
  if (localeContext.kind === "internal") return false;
@@ -57,9 +58,25 @@ function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
57
58
  return matchPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
58
59
  }
59
60
  function matchObjectMatcher(pathname, matcher, i18nConfig, localeContext) {
61
+ if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, matcher.source, i18nConfig, matcher.locale !== false);
60
62
  if (matcher.locale !== false) return matchMatcherPattern(pathname, matcher.source, i18nConfig, localeContext);
61
63
  return matchPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
62
64
  }
65
+ function matchNextDataPattern(pathname, source, i18nConfig, localized) {
66
+ if (compiledPattern(source) === UNSAFE_MATCHER_PATTERN) return true;
67
+ return matchPattern(pathname, nextDataMatcherSource(source, i18nConfig, localized));
68
+ }
69
+ /**
70
+ * The source Next.js compiles for a matcher: an optional `_next/data/<buildId>`
71
+ * prefix, an internal locale segment under i18n, and an optional data suffix.
72
+ * Next.js also accepts App Router transport suffixes, which end in `.rsc` and
73
+ * so can never match a `.json` data URL.
74
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
75
+ */
76
+ function nextDataMatcherSource(source, i18nConfig, localized) {
77
+ const isRoot = source === "/";
78
+ return `/:nextData(_next/data/[^/]{1,})?${i18nConfig && localized ? `/:nextInternalLocale((?!_next/)[^/.]{1,})${isRoot ? "" : source}` : source}${isRoot ? `(${i18nConfig ? "|\\.json|" : ""}/?index|/?index\\.json)?` : "{(\\.json)}?"}`;
79
+ }
63
80
  function stripLocalePrefix(pathname, i18nConfig) {
64
81
  if (pathname === "/") return null;
65
82
  const lowerFirstSegment = pathname.split("/")[1]?.toLowerCase();
@@ -71,13 +88,17 @@ function stripFirstPathSegment(pathname) {
71
88
  }
72
89
  function matchPattern(pathname, pattern) {
73
90
  if (pattern === "/" && (pathname === "//" || pathname === "/?" || pathname === "/#")) return false;
91
+ const compiled = compiledPattern(pattern);
92
+ if (compiled === UNSAFE_MATCHER_PATTERN) return true;
93
+ return compiled.test(pathname);
94
+ }
95
+ function compiledPattern(pattern) {
74
96
  let cached = _mwPatternCache.get(pattern);
75
97
  if (cached === void 0) {
76
98
  cached = compileMatcherPattern(pattern);
77
99
  _mwPatternCache.set(pattern, cached);
78
100
  }
79
- if (cached === UNSAFE_MATCHER_PATTERN) return true;
80
- return cached.test(pathname);
101
+ return cached;
81
102
  }
82
103
  function compileMatcherPattern(pattern) {
83
104
  const result = compileMiddlewareMatcherPattern(pattern);
@@ -10,6 +10,8 @@ export type MiddlewareResult = {
10
10
  redirectUrl?: string;
11
11
  redirectStatus?: number;
12
12
  rewriteUrl?: string;
13
+ /** False when a same-origin rewrite target was outside the basePath. */
14
+ rewriteHadBasePath?: boolean;
13
15
  rewriteStatus?: number;
14
16
  status?: number;
15
17
  responseHeaders?: Headers;
@@ -41,6 +43,11 @@ type ExecuteMiddlewareOptions = {
41
43
  * must derive and forward this from trusted URL normalization.
42
44
  */
43
45
  isDataRequest?: boolean;
46
+ /**
47
+ * The data request names a buildId other than the current build. Adapters
48
+ * pass it raw to middleware only under `skipProxyUrlNormalize`.
49
+ */
50
+ isStaleDataRequest?: boolean;
44
51
  isProxy: boolean;
45
52
  module: MiddlewareModule;
46
53
  normalizedPathname?: string;
@@ -63,6 +70,12 @@ type ExecuteMiddlewareOptions = {
63
70
  * `new URL('/x', req.url)`.
64
71
  */
65
72
  trailingSlash?: boolean;
73
+ /**
74
+ * next.config `skipTrailingSlashRedirect`. Middleware then sees requests in
75
+ * either slash form, so NextURL keeps each request's own trailing slash and
76
+ * redirect Locations keep theirs, as Next.js's `getNextPathnameInfo` does.
77
+ */
78
+ skipTrailingSlashRedirect?: boolean;
66
79
  };
67
80
  type RunGeneratedMiddlewareOptions = ExecuteMiddlewareOptions & {
68
81
  ctx?: ExecutionContextLike;
@@ -8,12 +8,14 @@ import { getRequestExecutionContext, runWithExecutionContext } from "../shims/re
8
8
  import "./server-globals.js";
9
9
  import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
10
10
  import { NextFetchEvent, NextRequest } from "../shims/server.js";
11
+ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
11
12
  import { normalizePath } from "./normalize-path.js";
12
13
  import { matchesMiddleware, matchesMiddlewarePathname } from "./middleware-matcher.js";
13
14
  import { badRequestResponse, internalServerErrorResponse } from "./http-error-responses.js";
14
15
  import { isOpenRedirectShaped } from "./open-redirect.js";
15
16
  import { processMiddlewareHeaders } from "./request-pipeline.js";
16
17
  import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
18
+ import { nextDataMiddlewareMatchPathname } from "./pages-data-route.js";
17
19
  import { reportRequestError } from "./instrumentation.js";
18
20
  import { isValidNavigationSignalError } from "../utils/navigation-signal.js";
19
21
  import { frameworkTracer } from "./tracer.js";
@@ -133,7 +135,7 @@ function resolveMiddlewarePathname(request) {
133
135
  return badRequestResponse();
134
136
  }
135
137
  }
136
- function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false) {
138
+ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBasePath, requestBodyAlreadyIsolated = false, skipTrailingSlashRedirect = false) {
137
139
  const url = new URL(request.url);
138
140
  let mwRequest = !requestBodyAlreadyIsolated && request.body && !request.bodyUsed ? request.clone() : request;
139
141
  const requestPathname = url.pathname;
@@ -143,10 +145,11 @@ function createNextRequest(request, i18nConfig, basePath, trailingSlash, hadBase
143
145
  mwUrl.pathname = mwPathname;
144
146
  mwRequest = new Request(mwUrl, mwRequest);
145
147
  }
146
- const nextConfig = basePath || i18nConfig || trailingSlash ? {
148
+ const nextConfig = basePath || i18nConfig || trailingSlash || skipTrailingSlashRedirect ? {
147
149
  basePath: basePath ?? "",
148
150
  i18n: i18nConfig ?? void 0,
149
- trailingSlash: trailingSlash ?? void 0
151
+ trailingSlash: trailingSlash ?? void 0,
152
+ skipTrailingSlashRedirect
150
153
  } : void 0;
151
154
  const nextRequest = mwRequest instanceof NextRequest && (!mwRequest.body || mwRequest.bodyUsed) ? mwRequest : new NextRequest(mwRequest, nextConfig ? { nextConfig } : void 0);
152
155
  if (mwRequest !== request && mwRequest.body && !mwRequest.bodyUsed && !mwRequest.body.locked) mwRequest.body.cancel().catch(() => {});
@@ -164,7 +167,10 @@ function releaseMiddlewareRequestBody(request, waitUntilPromises, retainedByResp
164
167
  }
165
168
  Promise.allSettled(waitUntilPromises).then(cancel);
166
169
  }
167
- async function executeMiddleware(options) {
170
+ function executeMiddleware(options) {
171
+ return runInWorkStoreScope(() => executeMiddlewareModule(options));
172
+ }
173
+ async function executeMiddlewareModule(options) {
168
174
  const middlewareFn = resolveMiddlewareModuleHandler(options.module, {
169
175
  filePath: options.filePath,
170
176
  isProxy: options.isProxy
@@ -187,14 +193,17 @@ async function executeMiddleware(options) {
187
193
  if (options.hadBasePath === true) return candidate;
188
194
  return matcher === void 0 ? candidate : null;
189
195
  };
190
- const encodedMatchPathname = prepareMatcherPathname(encodedRequestPathname);
196
+ const isStaleDataRequest = options.isDataRequest === true && options.isStaleDataRequest === true;
197
+ const toPageMatcherPathname = (candidate) => candidate !== null && options.isDataRequest && !isStaleDataRequest ? nextDataMiddlewareMatchPathname(candidate) ?? candidate : candidate;
198
+ const encodedMatchPathname = toPageMatcherPathname(prepareMatcherPathname(encodedRequestPathname));
191
199
  let decodedMatchPathname = encodedMatchPathname;
192
200
  try {
193
201
  if (encodedMatchPathname !== null) decodedMatchPathname = decodeURIComponent(encodedMatchPathname);
194
- else if (!options.i18nConfig) decodedMatchPathname = prepareMatcherPathname(decodeURIComponent(encodedRequestPathname));
202
+ else if (!options.i18nConfig) decodedMatchPathname = toPageMatcherPathname(prepareMatcherPathname(decodeURIComponent(encodedRequestPathname)));
195
203
  } catch {}
196
204
  let localeContext;
197
- if (options.i18nConfig && encodedMatchPathname !== null) {
205
+ if (isStaleDataRequest) localeContext = { kind: "next-data" };
206
+ else if (options.i18nConfig && encodedMatchPathname !== null) {
198
207
  const hostname = normalizeHost(options.request.headers.get("host"), requestUrl.hostname);
199
208
  const firstSegment = encodedMatchPathname.split("/", 3)[1];
200
209
  if (firstSegment !== void 0 && options.i18nConfig.locales.some((locale) => locale.toLowerCase() === firstSegment.toLowerCase())) localeContext = { kind: "literal" };
@@ -210,7 +219,7 @@ async function executeMiddleware(options) {
210
219
  const decodedMatches = !encodedMatches && decodedMatchPathname !== null && decodedMatchPathname !== encodedMatchPathname && matchesMiddleware(decodedMatchPathname, matcher, options.request, options.i18nConfig, localeContext);
211
220
  if (!encodedMatches && !decodedMatches) return { continue: true };
212
221
  options.onMatch?.();
213
- const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated);
222
+ const nextRequest = createNextRequest(options.request, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated, options.skipTrailingSlashRedirect);
214
223
  if (options.isDataRequest) Object.defineProperty(nextRequest, "__isData", {
215
224
  enumerable: false,
216
225
  value: true
@@ -248,21 +257,12 @@ async function executeMiddleware(options) {
248
257
  waitUntilPromises
249
258
  };
250
259
  }
251
- if (response.headers.get("x-middleware-next") === "1") {
252
- releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
253
- return {
254
- continue: true,
255
- responseHeaders: collectMiddlewareHeaders(response),
256
- status: response.status !== 200 ? response.status : void 0,
257
- waitUntilPromises
258
- };
259
- }
260
260
  if (response.status >= 300 && response.status < 400) {
261
261
  const location = response.headers.get("Location") ?? response.headers.get("location");
262
262
  if (location) {
263
263
  const relativeLocation = relativizeLocation(location, options.request.url);
264
264
  let normalizedLocation = relativeLocation;
265
- if (options.trailingSlash !== void 0) try {
265
+ if (options.trailingSlash !== void 0 && !options.skipTrailingSlashRedirect) try {
266
266
  const loc = new URL(relativeLocation, options.request.url);
267
267
  if (loc.origin === new URL(options.request.url).origin) {
268
268
  const p = loc.pathname;
@@ -309,11 +309,14 @@ async function executeMiddleware(options) {
309
309
  const rewriteUrl = response.headers.get(MIDDLEWARE_REWRITE_HEADER);
310
310
  if (rewriteUrl) {
311
311
  let rewritePath;
312
+ let rewriteHadBasePath;
312
313
  try {
313
314
  const rewriteParsed = new URL(rewriteUrl, options.request.url);
314
315
  const requestOrigin = new URL(options.request.url).origin;
315
- if (rewriteParsed.origin === requestOrigin) rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
316
- else rewritePath = rewriteParsed.href;
316
+ if (rewriteParsed.origin === requestOrigin) {
317
+ rewriteHadBasePath = !options.basePath || hasBasePath(rewriteParsed.pathname, options.basePath);
318
+ rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
319
+ } else rewritePath = rewriteParsed.href;
317
320
  } catch {
318
321
  rewritePath = rewriteUrl;
319
322
  }
@@ -321,12 +324,22 @@ async function executeMiddleware(options) {
321
324
  return {
322
325
  continue: true,
323
326
  rewriteUrl: rewritePath,
327
+ rewriteHadBasePath,
324
328
  rewriteStatus: response.status !== 200 ? response.status : void 0,
325
329
  responseHeaders: collectMiddlewareHeaders(response),
326
330
  status: response.status !== 200 ? response.status : void 0,
327
331
  waitUntilPromises
328
332
  };
329
333
  }
334
+ if (response.headers.get("x-middleware-next") === "1") {
335
+ releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
336
+ return {
337
+ continue: true,
338
+ responseHeaders: collectMiddlewareHeaders(response),
339
+ status: response.status !== 200 ? response.status : void 0,
340
+ waitUntilPromises
341
+ };
342
+ }
330
343
  releaseMiddlewareRequestBody(nextRequest, waitUntilPromises, response.body === nextRequest.body);
331
344
  return {
332
345
  continue: false,
@@ -44,6 +44,6 @@ export declare function findMiddlewareFile(root: string, fileMatcher: ValidFileM
44
44
  * @param request - The incoming Request object
45
45
  * @returns Middleware result describing what action to take
46
46
  */
47
- export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string): Promise<MiddlewareResult>;
47
+ export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string, isStaleDataRequest?: boolean, skipTrailingSlashRedirect?: boolean): Promise<MiddlewareResult>;
48
48
  //#endregion
49
49
  export { matchPattern, matchesMiddleware };
@@ -77,7 +77,7 @@ function isMiddlewareModule(value) {
77
77
  * @param request - The incoming Request object
78
78
  * @returns Middleware result describing what action to take
79
79
  */
80
- async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname) {
80
+ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname, isStaleDataRequest, skipTrailingSlashRedirect) {
81
81
  const mod = await runner.import(middlewarePath);
82
82
  if (!isMiddlewareModule(mod)) throw new Error(`Middleware module "${middlewarePath}" did not evaluate to an object.`);
83
83
  return runGeneratedMiddleware({
@@ -87,10 +87,12 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
87
87
  i18nConfig,
88
88
  includeErrorDetails: process.env.NODE_ENV !== "production",
89
89
  isDataRequest,
90
+ isStaleDataRequest,
90
91
  isProxy: isProxyFile(middlewarePath),
91
92
  module: mod,
92
93
  normalizedPathname,
93
94
  request,
95
+ skipTrailingSlashRedirect,
94
96
  trailingSlash
95
97
  });
96
98
  }
@@ -29,6 +29,17 @@ export type VinextResponseStageDispatchOptions = {
29
29
  * partition its lookup or stored entry by it.
30
30
  */
31
31
  renderWholeDocument?: boolean;
32
+ /**
33
+ * Regenerate an entry the adapter already stores for this build. Set only
34
+ * from a platform-invoked regeneration callback or a platform cache's
35
+ * conditional revalidation of a stored entry, never from anything a client
36
+ * can send: core then treats the path as admitted and skips
37
+ * request-time admission work such as a Pages `getStaticPaths()` call, as
38
+ * Next.js regenerates a generated path without consulting it. It is not a
39
+ * cache selector: a transport must not partition its lookup or stored entry
40
+ * by it.
41
+ */
42
+ regeneratesStoredEntry?: boolean;
32
43
  /**
33
44
  * Query-free identity of a shared App page GET/HEAD dispatch, supplied only
34
45
  * to adapters that declare `responseStageCacheIdentity: "query-free"` and
@@ -1,17 +1,4 @@
1
1
  //#region src/server/normalize-path.d.ts
2
- export declare function escapePathDelimiters(segment: string, escapeEncoded?: boolean): string;
3
- /**
4
- * Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
5
- * Non-ASCII characters (e.g. %C3%A9 -> e) are decoded, but structural characters
6
- * like %2F (/) %23 (#) %3F (?) %5C (\) are re-encoded after decoding.
7
- *
8
- * This prevents encoded slashes from changing the path structure (e.g.
9
- * /admin%2Fpanel stays as a single segment, not /admin/panel).
10
- *
11
- * Ported from Next.js: packages/next/src/server/lib/router-utils/decode-path-params.ts
12
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts
13
- */
14
- export declare function decodePathParams(pathname: string): string;
15
2
  export declare function isInterceptionMatchedUrlPath(value: string): boolean;
16
3
  /**
17
4
  * Path normalization utility for request handling.
@@ -1,37 +1,4 @@
1
1
  //#region src/server/normalize-path.ts
2
- /**
3
- * Re-encode path delimiter characters that were decoded by decodeURIComponent.
4
- * After decoding a URL segment, characters like / # ? \ need to be re-encoded
5
- * so they don't change the path structure.
6
- *
7
- * Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
8
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
9
- */
10
- const PATH_DELIMITERS_RE = /([/#?])/gi;
11
- const PATH_DELIMITERS_ENCODED_RE = /([/#?]|%(2f|23|3f|5c))/gi;
12
- function escapePathDelimiters(segment, escapeEncoded) {
13
- return segment.replace(escapeEncoded ? PATH_DELIMITERS_ENCODED_RE : PATH_DELIMITERS_RE, (char) => encodeURIComponent(char));
14
- }
15
- /**
16
- * Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
17
- * Non-ASCII characters (e.g. %C3%A9 -> e) are decoded, but structural characters
18
- * like %2F (/) %23 (#) %3F (?) %5C (\) are re-encoded after decoding.
19
- *
20
- * This prevents encoded slashes from changing the path structure (e.g.
21
- * /admin%2Fpanel stays as a single segment, not /admin/panel).
22
- *
23
- * Ported from Next.js: packages/next/src/server/lib/router-utils/decode-path-params.ts
24
- * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts
25
- */
26
- function decodePathParams(pathname) {
27
- return pathname.split("/").map((seg) => {
28
- try {
29
- return escapePathDelimiters(decodeURIComponent(seg), true);
30
- } catch {
31
- return seg;
32
- }
33
- }).join("/");
34
- }
35
2
  function isInterceptionMatchedUrlPath(value) {
36
3
  return value.startsWith("/") && !value.startsWith("//") && !value.includes("?") && !value.includes("#") && !value.includes("\\") && !value.includes("\0");
37
4
  }
@@ -67,4 +34,4 @@ function normalizePath(pathname) {
67
34
  return "/" + resolved.join("/");
68
35
  }
69
36
  //#endregion
70
- export { decodePathParams, escapePathDelimiters, isInterceptionMatchedUrlPath, normalizePath };
37
+ export { isInterceptionMatchedUrlPath, normalizePath };