vinext 1.0.0 → 1.1.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 (263) hide show
  1. package/README.md +72 -1
  2. package/THIRD_PARTY_LICENSES.md +26 -0
  3. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  4. package/dist/build/app-page-static-eligibility.js +82 -0
  5. package/dist/build/lifecycle.js +10 -10
  6. package/dist/build/pages-client-assets-module.js +6 -1
  7. package/dist/build/precompress.js +1 -1
  8. package/dist/build/prerender-paths.js +6 -85
  9. package/dist/build/prerender.d.ts +8 -1
  10. package/dist/build/prerender.js +167 -46
  11. package/dist/build/run-prerender.js +4 -3
  12. package/dist/build/server-entry.d.ts +2 -0
  13. package/dist/build/server-entry.js +7 -1
  14. package/dist/build/server-manifest.d.ts +7 -0
  15. package/dist/build/server-manifest.js +11 -1
  16. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  17. package/dist/cache/cache-adapters-virtual.js +3 -3
  18. package/dist/client/browser-globals.d.ts +4 -0
  19. package/dist/client/browser-globals.js +5 -0
  20. package/dist/client/navigation-runtime.d.ts +11 -1
  21. package/dist/client/navigation-runtime.js +3 -3
  22. package/dist/config/config-matchers.js +3 -1
  23. package/dist/config/dotenv.js +14 -3
  24. package/dist/config/next-config.d.ts +7 -0
  25. package/dist/config/next-config.js +18 -3
  26. package/dist/entries/app-rsc-entry.d.ts +2 -0
  27. package/dist/entries/app-rsc-entry.js +67 -45
  28. package/dist/index.d.ts +4 -1
  29. package/dist/index.js +200 -100
  30. package/dist/init-cloudflare.d.ts +1 -1
  31. package/dist/init-cloudflare.js +6 -6
  32. package/dist/init-platform.js +1 -1
  33. package/dist/init.js +4 -2
  34. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  35. package/dist/plugins/cache-flight-codec.js +68 -0
  36. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  37. package/dist/plugins/commonjs-esm-facade.js +80 -0
  38. package/dist/plugins/commonjs-syntax.d.ts +16 -0
  39. package/dist/plugins/commonjs-syntax.js +22 -0
  40. package/dist/plugins/fonts.d.ts +8 -11
  41. package/dist/plugins/fonts.js +71 -34
  42. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  43. package/dist/plugins/ignore-dynamic-requests.js +54 -3
  44. package/dist/plugins/import-meta-url.js +6 -6
  45. package/dist/plugins/og-asset-ownership.d.ts +2 -0
  46. package/dist/plugins/og-asset-ownership.js +24 -13
  47. package/dist/plugins/og-assets.d.ts +18 -0
  48. package/dist/plugins/og-assets.js +50 -8
  49. package/dist/plugins/og-harfbuzz.js +38 -27
  50. package/dist/plugins/optimize-imports.js +2 -1
  51. package/dist/plugins/require-context.d.ts +1 -0
  52. package/dist/plugins/require-context.js +28 -1
  53. package/dist/plugins/scan-build-css.d.ts +12 -0
  54. package/dist/plugins/scan-build-css.js +39 -0
  55. package/dist/plugins/strip-server-exports.d.ts +2 -0
  56. package/dist/plugins/strip-server-exports.js +3 -1
  57. package/dist/plugins/transform-result.d.ts +18 -0
  58. package/dist/plugins/transform-result.js +23 -2
  59. package/dist/plugins/typeof-window.d.ts +10 -0
  60. package/dist/plugins/typeof-window.js +33 -9
  61. package/dist/routing/app-route-graph.d.ts +0 -1
  62. package/dist/routing/app-route-graph.js +2 -4
  63. package/dist/routing/app-route-ids.d.ts +3 -0
  64. package/dist/routing/app-route-ids.js +6 -0
  65. package/dist/routing/file-matcher.d.ts +1 -0
  66. package/dist/routing/file-matcher.js +1 -1
  67. package/dist/server/api-handler.js +17 -7
  68. package/dist/server/app-bfcache-id.d.ts +1 -0
  69. package/dist/server/app-bfcache-id.js +7 -1
  70. package/dist/server/app-bfcache-identity.js +1 -2
  71. package/dist/server/app-browser-entry.js +15 -9
  72. package/dist/server/app-browser-history-controller.d.ts +2 -0
  73. package/dist/server/app-browser-history-controller.js +8 -0
  74. package/dist/server/app-browser-hydration.d.ts +1 -1
  75. package/dist/server/app-browser-hydration.js +2 -2
  76. package/dist/server/app-browser-interception-context.js +26 -2
  77. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  78. package/dist/server/app-browser-navigation-controller.js +94 -11
  79. package/dist/server/app-browser-state.js +1 -1
  80. package/dist/server/app-browser-stream.d.ts +1 -1
  81. package/dist/server/app-fallback-renderer.d.ts +2 -0
  82. package/dist/server/app-fallback-renderer.js +1 -0
  83. package/dist/server/app-history-state.d.ts +3 -1
  84. package/dist/server/app-history-state.js +9 -5
  85. package/dist/server/app-middleware.js +2 -1
  86. package/dist/server/app-optimistic-routing.js +1 -1
  87. package/dist/server/app-page-boundary-render.d.ts +5 -0
  88. package/dist/server/app-page-boundary-render.js +6 -4
  89. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  90. package/dist/server/app-page-cache-finalizer.js +41 -7
  91. package/dist/server/app-page-cache-render.d.ts +18 -0
  92. package/dist/server/app-page-cache-render.js +65 -44
  93. package/dist/server/app-page-cache.d.ts +5 -0
  94. package/dist/server/app-page-cache.js +27 -7
  95. package/dist/server/app-page-dispatch.d.ts +9 -1
  96. package/dist/server/app-page-dispatch.js +47 -30
  97. package/dist/server/app-page-element-builder.d.ts +7 -0
  98. package/dist/server/app-page-element-builder.js +27 -42
  99. package/dist/server/app-page-execution.d.ts +15 -12
  100. package/dist/server/app-page-execution.js +30 -40
  101. package/dist/server/app-page-head.d.ts +5 -0
  102. package/dist/server/app-page-head.js +9 -3
  103. package/dist/server/app-page-probe.d.ts +0 -109
  104. package/dist/server/app-page-probe.js +3 -136
  105. package/dist/server/app-page-render-identity.d.ts +0 -1
  106. package/dist/server/app-page-render-identity.js +1 -1
  107. package/dist/server/app-page-render-observation.js +2 -2
  108. package/dist/server/app-page-render.d.ts +13 -5
  109. package/dist/server/app-page-render.js +219 -118
  110. package/dist/server/app-page-request.d.ts +0 -1
  111. package/dist/server/app-page-request.js +1 -2
  112. package/dist/server/app-page-response.d.ts +4 -3
  113. package/dist/server/app-page-response.js +4 -3
  114. package/dist/server/app-page-route-wiring.d.ts +17 -5
  115. package/dist/server/app-page-route-wiring.js +55 -33
  116. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  117. package/dist/server/app-page-rsc-render-status.js +54 -0
  118. package/dist/server/app-page-search-params-observation.js +1 -1
  119. package/dist/server/app-page-stream.d.ts +15 -8
  120. package/dist/server/app-page-stream.js +7 -1
  121. package/dist/server/app-page-tracing.js +2 -1
  122. package/dist/server/app-pages-bridge.js +3 -2
  123. package/dist/server/app-render-abort-error.d.ts +3 -0
  124. package/dist/server/app-render-abort-error.js +8 -0
  125. package/dist/server/app-request-stage-dispatch.js +1 -1
  126. package/dist/server/app-request-stage-independent-entry.js +8 -11
  127. package/dist/server/app-response-header-provenance.d.ts +2 -0
  128. package/dist/server/app-response-header-provenance.js +10 -4
  129. package/dist/server/app-route-handler-cache.d.ts +2 -1
  130. package/dist/server/app-route-handler-cache.js +11 -7
  131. package/dist/server/app-route-handler-dispatch.js +11 -4
  132. package/dist/server/app-route-handler-execution.d.ts +9 -1
  133. package/dist/server/app-route-handler-execution.js +28 -13
  134. package/dist/server/app-route-handler-policy.js +2 -2
  135. package/dist/server/app-route-handler-response.d.ts +1 -1
  136. package/dist/server/app-route-handler-response.js +14 -5
  137. package/dist/server/app-router-entry.d.ts +2 -1
  138. package/dist/server/app-router-entry.js +8 -12
  139. package/dist/server/app-rsc-errors.d.ts +0 -1
  140. package/dist/server/app-rsc-errors.js +2 -6
  141. package/dist/server/app-rsc-handler.d.ts +5 -0
  142. package/dist/server/app-rsc-handler.js +229 -102
  143. package/dist/server/app-rsc-request-normalization.d.ts +4 -2
  144. package/dist/server/app-rsc-request-normalization.js +7 -4
  145. package/dist/server/app-rsc-response-finalizer.js +4 -1
  146. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  147. package/dist/server/app-rsc-response-stage.js +6 -4
  148. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  149. package/dist/server/app-rsc-route-matching.js +37 -20
  150. package/dist/server/app-server-action-execution.d.ts +2 -1
  151. package/dist/server/app-server-action-execution.js +1 -1
  152. package/dist/server/app-ssr-entry.d.ts +3 -2
  153. package/dist/server/app-ssr-entry.js +9 -7
  154. package/dist/server/app-ssr-search-params-gate.js +1 -1
  155. package/dist/server/app-ssr-stream.d.ts +6 -1
  156. package/dist/server/app-ssr-stream.js +35 -16
  157. package/dist/server/app-worker-stages.d.ts +2 -2
  158. package/dist/server/app-worker-stages.js +3 -3
  159. package/dist/server/cache-control.js +3 -11
  160. package/dist/server/cacheability-request.js +56 -52
  161. package/dist/server/config-headers.js +3 -1
  162. package/dist/server/dev-server.js +23 -5
  163. package/dist/server/headers.d.ts +16 -0
  164. package/dist/server/headers.js +19 -1
  165. package/dist/server/http-error-responses.js +1 -0
  166. package/dist/server/image-optimization.d.ts +16 -0
  167. package/dist/server/image-optimization.js +26 -6
  168. package/dist/server/isr-cache.d.ts +2 -0
  169. package/dist/server/isr-cache.js +8 -3
  170. package/dist/server/metadata-route-response.js +94 -37
  171. package/dist/server/middleware-matcher-pattern.js +8 -4
  172. package/dist/server/middleware-response-headers.js +2 -0
  173. package/dist/server/multi-stage.d.ts +8 -0
  174. package/dist/server/navigation-planner.js +1 -1
  175. package/dist/server/node-response-signal.d.ts +16 -0
  176. package/dist/server/node-response-signal.js +26 -0
  177. package/dist/server/open-redirect.d.ts +47 -0
  178. package/dist/server/open-redirect.js +74 -1
  179. package/dist/server/pages-api-route.js +8 -2
  180. package/dist/server/pages-asset-tags.d.ts +1 -0
  181. package/dist/server/pages-asset-tags.js +73 -22
  182. package/dist/server/pages-client-assets.d.ts +2 -0
  183. package/dist/server/pages-page-data.d.ts +6 -1
  184. package/dist/server/pages-page-data.js +71 -65
  185. package/dist/server/pages-page-handler.js +50 -15
  186. package/dist/server/pages-page-response.js +3 -1
  187. package/dist/server/pages-request-pipeline.d.ts +2 -1
  188. package/dist/server/pages-request-pipeline.js +20 -5
  189. package/dist/server/pages-request-stage-entry.js +14 -8
  190. package/dist/server/prerender-manifest.d.ts +6 -0
  191. package/dist/server/prerender-manifest.js +1 -1
  192. package/dist/server/prod-server.d.ts +8 -7
  193. package/dist/server/prod-server.js +152 -78
  194. package/dist/server/request-pipeline.d.ts +20 -8
  195. package/dist/server/request-pipeline.js +21 -7
  196. package/dist/server/response-compression.d.ts +43 -0
  197. package/dist/server/response-compression.js +111 -0
  198. package/dist/server/response-stage-cacheability.js +1 -1
  199. package/dist/server/seed-cache.js +10 -7
  200. package/dist/server/socket-error-backstop.d.ts +14 -0
  201. package/dist/server/socket-error-backstop.js +27 -4
  202. package/dist/server/worker-revalidation-context.d.ts +2 -1
  203. package/dist/server/worker-revalidation-context.js +1 -0
  204. package/dist/server/workers-tracing.d.ts +17 -0
  205. package/dist/server/workers-tracing.js +48 -8
  206. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  207. package/dist/shims/cache-adapter-instantiate.js +118 -0
  208. package/dist/shims/cache-callable-runtime.d.ts +2 -40
  209. package/dist/shims/cache-callable-runtime.js +50 -168
  210. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  211. package/dist/shims/cache-flight-arguments.js +56 -0
  212. package/dist/shims/cache-request-state.d.ts +18 -14
  213. package/dist/shims/cache-request-state.js +33 -21
  214. package/dist/shims/cache-runtime.d.ts +6 -19
  215. package/dist/shims/cache-runtime.js +168 -226
  216. package/dist/shims/cache.d.ts +3 -2
  217. package/dist/shims/cache.js +8 -5
  218. package/dist/shims/cacheability-classification.d.ts +8 -1
  219. package/dist/shims/cacheability-classification.js +2 -2
  220. package/dist/shims/cdn-cache.d.ts +10 -1
  221. package/dist/shims/cdn-cache.js +16 -9
  222. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  223. package/dist/shims/error-boundary-navigation.js +19 -1
  224. package/dist/shims/error-boundary.d.ts +10 -10
  225. package/dist/shims/error-boundary.js +26 -7
  226. package/dist/shims/error.d.ts +4 -18
  227. package/dist/shims/error.js +17 -10
  228. package/dist/shims/error.react-server.d.ts +4 -6
  229. package/dist/shims/error.react-server.js +4 -1
  230. package/dist/shims/fetch-cache.d.ts +6 -0
  231. package/dist/shims/fetch-cache.js +23 -4
  232. package/dist/shims/form.js +1 -1
  233. package/dist/shims/headers.d.ts +19 -69
  234. package/dist/shims/headers.js +82 -183
  235. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  236. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  237. package/dist/shims/internal/cache-context-accessor.d.ts +16 -0
  238. package/dist/shims/internal/cache-context-accessor.js +15 -0
  239. package/dist/shims/internal/headers-state.d.ts +73 -0
  240. package/dist/shims/internal/headers-state.js +157 -0
  241. package/dist/shims/metadata.js +3 -3
  242. package/dist/shims/navigation-errors.d.ts +6 -0
  243. package/dist/shims/navigation-errors.js +11 -1
  244. package/dist/shims/navigation.d.ts +7 -0
  245. package/dist/shims/navigation.js +16 -6
  246. package/dist/shims/request-context.d.ts +9 -0
  247. package/dist/shims/request-state-types.d.ts +2 -1
  248. package/dist/shims/root-params.js +1 -1
  249. package/dist/shims/server.js +52 -2
  250. package/dist/shims/thenable-params.d.ts +4 -0
  251. package/dist/shims/thenable-params.js +25 -3
  252. package/dist/shims/unified-request-context.d.ts +1 -1
  253. package/dist/shims/unified-request-context.js +1 -0
  254. package/dist/utils/encode-cache-tag.d.ts +1 -0
  255. package/dist/utils/encode-cache-tag.js +11 -1
  256. package/dist/utils/mdx-scan.d.ts +5 -0
  257. package/dist/utils/mdx-scan.js +8 -1
  258. package/dist/utils/regex-safety.d.ts +10 -0
  259. package/dist/utils/regex-safety.js +70 -18
  260. package/dist/utils/vite-cli-invocation.js +8 -1
  261. package/package.json +8 -4
  262. package/dist/server/metadata-route-cache-policy.d.ts +0 -5
  263. package/dist/server/metadata-route-cache-policy.js +0 -6
@@ -5,30 +5,33 @@ import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix } from
5
5
  import { StaticFileCache, contentTypeForPath, etagFromFilenameHash } from "./static-file-cache.js";
6
6
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
7
7
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
8
- import { VINEXT_PRERENDER_RENDER_ERROR_HEADER } from "./headers.js";
8
+ import { VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "./headers.js";
9
9
  import { normalizePath } from "./normalize-path.js";
10
10
  import { notFoundResponse } from "./http-error-responses.js";
11
- import { isOpenRedirectShaped } from "./open-redirect.js";
11
+ import { isOpenRedirectShaped, sendRepeatedSlashRedirect } from "./open-redirect.js";
12
12
  import { readStaticFileSignal } from "./static-file-signal.js";
13
13
  import { canonicalizeRequestPathname, filterInternalHeaders } from "./request-pipeline.js";
14
+ import { buildCacheStateHeaders } from "./cache-headers.js";
14
15
  import { parseHttpDate } from "./http-date.js";
15
16
  import { evaluateStaticPreconditions } from "./http-conditional.js";
16
17
  import { buildNextDataNotFoundResponse, encodeUrlParserIgnoredCharacters, isNextDataPathname, normalizeNextDataPagePathname, parseNextDataPathname, shouldAddTrailingSlashToPagesDataPath, urlParserCreatesPagesDataPath } from "./pages-data-route.js";
17
18
  import { setPagesClientAssets } from "./pages-client-assets.js";
18
19
  import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
19
- import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
20
+ import { signalFromNodeResponse } from "./node-response-signal.js";
21
+ import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_RESPONSE_CACHE_STATE, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
20
22
  import { installSocketErrorBackstop } from "./socket-error-backstop.js";
21
23
  import { collectInlineCssManifest } from "../build/inline-css.js";
22
24
  import { readTrustedRevalidationHostname } from "./revalidation-host.js";
23
25
  import { traceFrameworkRequest } from "./request-tracing.js";
24
- import { finalizeMissingStaticAssetResponse, mergeHeaders } from "./worker-utils.js";
26
+ import { mergeHeaders } from "./worker-utils.js";
25
27
  import { runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
26
28
  import { computeClientRuntimeMetadata } from "../utils/client-runtime-metadata.js";
27
- import { readPrerenderSecret } from "../build/server-manifest.js";
29
+ import { readPrerenderSecret, readServerCompress } from "../build/server-manifest.js";
28
30
  import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
29
31
  import { registerPrerenderCloudflareLoader } from "../build/prerender-cloudflare-loader.js";
30
32
  import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
31
33
  import { negotiateEncoding, parseAcceptedEncodings, selectContentEncoding } from "./accept-encoding.js";
34
+ import { COMPRESS_THRESHOLD, hasNoTransform, isCompressibleContentType, parseContentLengthHeader, resolveResponseCompression } from "./response-compression.js";
32
35
  import { ifRangeAllowsRange, parseByteRange } from "./http-range.js";
33
36
  import { createRequire } from "node:module";
34
37
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -192,25 +195,6 @@ function readNodeStream(req) {
192
195
  }
193
196
  });
194
197
  }
195
- /** Content types that benefit from compression. */
196
- const COMPRESSIBLE_TYPES = /* @__PURE__ */ new Set([
197
- "text/html",
198
- "text/css",
199
- "text/plain",
200
- "text/xml",
201
- "text/javascript",
202
- "application/javascript",
203
- "application/json",
204
- "application/xml",
205
- "application/xhtml+xml",
206
- "application/rss+xml",
207
- "application/atom+xml",
208
- "image/svg+xml",
209
- "application/manifest+json",
210
- "application/wasm"
211
- ]);
212
- /** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
213
- const COMPRESS_THRESHOLD = 1024;
214
198
  /**
215
199
  * Create a compression stream for the given encoding.
216
200
  */
@@ -276,12 +260,14 @@ const NO_BODY_RESPONSE_STATUSES = /* @__PURE__ */ new Set([
276
260
  205,
277
261
  304
278
262
  ]);
263
+ const IMAGE_REPRESENTATION_HEADERS = buildCacheStateHeaders(IMAGE_RESPONSE_CACHE_STATE);
279
264
  const OMIT_BODY_HEADERS = /* @__PURE__ */ new Set(["content-length", "content-type"]);
280
265
  const OMIT_STATIC_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
281
266
  "content-encoding",
282
267
  "content-length",
283
268
  "content-range",
284
- "content-type"
269
+ "content-type",
270
+ VINEXT_PRERENDER_SPECIAL_ERROR_HEADER
285
271
  ]);
286
272
  function omitHeadersCaseInsensitive(headersRecord, targets) {
287
273
  const filtered = {};
@@ -291,6 +277,23 @@ function omitHeadersCaseInsensitive(headersRecord, targets) {
291
277
  }
292
278
  return filtered;
293
279
  }
280
+ function readHeaderCaseInsensitive(headersRecord, name) {
281
+ const key = Object.keys(headersRecord).find((entry) => entry.toLowerCase() === name);
282
+ if (key === void 0) return void 0;
283
+ const value = headersRecord[key];
284
+ return Array.isArray(value) ? value.join(", ") : value;
285
+ }
286
+ /** Remove one field name from a Vary header, dropping the header if it empties. */
287
+ function omitVaryToken(headers, token) {
288
+ const varyKey = Object.keys(headers).find((key) => key.toLowerCase() === "vary");
289
+ if (varyKey === void 0) return headers;
290
+ const rawVary = headers[varyKey];
291
+ const remaining = (Array.isArray(rawVary) ? rawVary.join(",") : rawVary).split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0 && entry.toLowerCase() !== token.toLowerCase());
292
+ const result = { ...headers };
293
+ delete result[varyKey];
294
+ if (remaining.length > 0) result[varyKey] = remaining.join(", ");
295
+ return result;
296
+ }
294
297
  function mergeVaryHeader(headers, value) {
295
298
  const merged = { ...headers };
296
299
  const existingKey = Object.keys(merged).find((key) => key.toLowerCase() === "vary");
@@ -337,6 +340,32 @@ function cancelResponseBody(response) {
337
340
  if (!body || body.locked) return;
338
341
  body.cancel().catch(() => {});
339
342
  }
343
+ /**
344
+ * Buffer a response body, cancelling it if the client disconnects first so a
345
+ * stalled stream does not outlive the connection. Resolves null when aborted.
346
+ */
347
+ async function bufferResponseBodyUntilAborted(response, signal) {
348
+ const body = response.body;
349
+ if (!body) return Buffer.alloc(0);
350
+ const reader = body.getReader();
351
+ const cancel = () => {
352
+ reader.cancel(signal.reason).catch(() => {});
353
+ };
354
+ if (signal.aborted) cancel();
355
+ else signal.addEventListener("abort", cancel, { once: true });
356
+ const chunks = [];
357
+ try {
358
+ while (true) {
359
+ const result = await reader.read();
360
+ if (result.done) break;
361
+ chunks.push(result.value);
362
+ }
363
+ } finally {
364
+ signal.removeEventListener("abort", cancel);
365
+ reader.releaseLock();
366
+ }
367
+ return signal.aborted ? null : Buffer.concat(chunks);
368
+ }
340
369
  function isVinextStreamedHtmlResponse(response) {
341
370
  return response.__vinextStreamedHtmlResponse === true;
342
371
  }
@@ -370,24 +399,23 @@ function mergeWebResponse(middlewareHeaders, response, statusOverride) {
370
399
  */
371
400
  function sendCompressed(req, res, body, contentType, statusCode, extraHeaders = {}, compress = true, statusText) {
372
401
  const buf = typeof body === "string" ? Buffer.from(body) : body;
373
- const baseType = contentType.split(";")[0].trim();
374
- const varyByEncoding = compress && COMPRESSIBLE_TYPES.has(baseType);
375
- const encoding = compress ? negotiateEncoding(req) : "identity";
376
402
  const headersWithoutBodyHeaders = omitHeadersCaseInsensitive(extraHeaders, OMIT_BODY_HEADERS);
403
+ const { varyAcceptEncoding: varyByEncoding, encoding } = resolveResponseCompression(req, compress, {
404
+ contentType,
405
+ cacheControl: readHeaderCaseInsensitive(extraHeaders, "cache-control"),
406
+ contentEncoding: readHeaderCaseInsensitive(extraHeaders, "content-encoding"),
407
+ contentLength: buf.length
408
+ });
377
409
  const writeHead = (headers, responseStatus = statusCode, responseStatusText = statusText) => {
378
410
  if (responseStatusText) res.writeHead(responseStatus, responseStatusText, headers);
379
411
  else res.writeHead(responseStatus, headers);
380
412
  };
381
- if (encoding !== "identity" && varyByEncoding && buf.length >= 1024) {
413
+ if (encoding !== "identity") {
382
414
  writeHead(mergeVaryHeader({
383
415
  ...headersWithoutBodyHeaders,
384
416
  "Content-Type": contentType,
385
417
  "Content-Encoding": encoding
386
418
  }, "Accept-Encoding"));
387
- if (req.method === "HEAD") {
388
- res.end();
389
- return;
390
- }
391
419
  const compressor = createCompressor(encoding);
392
420
  compressor.end(buf);
393
421
  pipeline(compressor, res, () => {});
@@ -415,8 +443,12 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
415
443
  *
416
444
  * Without a cache, falls back to async filesystem probing (still non-blocking,
417
445
  * unlike the old sync existsSync/statSync approach).
446
+ *
447
+ * `extraHeaders` go on every response for the file. `representationHeaders` go
448
+ * only on responses that carry the file's bytes (200 and 206), never on a 304,
449
+ * 412, 416 or 405.
418
450
  */
419
- async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode) {
451
+ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode, representationHeaders) {
420
452
  if (pathname === "/") return false;
421
453
  const responseStatus = statusCode ?? 200;
422
454
  const omitBody = isNoBodyResponseStatus(responseStatus);
@@ -442,23 +474,27 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
442
474
  sendStaticMethodNotAllowed(res, extraHeaders);
443
475
  return true;
444
476
  }
445
- const rawAe = compress ? req.headers["accept-encoding"] : void 0;
477
+ const encodingAllowed = compress && !hasNoTransform((extraHeaders && readHeaderCaseInsensitive(extraHeaders, "cache-control")) ?? entry.original.headers["Cache-Control"]);
478
+ const rawAe = encodingAllowed ? req.headers["accept-encoding"] : void 0;
446
479
  const parsed = typeof rawAe === "string" ? parseAcceptedEncodings(rawAe) : void 0;
447
480
  const availableVariants = [
448
481
  ...entry.zst ? ["zstd"] : [],
449
482
  ...entry.br ? ["br"] : [],
450
483
  ...entry.gz ? ["gzip"] : []
451
484
  ];
452
- const variesByEncoding = compress && availableVariants.length > 0;
485
+ const variesByEncoding = encodingAllowed && availableVariants.length > 0;
453
486
  const selected = parsed ? selectContentEncoding(parsed, availableVariants) : "identity";
454
487
  const variant = selected === "zstd" ? entry.zst : selected === "br" ? entry.br : selected === "gzip" ? entry.gz : entry.original;
488
+ const notModifiedBaseHeaders = encodingAllowed ? entry.notModifiedHeaders : omitVaryToken(entry.notModifiedHeaders, "Accept-Encoding");
489
+ const originalHeaders = encodingAllowed ? entry.original.headers : omitVaryToken(entry.original.headers, "Accept-Encoding");
490
+ const variantHeaders = variant === entry.original ? originalHeaders : variant.headers;
455
491
  const validators = extraHeaders ? resolveStaticValidators(entry.etag, entry.mtimeMs, extraHeaders) : void 0;
456
492
  const effectiveEtag = validators ? validators.etag : entry.etag;
457
493
  const effectiveMtimeMs = validators ? validators.mtimeMs : entry.mtimeMs;
458
494
  const preconditionResult = responseStatus === 200 ? evaluateRequestPreconditions(req, effectiveEtag, effectiveMtimeMs) : "proceed";
459
495
  if (preconditionResult === "precondition-failed") {
460
496
  res.writeHead(412, {
461
- ...entry.notModifiedHeaders,
497
+ ...notModifiedBaseHeaders,
462
498
  ...extraHeaders,
463
499
  "Content-Type": entry.original.headers["Content-Type"],
464
500
  "Accept-Ranges": "bytes"
@@ -468,10 +504,10 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
468
504
  }
469
505
  if (preconditionResult === "not-modified") {
470
506
  const notModifiedHeaders = variesByEncoding ? mergeVaryHeader({
471
- ...entry.notModifiedHeaders,
507
+ ...notModifiedBaseHeaders,
472
508
  ...extraHeaders
473
509
  }, "Accept-Encoding") : {
474
- ...entry.notModifiedHeaders,
510
+ ...notModifiedBaseHeaders,
475
511
  ...extraHeaders
476
512
  };
477
513
  if (selected !== "identity") notModifiedHeaders["Content-Encoding"] = selected;
@@ -482,7 +518,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
482
518
  const range = resolveRequestedRange(requestRange, ifRange, entry.original.size, effectiveEtag ?? "", effectiveMtimeMs, responseStatus);
483
519
  if (range.kind === "unsatisfiable") {
484
520
  res.writeHead(416, {
485
- ...entry.notModifiedHeaders,
521
+ ...notModifiedBaseHeaders,
486
522
  ...extraHeaders,
487
523
  "Content-Type": entry.original.headers["Content-Type"],
488
524
  "Accept-Ranges": "bytes",
@@ -494,8 +530,9 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
494
530
  if (range.kind === "range") {
495
531
  const length = range.end - range.start + 1;
496
532
  const rangeHeaders = {
497
- ...entry.original.headers,
533
+ ...originalHeaders,
498
534
  ...extraHeaders,
535
+ ...representationHeaders,
499
536
  "Accept-Ranges": "bytes",
500
537
  "Content-Length": String(length),
501
538
  "Content-Range": `bytes ${range.start}-${range.end}/${entry.original.size}`
@@ -510,8 +547,9 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
510
547
  return true;
511
548
  }
512
549
  const responseHeaders = {
513
- ...variant.headers,
550
+ ...variantHeaders,
514
551
  ...extraHeaders,
552
+ ...representationHeaders,
515
553
  "Accept-Ranges": "bytes"
516
554
  };
517
555
  res.writeHead(responseStatus, variesByEncoding ? mergeVaryHeader(responseHeaders, "Accept-Encoding") : responseHeaders);
@@ -549,8 +587,6 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
549
587
  const isHashed = pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`);
550
588
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
551
589
  const etag = isHashed && etagFromFilenameHash(resolved.path, ext) || `W/"${resolved.size}-${Math.floor(resolved.mtimeMs / 1e3)}"`;
552
- const baseType = ct.split(";")[0].trim();
553
- const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
554
590
  const baseHeaders = {
555
591
  "Content-Type": ct,
556
592
  "Cache-Control": cacheControl,
@@ -559,7 +595,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
559
595
  "Accept-Ranges": "bytes",
560
596
  ...extraHeaders
561
597
  };
562
- const encoding = isCompressible ? negotiateEncoding(req) : "identity";
598
+ const { varyAcceptEncoding: isCompressible, encoding } = resolveResponseCompression(req, compress, {
599
+ contentType: ct,
600
+ cacheControl: (extraHeaders && readHeaderCaseInsensitive(extraHeaders, "cache-control")) ?? cacheControl,
601
+ contentEncoding: extraHeaders && readHeaderCaseInsensitive(extraHeaders, "content-encoding"),
602
+ contentLength: resolved.size
603
+ });
563
604
  const validators = extraHeaders ? resolveStaticValidators(etag, resolved.mtimeMs, extraHeaders) : void 0;
564
605
  const effectiveEtag = validators ? validators.etag : etag;
565
606
  const effectiveMtimeMs = validators ? validators.mtimeMs : resolved.mtimeMs;
@@ -595,6 +636,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
595
636
  const length = range.end - range.start + 1;
596
637
  const rangeHeaders = {
597
638
  ...baseHeaders,
639
+ ...representationHeaders,
598
640
  "Content-Length": String(length),
599
641
  "Content-Range": `bytes ${range.start}-${range.end}/${resolved.size}`
600
642
  };
@@ -609,6 +651,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
609
651
  if (isCompressible && encoding !== "identity") {
610
652
  res.writeHead(responseStatus, mergeVaryHeader({
611
653
  ...baseHeaders,
654
+ ...representationHeaders,
612
655
  "Content-Encoding": encoding
613
656
  }, "Accept-Encoding"));
614
657
  if (omitBody || req.method === "HEAD") {
@@ -626,6 +669,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
626
669
  }
627
670
  const identityHeaders = {
628
671
  ...baseHeaders,
672
+ ...representationHeaders,
629
673
  "Content-Length": String(resolved.size)
630
674
  };
631
675
  res.writeHead(responseStatus, isCompressible ? mergeVaryHeader(identityHeaders, "Accept-Encoding") : identityHeaders);
@@ -734,7 +778,7 @@ async function statIfFile(filePath) {
734
778
  * When `urlOverride` is provided, it is used as the path + query string
735
779
  * instead of `req.url`.
736
780
  */
737
- function nodeToWebRequest(req, urlOverride, prerenderSecret, i18nConfig, authorizeOnDemandRevalidate) {
781
+ function nodeToWebRequest(req, urlOverride, prerenderSecret, i18nConfig, authorizeOnDemandRevalidate, signal) {
738
782
  const proto = resolveRequestProtocol(req);
739
783
  const rawHeaders = nodeHeadersToWebHeaders(req.headers);
740
784
  const revalidationHostname = readTrustedRevalidationHostname(rawHeaders, i18nConfig, authorizeOnDemandRevalidate);
@@ -751,9 +795,10 @@ function nodeToWebRequest(req, urlOverride, prerenderSecret, i18nConfig, authori
751
795
  const hasBody = method !== "GET" && method !== "HEAD";
752
796
  const init = {
753
797
  method,
754
- headers
798
+ headers,
799
+ signal
755
800
  };
756
- if (hasBody) {
801
+ if (hasBody && !signal?.aborted) {
757
802
  init.body = readNodeStream(req);
758
803
  init.duplex = "half";
759
804
  }
@@ -776,22 +821,23 @@ async function sendWebResponse(webResponse, req, res, compress) {
776
821
  if (existing !== void 0) nodeHeaders[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
777
822
  else nodeHeaders[key] = value;
778
823
  });
779
- const alreadyEncoded = webResponse.headers.get("content-encoding") !== null;
780
- if (!webResponse.body) {
781
- writeHead(nodeHeaders);
782
- res.end();
783
- return;
784
- }
785
- const baseType = (webResponse.headers.get("content-type") ?? "").split(";")[0].trim();
786
- const varyByEncoding = compress && !alreadyEncoded && COMPRESSIBLE_TYPES.has(baseType);
787
- const encoding = compress && !alreadyEncoded ? negotiateEncoding(req) : "identity";
788
- const shouldCompress = encoding !== "identity" && COMPRESSIBLE_TYPES.has(baseType);
824
+ const { varyAcceptEncoding: varyByEncoding, encoding } = resolveResponseCompression(req, compress, {
825
+ contentType: webResponse.headers.get("content-type"),
826
+ cacheControl: webResponse.headers.get("cache-control"),
827
+ contentEncoding: webResponse.headers.get("content-encoding"),
828
+ contentLength: webResponse.body ? parseContentLengthHeader(webResponse.headers.get("content-length")) : 0
829
+ });
830
+ const shouldCompress = encoding !== "identity";
789
831
  if (shouldCompress) {
790
832
  delete nodeHeaders["content-length"];
791
- delete nodeHeaders["Content-Length"];
833
+ delete nodeHeaders["content-encoding"];
792
834
  nodeHeaders["Content-Encoding"] = encoding;
793
835
  }
794
836
  writeHead(varyByEncoding ? mergeVaryHeader(nodeHeaders, "Accept-Encoding") : nodeHeaders);
837
+ if (!webResponse.body) {
838
+ res.end();
839
+ return;
840
+ }
795
841
  if (req.method === "HEAD") {
796
842
  cancelResponseBody(webResponse);
797
843
  res.end();
@@ -861,6 +907,7 @@ async function startProdServer(options = {}) {
861
907
  port,
862
908
  host,
863
909
  clientDir,
910
+ serverDir,
864
911
  serverEntryPath,
865
912
  compress,
866
913
  purpose,
@@ -877,6 +924,23 @@ function createNodeExecutionContext(trustedRevalidateOrigin) {
877
924
  trustedRevalidateOrigin
878
925
  };
879
926
  }
927
+ /**
928
+ * Sets the prerender special-error marker only from the page's recorded
929
+ * special error, which middleware, route handlers and external rewrites can't
930
+ * reach, and drops one they set.
931
+ */
932
+ function markPrerenderSpecialError(response, marker) {
933
+ const trusted = marker !== void 0 && marker.status === response.status;
934
+ if (!trusted && !response.headers.has("x-vinext-prerender-special-error")) return response;
935
+ const headers = new Headers(response.headers);
936
+ headers.delete(VINEXT_PRERENDER_SPECIAL_ERROR_HEADER);
937
+ if (trusted) headers.set(VINEXT_PRERENDER_SPECIAL_ERROR_HEADER, JSON.stringify(marker.headers));
938
+ return new Response(response.body, {
939
+ headers,
940
+ status: response.status,
941
+ statusText: response.statusText
942
+ });
943
+ }
880
944
  function resolveTrustedNodeRevalidateOrigin(req, configuredHost, configuredPort) {
881
945
  const port = req.socket.localPort ?? configuredPort;
882
946
  return `http://${normalizeInternalFetchHost(configuredHost)}:${port}`;
@@ -1008,8 +1072,9 @@ function installPagesClientAssets(options) {
1008
1072
  * 4. Stream the Web Response back (with optional compression)
1009
1073
  */
1010
1074
  async function startAppRouterServer(options) {
1011
- const { port, host, clientDir, serverDir, rscEntryPath, compress, purpose, silent } = options;
1075
+ const { port, host, clientDir, serverDir, rscEntryPath, compress: compressOption, purpose, silent } = options;
1012
1076
  const prerenderSecret = readPrerenderSecret(serverDir);
1077
+ const compress = compressOption && readServerCompress(serverDir);
1013
1078
  const rscModule = await importServerEntryModule(rscEntryPath);
1014
1079
  const rscEntryRequire = createServerEntryRequire(rscEntryPath);
1015
1080
  const rscHandler = resolveAppRouterHandler(rscModule.default);
@@ -1048,6 +1113,7 @@ async function startAppRouterServer(options) {
1048
1113
  const handleRequestImpl = async (req, res) => {
1049
1114
  const rawUrl = req.url ?? "/";
1050
1115
  const rawPathname = rawUrl.split("?")[0];
1116
+ if (sendRepeatedSlashRedirect(rawUrl, res)) return;
1051
1117
  if (isOpenRedirectShaped(rawPathname)) {
1052
1118
  res.writeHead(404);
1053
1119
  res.end("This page could not be found");
@@ -1070,12 +1136,10 @@ async function startAppRouterServer(options) {
1070
1136
  return;
1071
1137
  }
1072
1138
  }
1073
- let missingBuildAsset = false;
1074
1139
  {
1075
1140
  const assetLookupPath = resolveAppRouterAssetPath(pathname, appAssetPathPrefix, appRouterAssetPrefix);
1076
1141
  if (assetLookupPath) {
1077
1142
  if (await tryServeStatic(req, res, clientDir, assetLookupPath, compress, staticCache)) return;
1078
- missingBuildAsset = true;
1079
1143
  }
1080
1144
  }
1081
1145
  if (isImageOptimizationPath(pathname)) {
@@ -1097,16 +1161,21 @@ async function startAppRouterServer(options) {
1097
1161
  "X-Content-Type-Options": "nosniff",
1098
1162
  "Content-Disposition": imageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1099
1163
  };
1100
- if (await tryServeStatic(req, res, clientDir, params.imageUrl, false, staticCache, imageSecurityHeaders)) return;
1164
+ if (await tryServeStatic(req, res, clientDir, params.imageUrl, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS)) return;
1101
1165
  res.writeHead(404);
1102
1166
  res.end("Image not found");
1103
1167
  return;
1104
1168
  }
1105
1169
  try {
1106
- const request = nodeToWebRequest(req, rawUrl, prerenderSecret, appRouterI18nConfig, appRouterAuthorizeOnDemandRevalidate);
1107
- const response = await rscHandler(request, createNodeExecutionContext(resolveTrustedNodeRevalidateOrigin(req, host, port)));
1108
- if (missingBuildAsset && response.status === 404) {
1109
- await sendWebResponse(finalizeMissingStaticAssetResponse(response, true), req, res, compress);
1170
+ const request = nodeToWebRequest(req, rawUrl, prerenderSecret, appRouterI18nConfig, appRouterAuthorizeOnDemandRevalidate, signalFromNodeResponse(res));
1171
+ const ctx = createNodeExecutionContext(resolveTrustedNodeRevalidateOrigin(req, host, port));
1172
+ const recorded = {};
1173
+ if (purpose === "prerender") ctx.recordPrerenderSpecialError = (marker) => {
1174
+ recorded.marker = marker;
1175
+ };
1176
+ const response = await rscHandler(request, ctx);
1177
+ if (request.signal.aborted) {
1178
+ cancelResponseBody(response);
1110
1179
  return;
1111
1180
  }
1112
1181
  const staticFileSignal = readStaticFileSignal(response);
@@ -1124,7 +1193,7 @@ async function startAppRouterServer(options) {
1124
1193
  await sendWebResponse(notFoundResponse({ headers: toWebHeaders(staticResponseHeaders) }), req, res, compress);
1125
1194
  return;
1126
1195
  }
1127
- await sendWebResponse(response, req, res, compress);
1196
+ await sendWebResponse(purpose === "prerender" ? markPrerenderSpecialError(response, recorded.marker) : response, req, res, compress);
1128
1197
  } catch (e) {
1129
1198
  console.error("[vinext] Server error:", e);
1130
1199
  if (!res.headersSent) {
@@ -1155,7 +1224,7 @@ async function startAppRouterServer(options) {
1155
1224
  },
1156
1225
  getStatus: () => res.statusCode,
1157
1226
  headers,
1158
- isRsc: new URL(target, "http://localhost").pathname.endsWith(".rsc") || headers.get("RSC") === "1",
1227
+ isRsc: target.split("?", 1)[0].endsWith(".rsc") || headers.get("RSC") === "1",
1159
1228
  method: req.method ?? "GET",
1160
1229
  target
1161
1230
  });
@@ -1201,7 +1270,7 @@ function readPagesServerEntryPageRoutes(value) {
1201
1270
  * - vinextConfig — embedded next.config.js settings
1202
1271
  */
1203
1272
  async function startPagesRouterServer(options) {
1204
- const { port, host, clientDir, serverEntryPath, compress, purpose, silent } = options;
1273
+ const { port, host, clientDir, serverDir, serverEntryPath, compress: compressOption, purpose, silent } = options;
1205
1274
  const serverEntry = await importServerEntryModule(serverEntryPath);
1206
1275
  const serverEntryRequire = createServerEntryRequire(serverEntryPath);
1207
1276
  const { renderPage, handleApiRoute: handleApi, authorizeOnDemandRevalidate, runMiddleware, vinextConfig, buildId: pagesBuildId } = serverEntry;
@@ -1211,6 +1280,7 @@ async function startPagesRouterServer(options) {
1211
1280
  const hasMiddleware = serverEntry.hasMiddleware === true;
1212
1281
  const pageRoutes = readPagesServerEntryPageRoutes(serverEntry.pageRoutes);
1213
1282
  const prerenderSecret = readPrerenderSecret(path.dirname(serverEntryPath));
1283
+ const compress = compressOption && readServerCompress(serverDir);
1214
1284
  const basePath = vinextConfig?.basePath ?? "";
1215
1285
  const assetPrefix = vinextConfig?.assetPrefix ?? "";
1216
1286
  const pagesAssetPathPrefix = assetPrefixPathname(assetPrefix);
@@ -1267,6 +1337,7 @@ async function startPagesRouterServer(options) {
1267
1337
  const handleRequestImpl = async (req, res) => {
1268
1338
  const rawUrl = req.url ?? "/";
1269
1339
  const rawPagesPathnameBeforeNormalize = rawUrl.split("?")[0];
1340
+ if (sendRepeatedSlashRedirect(rawUrl, res)) return;
1270
1341
  if (isOpenRedirectShaped(rawPagesPathnameBeforeNormalize)) {
1271
1342
  res.writeHead(404);
1272
1343
  res.end("This page could not be found");
@@ -1316,7 +1387,6 @@ async function startPagesRouterServer(options) {
1316
1387
  return;
1317
1388
  }
1318
1389
  const pagesAssetLookup = resolveAppRouterAssetPath(pathname, pagesAssetPathPrefix, assetPrefix);
1319
- const missingBuildAsset = pagesAssetLookup !== null;
1320
1390
  if (pagesAssetLookup) {
1321
1391
  if (await tryServeStatic(req, res, clientDir, pagesAssetLookup, compress, staticCache)) return;
1322
1392
  }
@@ -1373,15 +1443,18 @@ async function startPagesRouterServer(options) {
1373
1443
  const reqHeaders = filterInternalHeaders(rawReqHeaders);
1374
1444
  if (revalidationHostname) reqHeaders.set("host", revalidationHostname);
1375
1445
  const method = req.method ?? "GET";
1376
- const hasBody = method !== "GET" && method !== "HEAD";
1446
+ const signal = signalFromNodeResponse(res);
1447
+ const hasBody = method !== "GET" && method !== "HEAD" && !signal.aborted;
1377
1448
  const webRequest = new Request(`${protocol}://${revalidationHostname ?? hostHeader}${url}`, {
1378
1449
  method,
1379
1450
  headers: reqHeaders,
1451
+ signal,
1380
1452
  body: hasBody ? readNodeStream(req) : void 0,
1381
1453
  duplex: hasBody ? "half" : void 0
1382
1454
  });
1383
1455
  const middlewareRequest = isDataReq && skipProxyUrlNormalize ? new Request(new URL(originalRenderUrl, webRequest.url), webRequest) : void 0;
1384
1456
  const deps = {
1457
+ assetPrefix,
1385
1458
  basePath,
1386
1459
  trailingSlash,
1387
1460
  i18nConfig,
@@ -1404,7 +1477,7 @@ async function startPagesRouterServer(options) {
1404
1477
  renderPage: typeof renderPage === "function" ? (request, resolvedUrl, options, stagedHeaders) => renderPage(request, resolvedUrl, ssrManifest, void 0, stagedHeaders, {
1405
1478
  ...options,
1406
1479
  originalUrl: originalRenderUrl
1407
- }) : null,
1480
+ }, stagedHeaders?.has("Cache-Control") ? new Headers({ "Cache-Control": stagedHeaders.get("Cache-Control") }) : void 0) : null,
1408
1481
  handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node") : null,
1409
1482
  serveFilesystemRoute: async (requestPathname, stagedHeaders, phase, resolvedUrl) => {
1410
1483
  if (isImageOptimizationPath(requestPathname)) {
@@ -1427,7 +1500,7 @@ async function startPagesRouterServer(options) {
1427
1500
  "X-Content-Type-Options": "nosniff",
1428
1501
  "Content-Disposition": pagesImageConfig?.contentDispositionType === "attachment" ? "attachment" : "inline"
1429
1502
  };
1430
- if (await tryServeStatic(req, res, clientDir, params.imageUrl, false, staticCache, imageSecurityHeaders)) return true;
1503
+ if (await tryServeStatic(req, res, clientDir, params.imageUrl, false, staticCache, imageSecurityHeaders, void 0, IMAGE_REPRESENTATION_HEADERS)) return true;
1431
1504
  res.writeHead(404);
1432
1505
  res.end("Image not found");
1433
1506
  return true;
@@ -1440,8 +1513,8 @@ async function startPagesRouterServer(options) {
1440
1513
  if (result.type === "handled") return;
1441
1514
  if (result.type === "response") {
1442
1515
  const { response } = result;
1443
- if (missingBuildAsset && response.status === 404) {
1444
- await sendWebResponse(finalizeMissingStaticAssetResponse(response, true), req, res, compress);
1516
+ if (webRequest.signal.aborted) {
1517
+ cancelResponseBody(response);
1445
1518
  return;
1446
1519
  }
1447
1520
  const streamedApi = isVinextStreamedApiResponse(response);
@@ -1450,7 +1523,8 @@ async function startPagesRouterServer(options) {
1450
1523
  await sendWebResponse(response, req, res, compress);
1451
1524
  return;
1452
1525
  }
1453
- const responseBody = Buffer.from(await response.arrayBuffer());
1526
+ const responseBody = await bufferResponseBodyUntilAborted(response, webRequest.signal);
1527
+ if (!responseBody) return;
1454
1528
  const ct = response.headers.get("content-type") ?? result.defaultContentType;
1455
1529
  const responseHeaders = {};
1456
1530
  response.headers.forEach((v, k) => {
@@ -1492,4 +1566,4 @@ async function startPagesRouterServer(options) {
1492
1566
  };
1493
1567
  }
1494
1568
  //#endregion
1495
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, rememberCurrentServerEntryImportMtime, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic, waitForNodeResponseCompletion };
1569
+ export { COMPRESS_THRESHOLD, importServerEntryModule, isCompressibleContentType, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, rememberCurrentServerEntryImportMtime, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic, waitForNodeResponseCompletion };
@@ -1,7 +1,7 @@
1
1
  import { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS } from "./headers.js";
2
2
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
3
  import { StaticFileSignalContext, createStaticFileSignal } from "./static-file-signal.js";
4
- import { isOpenRedirectShaped } from "./open-redirect.js";
4
+ import { getRepeatedSlashRedirect, isOpenRedirectShaped, sendRepeatedSlashRedirect } from "./open-redirect.js";
5
5
  //#region src/server/request-pipeline.d.ts
6
6
  /**
7
7
  * Apply the URL Standard's pathname canonicalization without decoding and
@@ -39,10 +39,21 @@ export declare function canonicalizeRequestUrlPathname(url: string): string;
39
39
  * Backslashes are equivalent to forward slashes in the URL spec
40
40
  * (e.g. `/\evil.com` is treated as `//evil.com` by browsers).
41
41
  *
42
- * Next.js returns 404 for these paths. We check the RAW pathname before
43
- * normalization so the guard fires before normalizePath collapses `//`.
44
- *
45
- * Percent-encoded variants are also blocked because:
42
+ * Like Next.js, any raw path containing a literal backslash or a repeated
43
+ * slash first gets a 308 to the collapsed path (`//evil.com` → `/evil.com`,
44
+ * `//` → `/`); see `getRepeatedSlashRedirect`. We check the RAW
45
+ * pathname before normalization so this fires before normalizePath collapses
46
+ * `//`.
47
+ *
48
+ * Request-based (Worker) entries only see `request.url` after WHATWG URL
49
+ * parsing, which has already turned `\` into `/`; the raw request target is
50
+ * not available there. `/\evil.com` still redirects (it arrives as
51
+ * `//evil.com`), but a lone backslash such as `/foo\bar` arrives as
52
+ * `/foo/bar` and is routed without the redirect. On Cloudflare, requests that
53
+ * match a static asset are also answered by the asset layer before the
54
+ * Worker runs. Node entries (dev and `vinext start`) check the raw target.
55
+ *
56
+ * Percent-encoded variants get a 404, as in Next.js, because:
46
57
  * - `%5C` decodes to `\` (browsers treat `/\evil.com` as `//evil.com`).
47
58
  * - `%2F` decodes to `/` (so `/%2F/evil.com` effectively becomes `//evil.com`).
48
59
  * These forms survive segment-wise decoding that re-encodes path delimiters
@@ -51,9 +62,10 @@ export declare function canonicalizeRequestUrlPathname(url: string): string;
51
62
  * `isOpenRedirectShaped` for the full list of rejected leading-segment forms.
52
63
  *
53
64
  * @param rawPathname - The raw pathname from the URL, before any normalization
54
- * @returns A 404 Response if the path is protocol-relative, or null to continue
65
+ * @param search - The raw query string (including `?`), kept in the 308 Location
66
+ * @returns A 308 or 404 Response, or null to continue
55
67
  */
56
- export declare function guardProtocolRelativeUrl(rawPathname: string): Response | null;
68
+ export declare function guardProtocolRelativeUrl(rawPathname: string, search?: string): Response | null;
57
69
  export type HeaderRecord = Record<string, string | string[]>;
58
70
  type ResolvePublicFileRouteOptions = {
59
71
  cleanPathname: string;
@@ -175,4 +187,4 @@ export declare function cloneRequestWithHeaders(request: Request, headers: Heade
175
187
  */
176
188
  export declare function cloneRequestWithUrl(request: Request, url: string): Request;
177
189
  //#endregion
178
- export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, createStaticFileSignal, hasBasePath, isOpenRedirectShaped, stripBasePath };
190
+ export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, createStaticFileSignal, getRepeatedSlashRedirect, hasBasePath, isOpenRedirectShaped, sendRepeatedSlashRedirect, stripBasePath };