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
@@ -55,6 +55,15 @@ function indexOfByte(bytes, byte, from) {
55
55
  for (let index = from; index < bytes.byteLength; index++) if (bytes[index] === byte) return index;
56
56
  return -1;
57
57
  }
58
+ function utf8CompleteEnd(bytes, from) {
59
+ const length = bytes.byteLength;
60
+ for (let index = length - 1; index >= from && index >= length - 3; index--) {
61
+ const byte = bytes[index];
62
+ if ((byte & 192) === 128) continue;
63
+ return index + (byte >= 240 ? 4 : byte >= 224 ? 3 : byte >= 192 ? 2 : 1) > length ? index : length;
64
+ }
65
+ return length;
66
+ }
58
67
  function parseHexBytes(bytes, start, end) {
59
68
  if (start === end) return null;
60
69
  let value = 0;
@@ -101,16 +110,30 @@ function normalizeReactFlightPreloadHints(stream) {
101
110
  let carry = null;
102
111
  let rawBytesRemaining = 0;
103
112
  let passThrough = false;
113
+ let inPlainRow = false;
104
114
  return stream.pipeThrough(new TransformStream({
105
115
  transform(chunk, controller) {
106
116
  if (passThrough) {
107
117
  controller.enqueue(chunk);
108
118
  return;
109
119
  }
120
+ let start = 0;
121
+ if (inPlainRow) {
122
+ const end = indexOfByte(chunk, NEWLINE_BYTE, 0);
123
+ if (end === -1) {
124
+ const bytes = carry === null ? chunk : concatBytes(carry, chunk);
125
+ const complete = utf8CompleteEnd(bytes, 0);
126
+ carry = complete === bytes.byteLength ? null : bytes.slice(complete);
127
+ if (complete > 0) controller.enqueue(complete === bytes.byteLength ? bytes : bytes.subarray(0, complete));
128
+ return;
129
+ }
130
+ inPlainRow = false;
131
+ start = (carry === null ? 0 : carry.byteLength) + end + 1;
132
+ }
110
133
  const bytes = carry === null ? chunk : concatBytes(carry, chunk);
111
134
  carry = null;
112
135
  const byteLength = bytes.byteLength;
113
- let offset = 0;
136
+ let offset = start;
114
137
  let emittedThrough = 0;
115
138
  let output = bytes;
116
139
  let ownsOutput = bytes !== chunk;
@@ -146,7 +169,13 @@ function normalizeReactFlightPreloadHints(stream) {
146
169
  break;
147
170
  }
148
171
  const newline = indexOfByte(bytes, NEWLINE_BYTE, offset);
149
- if (newline === -1) break;
172
+ if (newline === -1) {
173
+ if (!(tagByte === HINT_TAG_BYTE && (colon + 2 === byteLength || bytes[colon + 2] === LINK_HINT_CODE_BYTE))) {
174
+ inPlainRow = true;
175
+ offset = utf8CompleteEnd(bytes, offset);
176
+ }
177
+ break;
178
+ }
150
179
  if (tagByte === HINT_TAG_BYTE && bytes[colon + 2] === LINK_HINT_CODE_BYTE) {
151
180
  const line = bytes.subarray(offset, newline + 1);
152
181
  const normalized = normalizeReactFlightHintLine(line);
@@ -14,6 +14,7 @@ type PrerenderCacheSeedMetadata = {
14
14
  };
15
15
  type PrerenderCacheSeedOptions = {
16
16
  buildAppPageHtmlKey?: (pathname: string) => string;
17
+ buildAppPageFallbackShellKey?: (pathname: string, suffix: "html" | "rsc") => string;
17
18
  buildAppPageRscKey?: (pathname: string) => string;
18
19
  buildAppRouteKey?: (pathname: string) => string;
19
20
  writeAppPageEntry?: (key: string, data: CachedAppPageValue, metadata: PrerenderCacheSeedMetadata) => Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
- import { appIsrCacheKey, isrSet, isrSetPrerenderedAppPage } from "./isr-cache.js";
2
+ import { appIsrCacheKey, appIsrFallbackShellKey, isrSet, isrSetPrerenderedAppPage } from "./isr-cache.js";
3
3
  import { addPregeneratedConcretePath, addPregeneratedRoute, clearPregeneratedConcretePaths, normalizePregeneratedPathname } from "./pregenerated-concrete-paths.js";
4
4
  import { getRenderedAppRoutes, getRenderedMetadataRoutes, isFallbackShellArtifactPath, readPrerenderManifest } from "./prerender-manifest.js";
5
5
  import { buildAppPageCacheTags, buildAppRouteCacheTags } from "./app-page-cache.js";
@@ -61,11 +61,12 @@ async function seedMemoryCacheFromPrerender(serverDir, options) {
61
61
  const appRoutes = getRenderedAppRoutes(routes);
62
62
  for (const route of appRoutes) {
63
63
  const concretePathname = route.path ?? route.route;
64
- if (!isFallbackShellArtifactPath(concretePathname, route)) addPregeneratedConcretePath(route.route, concretePathname);
64
+ const isFallbackShell = isFallbackShellArtifactPath(concretePathname, route);
65
+ if (!isFallbackShell) addPregeneratedConcretePath(route.route, concretePathname);
65
66
  const artifactPathname = route.path ?? route.route;
66
67
  const cachePathname = normalizePregeneratedPathname(artifactPathname);
67
- const htmlKey = options?.buildAppPageHtmlKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "html", buildId);
68
- const rscKey = options?.buildAppPageRscKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "rsc", buildId);
68
+ const htmlKey = isFallbackShell ? options?.buildAppPageFallbackShellKey?.(cachePathname, "html") ?? appIsrFallbackShellKey(cachePathname, "html", buildId) : options?.buildAppPageHtmlKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "html", buildId);
69
+ const rscKey = isFallbackShell ? options?.buildAppPageFallbackShellKey?.(cachePathname, "rsc") ?? appIsrFallbackShellKey(cachePathname, "rsc", buildId) : options?.buildAppPageRscKey?.(cachePathname) ?? appIsrCacheKey(cachePathname, "rsc", buildId);
69
70
  const revalidateSeconds = typeof route.revalidate === "number" ? route.revalidate : void 0;
70
71
  const expireSeconds = typeof route.expire === "number" ? route.expire : void 0;
71
72
  const staleSeconds = typeof route.stale === "number" && route.stale >= 0 ? route.stale : void 0;
@@ -0,0 +1,4 @@
1
+ //#region src/server/server-reference-loader.d.ts
2
+ /** Wrap plugin-rsc's server-reference loader once; must run after it is installed. */
3
+ export declare function isolateServerReferenceLoads(): void;
4
+ //#endregion
@@ -0,0 +1,29 @@
1
+ import { runOutsideRequestScopes } from "../shims/internal/als-registry.js";
2
+ //#region src/server/server-reference-loader.ts
3
+ /**
4
+ * Keep server-reference module evaluation out of the request that first
5
+ * names the module.
6
+ *
7
+ * React resolves every server reference it decodes through
8
+ * `@vitejs/plugin-rsc`'s `globalThis.__vite_rsc_server_require__`, which
9
+ * imports the referenced module on first use. Some references are resolved
10
+ * only once the action consumes them — promise and async-iterable arguments,
11
+ * and the encrypted closure captures an inline action decrypts in its own
12
+ * body — so they load inside the caller's request, past the loads the action
13
+ * dispatcher already runs outside it. A dynamic `import()` carries
14
+ * AsyncLocalStorage into the module's top-level evaluation, so module-scope
15
+ * `cookies()`/`headers()` would capture that caller's request for every later
16
+ * caller in the isolate. Next.js evaluates action modules outside the request
17
+ * store, where those calls throw.
18
+ */
19
+ const ISOLATED = Symbol.for("vinext.serverReferenceRequire.isolated");
20
+ /** Wrap plugin-rsc's server-reference loader once; must run after it is installed. */
21
+ function isolateServerReferenceLoads() {
22
+ const load = globalThis.__vite_rsc_server_require__;
23
+ if (typeof load !== "function" || ISOLATED in load) return;
24
+ const isolated = (id) => runOutsideRequestScopes(() => load(id));
25
+ Object.defineProperty(isolated, ISOLATED, { value: true });
26
+ globalThis.__vite_rsc_server_require__ = isolated;
27
+ }
28
+ //#endregion
29
+ export { isolateServerReferenceLoads };
@@ -43,5 +43,13 @@ export declare function isCancelledRequestBodyEnqueueError(err: unknown): boolea
43
43
  * Symbol.for guard. Not part of the public API.
44
44
  */
45
45
  export declare function isSocketErrorBackstopInstalled(): boolean;
46
- export declare function installSocketErrorBackstop(): void;
46
+ /**
47
+ * Installs the backstop once per process. Pass `{ runningServer: true }`
48
+ * from a long-lived production server to log, rather than re-throw,
49
+ * errors outside the absorbed set. The flag is process-global and sticky,
50
+ * so it also applies when `index.ts` installed the listeners first.
51
+ */
52
+ export declare function installSocketErrorBackstop(options?: {
53
+ runningServer?: boolean;
54
+ }): void;
47
55
  //#endregion
@@ -1,9 +1,10 @@
1
1
  //#region src/server/socket-error-backstop.ts
2
2
  /**
3
- * Process-level backstop for peer-disconnect errors that escape
4
- * per-connection / per-request error guards.
3
+ * Process-level backstop for errors that escape per-connection /
4
+ * per-request error guards.
5
5
  *
6
- * Three real call sites in vinext that hit this:
6
+ * Three real call sites in vinext that hit this with peer-disconnect
7
+ * errors:
7
8
  * - `fromWeb(fetch().body).pipe(res)` in proxyExternalRewriteNode.
8
9
  * - Streaming surfaces inside `@vitejs/plugin-rsc` with their own
9
10
  * pipe topology (destinations aren't inbound connection sockets).
@@ -14,14 +15,25 @@
14
15
  * inside a `nextTick` callback. The throw escapes to
15
16
  * `uncaughtException`, where this listener filters it.
16
17
  *
17
- * Filters strictly on peer-disconnect codes (ECONNRESET / EPIPE /
18
- * ECONNABORTED), benign static-asset `import()` rejections (see
19
- * `isBenignAssetImportError`) and Node's cancelled-request-body enqueue
20
- * race (see `isCancelledRequestBodyEnqueueError`), and synchronously
21
- * re-throws everything else, preserving Node's default crash semantics for genuine bugs.
22
- * This is more conservative than Next.js's equivalent
23
- * (`router-server.ts`'s log-only handler), which silently swallows
24
- * every uncaught — vinext keeps real bugs surfacing.
18
+ * Always absorbs benign static-asset `import()` rejections (see
19
+ * `isBenignAssetImportError`). Outside prerender it also silently
20
+ * absorbs peer-disconnect codes (ECONNRESET / EPIPE / ECONNABORTED) and
21
+ * Node's cancelled-request-body enqueue race (see
22
+ * `isCancelledRequestBodyEnqueueError`). Everything else depends on the
23
+ * phase:
24
+ *
25
+ * - **Running server** (`vite dev`, `vinext start`, standalone
26
+ * `server.js`): logged and absorbed, matching the log-only
27
+ * `uncaughtException` / `unhandledRejection` handlers that Next.js's
28
+ * `router-server.ts` installs for both `next dev` and `next start`.
29
+ * App code may legitimately leave a rejection unhandled for a few
30
+ * ticks (Next.js's documented "Preloading data" pattern,
31
+ * fire-and-forget calls), and request input can often choose whether
32
+ * it rejects, so crashing would let one anonymous request take down
33
+ * the whole process. The dev server's `configureServer` hook and
34
+ * `startProdServer()` opt in via `{ runningServer: true }`.
35
+ * - **Build and prerender**: synchronously re-thrown, preserving
36
+ * Node's default crash semantics.
25
37
  *
26
38
  * **Installed at module load.** Earlier iterations tried to gate
27
39
  * install via Vite's `config()` hook (`command === "serve"`) so it
@@ -67,11 +79,12 @@
67
79
  * top of every user's `vite.config.ts`, so vinext's listener registers
68
80
  * before most user / tooling listeners (Sentry, OpenTelemetry,
69
81
  * structured logging). For peer-disconnect codes the early-return is
70
- * fine. For non-peer-disconnect errors the synchronous re-throw still
71
- * crashes the process with the original stack, but listeners
72
- * registered after vinext don't observe the event. Users who need
73
- * crash-reporter visibility for non-peer-disconnect errors must
74
- * register their handler before importing vinext.
82
+ * fine, and on a running production server every other error is
83
+ * logged without throwing, so later listeners still observe it. In
84
+ * the re-throw phases the synchronous re-throw still crashes the
85
+ * process with the original stack, but listeners registered after
86
+ * vinext don't observe the event. Users who need crash-reporter
87
+ * visibility there must register their handler before importing vinext.
75
88
  *
76
89
  * **Symbol.for caveat.** `Symbol.for("vinext.socketErrorBackstop")`
77
90
  * is process-global, so if two different vinext versions are loaded
@@ -82,6 +95,7 @@
82
95
  * time the listener absorbs an error.
83
96
  */
84
97
  const SOCKET_BACKSTOP_FLAG = Symbol.for("vinext.socketErrorBackstop");
98
+ const RUNNING_SERVER_FLAG = Symbol.for("vinext.socketErrorBackstop.runningServer");
85
99
  /**
86
100
  * Pure predicate: returns the peer-disconnect code when `err` carries
87
101
  * one of `ECONNRESET` / `EPIPE` / `ECONNABORTED`, otherwise `undefined`.
@@ -179,8 +193,15 @@ function isCancelledRequestBodyEnqueueError(err) {
179
193
  function isSocketErrorBackstopInstalled() {
180
194
  return Boolean(process[SOCKET_BACKSTOP_FLAG]);
181
195
  }
182
- function installSocketErrorBackstop() {
196
+ /**
197
+ * Installs the backstop once per process. Pass `{ runningServer: true }`
198
+ * from a long-lived production server to log, rather than re-throw,
199
+ * errors outside the absorbed set. The flag is process-global and sticky,
200
+ * so it also applies when `index.ts` installed the listeners first.
201
+ */
202
+ function installSocketErrorBackstop(options = {}) {
183
203
  const proc = process;
204
+ if (options.runningServer) proc[RUNNING_SERVER_FLAG] = true;
184
205
  if (proc[SOCKET_BACKSTOP_FLAG]) return;
185
206
  if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") return;
186
207
  proc[SOCKET_BACKSTOP_FLAG] = true;
@@ -206,6 +227,10 @@ function installSocketErrorBackstop() {
206
227
  if (debug) console.warn("[vinext] absorbed uncaughtException ERR_INVALID_STATE (cancelled request body)");
207
228
  return;
208
229
  }
230
+ if (proc[RUNNING_SERVER_FLAG]) {
231
+ console.error("[vinext] uncaughtException:", err);
232
+ return;
233
+ }
209
234
  throw err;
210
235
  });
211
236
  process.on("unhandledRejection", (reason) => {
@@ -216,6 +241,10 @@ function installSocketErrorBackstop() {
216
241
  if (debug) console.warn(`[vinext] absorbed unhandledRejection ${code}`);
217
242
  return;
218
243
  }
244
+ if (proc[RUNNING_SERVER_FLAG]) {
245
+ console.error("[vinext] unhandledRejection:", reason);
246
+ return;
247
+ }
219
248
  throw reason;
220
249
  });
221
250
  }
@@ -3,14 +3,25 @@ export type StaticFileSignalContext = {
3
3
  headers: Headers | null;
4
4
  status: number | null;
5
5
  };
6
- /** Create the only response shape that host runtimes may resolve as an asset. */
7
- export declare function createStaticFileSignal(pathname: string, context: StaticFileSignalContext): Response;
6
+ /**
7
+ * Create the only response shape that host runtimes may resolve as an asset.
8
+ * `requestHeaders` are the request headers after middleware's overrides, which
9
+ * the asset fetch uses in place of the original request's headers.
10
+ */
11
+ export declare function createStaticFileSignal(pathname: string, context: StaticFileSignalContext, requestHeaders?: Headers | null): Response;
8
12
  /** Whether this response was created by vinext's public-file router. */
9
13
  export declare function isStaticFileSignal(response: Response): boolean;
10
14
  /** Return the encoded asset pathname only for a framework-created signal. */
11
15
  export declare function readStaticFileSignal(response: Response): string | null;
12
- /** Encode a framework-authenticated signal for a standards-only stage transport. */
16
+ /** Return the post-middleware request headers a framework-created signal carries. */
17
+ export declare function readStaticFileSignalRequestHeaders(response: Response): Headers | null;
18
+ /**
19
+ * Encode a framework-authenticated signal for a standards-only stage transport.
20
+ * Post-middleware request headers travel in the body, which has no platform
21
+ * size limit, rather than in a response header. Their marker header carries
22
+ * the real status, since the body needs a status that allows one.
23
+ */
13
24
  export declare function serializeStaticFileSignalForTransport(response: Response, token: string): Response;
14
25
  /** Restore and consume a signal returned by the trusted response-stage wrapper. */
15
- export declare function restoreStaticFileSignalFromTransport(response: Response, token: string): Response;
26
+ export declare function restoreStaticFileSignalFromTransport(response: Response, token: string): Promise<Response>;
16
27
  //#endregion
@@ -1,6 +1,8 @@
1
1
  //#region src/server/static-file-signal.ts
2
2
  const STATIC_FILE_SIGNAL = Symbol.for("vinext.static-file-signal");
3
+ const STATIC_FILE_REQUEST_HEADERS = Symbol.for("vinext.static-file-request-headers");
3
4
  const STATIC_FILE_SIGNAL_TRANSPORT_HEADER = "x-vinext-stage-static-file";
5
+ const STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER = "x-vinext-stage-static-file-request-headers";
4
6
  const STATIC_FILE_REPRESENTATION_HEADERS = [
5
7
  "content-encoding",
6
8
  "content-length",
@@ -14,17 +16,19 @@ const STATIC_FILE_REPRESENTATION_HEADERS = [
14
16
  * before the host runtime can fetch the asset. Application response headers
15
17
  * remain ordinary metadata and cannot alter framework control flow.
16
18
  */
17
- function markStaticFileSignal(response, pathname) {
18
- return markEncodedStaticFileSignal(response, encodeURIComponent(pathname));
19
+ function markStaticFileSignal(response, pathname, requestHeaders) {
20
+ return markEncodedStaticFileSignal(response, encodeURIComponent(pathname), requestHeaders);
19
21
  }
20
- function markEncodedStaticFileSignal(response, encodedPathname) {
22
+ function markEncodedStaticFileSignal(response, encodedPathname, requestHeaders) {
21
23
  Object.defineProperty(response, STATIC_FILE_SIGNAL, { value: encodedPathname });
24
+ if (requestHeaders) Object.defineProperty(response, STATIC_FILE_REQUEST_HEADERS, { value: requestHeaders });
22
25
  return response;
23
26
  }
24
27
  function withoutTransportHeader(response) {
25
- if (!response.headers.has(STATIC_FILE_SIGNAL_TRANSPORT_HEADER)) return response;
28
+ if (!response.headers.has(STATIC_FILE_SIGNAL_TRANSPORT_HEADER) && !response.headers.has(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER)) return response;
26
29
  const headers = new Headers(response.headers);
27
30
  headers.delete(STATIC_FILE_SIGNAL_TRANSPORT_HEADER);
31
+ headers.delete(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER);
28
32
  if (response.status < 200 || response.status > 599) return response;
29
33
  const body = response.status === 204 || response.status === 205 || response.status === 304 ? null : response.body;
30
34
  return new Response(body, {
@@ -33,14 +37,18 @@ function withoutTransportHeader(response) {
33
37
  statusText: response.statusText
34
38
  });
35
39
  }
36
- /** Create the only response shape that host runtimes may resolve as an asset. */
37
- function createStaticFileSignal(pathname, context) {
40
+ /**
41
+ * Create the only response shape that host runtimes may resolve as an asset.
42
+ * `requestHeaders` are the request headers after middleware's overrides, which
43
+ * the asset fetch uses in place of the original request's headers.
44
+ */
45
+ function createStaticFileSignal(pathname, context, requestHeaders = null) {
38
46
  const headers = new Headers();
39
47
  if (context.headers) for (const [key, value] of context.headers) headers.append(key, value);
40
48
  return markStaticFileSignal(new Response(null, {
41
49
  status: context.status ?? 200,
42
50
  headers
43
- }), pathname);
51
+ }), pathname, requestHeaders);
44
52
  }
45
53
  /** Whether this response was created by vinext's public-file router. */
46
54
  function isStaticFileSignal(response) {
@@ -51,22 +59,40 @@ function readStaticFileSignal(response) {
51
59
  const signal = Reflect.get(response, STATIC_FILE_SIGNAL);
52
60
  return typeof signal === "string" ? signal : null;
53
61
  }
54
- /** Encode a framework-authenticated signal for a standards-only stage transport. */
62
+ /** Return the post-middleware request headers a framework-created signal carries. */
63
+ function readStaticFileSignalRequestHeaders(response) {
64
+ const headers = Reflect.get(response, STATIC_FILE_REQUEST_HEADERS);
65
+ return headers instanceof Headers ? headers : null;
66
+ }
67
+ function isHeaderEntries(value) {
68
+ return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
69
+ }
70
+ /**
71
+ * Encode a framework-authenticated signal for a standards-only stage transport.
72
+ * Post-middleware request headers travel in the body, which has no platform
73
+ * size limit, rather than in a response header. Their marker header carries
74
+ * the real status, since the body needs a status that allows one.
75
+ */
55
76
  function serializeStaticFileSignalForTransport(response, token) {
56
77
  const signal = readStaticFileSignal(response);
57
78
  if (signal === null) return response;
58
79
  const headers = new Headers(response.headers);
59
80
  for (const name of STATIC_FILE_REPRESENTATION_HEADERS) headers.delete(name);
81
+ headers.delete(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER);
60
82
  headers.set(STATIC_FILE_SIGNAL_TRANSPORT_HEADER, `${token}:${signal}`);
61
- return new Response(null, {
83
+ const requestHeaders = readStaticFileSignalRequestHeaders(response);
84
+ if (!requestHeaders) return new Response(null, {
62
85
  headers,
63
86
  status: response.status,
64
87
  statusText: response.statusText
65
88
  });
89
+ headers.set(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER, `${token}:${response.status}`);
90
+ return new Response(JSON.stringify([...requestHeaders]), { headers });
66
91
  }
67
92
  /** Restore and consume a signal returned by the trusted response-stage wrapper. */
68
- function restoreStaticFileSignalFromTransport(response, token) {
93
+ async function restoreStaticFileSignalFromTransport(response, token) {
69
94
  const transported = response.headers.get(STATIC_FILE_SIGNAL_TRANSPORT_HEADER);
95
+ const requestHeadersMarker = response.headers.get(STATIC_FILE_REQUEST_HEADERS_TRANSPORT_HEADER);
70
96
  const cleaned = withoutTransportHeader(response);
71
97
  const prefix = `${token}:`;
72
98
  if (transported === null || !transported.startsWith(prefix)) return cleaned;
@@ -76,7 +102,15 @@ function restoreStaticFileSignalFromTransport(response, token) {
76
102
  } catch {
77
103
  return cleaned;
78
104
  }
79
- return markEncodedStaticFileSignal(cleaned, encodedPathname);
105
+ if (requestHeadersMarker === null) return markEncodedStaticFileSignal(cleaned, encodedPathname, null);
106
+ const status = Number(requestHeadersMarker.startsWith(prefix) ? requestHeadersMarker.slice(prefix.length) : NaN);
107
+ if (!Number.isInteger(status) || status < 200 || status > 599) return cleaned;
108
+ const entries = JSON.parse(await cleaned.text());
109
+ if (!isHeaderEntries(entries)) throw new Error("Invalid static-file request headers from the response stage");
110
+ return markEncodedStaticFileSignal(new Response(null, {
111
+ headers: cleaned.headers,
112
+ status
113
+ }), encodedPathname, new Headers(entries));
80
114
  }
81
115
  //#endregion
82
- export { createStaticFileSignal, isStaticFileSignal, readStaticFileSignal, restoreStaticFileSignalFromTransport, serializeStaticFileSignalForTransport };
116
+ export { createStaticFileSignal, isStaticFileSignal, readStaticFileSignal, readStaticFileSignalRequestHeaders, restoreStaticFileSignalFromTransport, serializeStaticFileSignalForTransport };
@@ -0,0 +1,34 @@
1
+ import { VinextAssetFetcher } from "./multi-stage.js";
2
+ import { ImageConfig, ImageOptimizationSignal } from "./image-optimization.js";
3
+ //#region src/server/worker-image-optimization.d.ts
4
+ export type WorkerImageSourceOptions = {
5
+ assets: VinextAssetFetcher;
6
+ basePath: string;
7
+ assetPathPrefix: string;
8
+ clientBuildFiles: ReadonlySet<string>;
9
+ /**
10
+ * Run `sourceRequest`, a GET without the caller's headers, through the
11
+ * application pipeline and return the public file it selects with a 2xx
12
+ * outcome, or null.
13
+ */
14
+ fetchPublicFile(sourceRequest: Request): Promise<Response | null>;
15
+ };
16
+ /**
17
+ * Fetch a local image source. Build output is fetched from the asset binding,
18
+ * which serves it before the Worker and its middleware. Any other source is
19
+ * served only when the application pipeline selects a public file for it.
20
+ */
21
+ export declare function fetchWorkerImageSource(imageUrl: string, request: Request, options: WorkerImageSourceOptions): Promise<Response>;
22
+ /**
23
+ * Return the public file a framework static-file signal selects, provided the
24
+ * pipeline answered with a body-bearing 2xx outcome. The file is fetched with middleware's
25
+ * request header overrides, as resolveStaticAssetSignal fetches it.
26
+ */
27
+ export declare function fetchSignalledPublicFile(response: Response, sourceRequest: Request, assets: VinextAssetFetcher): Promise<Response> | null;
28
+ /**
29
+ * Serve the App Router image endpoint once the request handler admitted it
30
+ * through middleware and rewrites. Middleware's response headers are sent with
31
+ * the image, as Next.js does.
32
+ */
33
+ export declare function serveWorkerImageOptimization(request: Request, signal: ImageOptimizationSignal, allowedWidths: number[] | undefined, imageConfig: ImageConfig | undefined, options: WorkerImageSourceOptions): Promise<Response>;
34
+ //#endregion
@@ -0,0 +1,86 @@
1
+ import { isNextStaticPath } from "../utils/asset-prefix.js";
2
+ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
3
+ import { VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "./headers.js";
4
+ import { normalizePath } from "./normalize-path.js";
5
+ import { readStaticFileSignal, readStaticFileSignalRequestHeaders } from "./static-file-signal.js";
6
+ import { attachRequestCfMetadata, cloneRequestWithUrl } from "./request-pipeline.js";
7
+ import { handleConfiguredImageOptimization, invalidImageSourceResponse, isImageSourceBodyStatus } from "./image-optimization.js";
8
+ import { createStaticAssetRequest, mergeHeaders } from "./worker-utils.js";
9
+ import { resolveClientBuildFile } from "./client-build-files.js";
10
+ //#region src/server/worker-image-optimization.ts
11
+ /**
12
+ * Image optimization for Worker hosts. A local source is fetched as an
13
+ * anonymous direct request would receive it, like Next.js' fetchInternalImage
14
+ * (packages/next/src/server/image-optimizer.ts), so middleware that denies or
15
+ * redirects a direct request also denies the optimizer.
16
+ */
17
+ const OMITTED_MIDDLEWARE_HEADERS = /* @__PURE__ */ new Set([
18
+ "content-encoding",
19
+ "content-length",
20
+ "content-range",
21
+ "content-type",
22
+ "transfer-encoding",
23
+ VINEXT_PRERENDER_SPECIAL_ERROR_HEADER
24
+ ]);
25
+ function cancelResponseBody(response) {
26
+ const body = response.body;
27
+ if (!body || body.locked) return;
28
+ body.cancel().catch(() => {});
29
+ }
30
+ /**
31
+ * Fetch a local image source. Build output is fetched from the asset binding,
32
+ * which serves it before the Worker and its middleware. Any other source is
33
+ * served only when the application pipeline selects a public file for it.
34
+ */
35
+ async function fetchWorkerImageSource(imageUrl, request, options) {
36
+ const sourceUrl = new URL(imageUrl, request.url);
37
+ let sourcePathname;
38
+ try {
39
+ sourcePathname = normalizePath(normalizePathnameForRouteMatchStrict(sourceUrl.pathname));
40
+ } catch {
41
+ return invalidImageSourceResponse();
42
+ }
43
+ const buildAssetPath = isNextStaticPath(sourcePathname, options.basePath, options.assetPathPrefix) ? sourcePathname : resolveClientBuildFile(sourceUrl.pathname, options.basePath, options.clientBuildFiles);
44
+ if (buildAssetPath !== null) return options.assets.fetch(new Request(new URL(buildAssetPath, request.url)));
45
+ const sourceResponse = await options.fetchPublicFile(attachRequestCfMetadata(new Request(sourceUrl, { signal: request.signal }), request));
46
+ if (sourceResponse && isImageSourceBodyStatus(sourceResponse.status)) return sourceResponse;
47
+ if (sourceResponse) cancelResponseBody(sourceResponse);
48
+ return invalidImageSourceResponse();
49
+ }
50
+ /**
51
+ * Return the public file a framework static-file signal selects, provided the
52
+ * pipeline answered with a body-bearing 2xx outcome. The file is fetched with middleware's
53
+ * request header overrides, as resolveStaticAssetSignal fetches it.
54
+ */
55
+ function fetchSignalledPublicFile(response, sourceRequest, assets) {
56
+ const signal = isImageSourceBodyStatus(response.status) ? readStaticFileSignal(response) : null;
57
+ const requestHeaders = readStaticFileSignalRequestHeaders(response);
58
+ cancelResponseBody(response);
59
+ if (signal === null) return null;
60
+ let assetPath = signal;
61
+ try {
62
+ assetPath = decodeURIComponent(signal);
63
+ } catch {}
64
+ return Promise.resolve(assets.fetch(createStaticAssetRequest(assetPath, sourceRequest, requestHeaders)));
65
+ }
66
+ /**
67
+ * Serve the App Router image endpoint once the request handler admitted it
68
+ * through middleware and rewrites. Middleware's response headers are sent with
69
+ * the image, as Next.js does.
70
+ */
71
+ async function serveWorkerImageOptimization(request, signal, allowedWidths, imageConfig, options) {
72
+ const imageUrl = new URL(request.url);
73
+ imageUrl.search = signal.search;
74
+ const imageRequest = cloneRequestWithUrl(request, imageUrl.href);
75
+ const response = await handleConfiguredImageOptimization(signal.requestHeaders ? new Request(imageRequest, { headers: signal.requestHeaders }) : imageRequest, (sourceUrl) => fetchWorkerImageSource(sourceUrl, imageRequest, options), allowedWidths, imageConfig);
76
+ const middlewareHeaders = {};
77
+ signal.middlewareHeaders.forEach((value, name) => {
78
+ if (name === "set-cookie" || OMITTED_MIDDLEWARE_HEADERS.has(name)) return;
79
+ middlewareHeaders[name] = value;
80
+ });
81
+ const cookies = signal.middlewareHeaders.getSetCookie();
82
+ if (cookies.length > 0) middlewareHeaders["set-cookie"] = cookies;
83
+ return mergeHeaders(response, middlewareHeaders);
84
+ }
85
+ //#endregion
86
+ export { fetchSignalledPublicFile, fetchWorkerImageSource, serveWorkerImageOptimization };
@@ -1,7 +1,7 @@
1
1
  import { VinextResponseStageCacheability, VinextResponseStageTransport } from "./multi-stage.js";
2
2
  import { PagesRenderOptions } from "./pages-request-pipeline.js";
3
3
  //#region src/server/worker-stages.d.ts
4
- export declare const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 4;
4
+ export declare const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 5;
5
5
  export declare const PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER = "x-vinext-pages-response-stage-policy-owner";
6
6
  export type PagesResponseStagePolicyOwner = "request-time" | "static";
7
7
  type PagesResponseStageEnvelope = {
@@ -1,5 +1,5 @@
1
1
  //#region src/server/worker-stages.ts
2
- const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 4;
2
+ const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 5;
3
3
  const PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER = "x-vinext-pages-response-stage-policy-owner";
4
4
  function isSerializedHeaders(value) {
5
5
  return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
@@ -12,7 +12,7 @@ function isResponseStageCacheability(value) {
12
12
  function isPagesResponseStageProps(value) {
13
13
  if (!value || typeof value !== "object") return false;
14
14
  const props = value;
15
- if (props.protocolVersion !== 4 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || typeof props.requestHost !== "string" || props.requestHost.length === 0 || props.stagedHeaders !== null && !isSerializedHeaders(props.stagedHeaders)) return false;
15
+ if (props.protocolVersion !== 5 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || typeof props.requestHost !== "string" || props.requestHost.length === 0 || props.stagedHeaders !== null && !isSerializedHeaders(props.stagedHeaders)) return false;
16
16
  if (props.kind === "pages-api") return typeof props.apiUrl === "string";
17
17
  if (props.kind === "pages-prerender-discovery") return true;
18
18
  if (props.kind !== "pages-page" || typeof props.resolvedUrl !== "string") return false;
@@ -2,8 +2,13 @@
2
2
  export declare function finalizeMissingStaticAssetResponse(response: Response, missingBuildAsset: boolean): Response;
3
3
  export declare function mergeHeaders(response: Response, extraHeaders: Record<string, string | string[]>, statusOverride?: number): Response;
4
4
  export declare function resolveStaticAssetSignal(signalResponse: Response, options: {
5
- fetchAsset(path: string): Promise<Response>;
5
+ /** `requestHeaders` are the post-middleware request headers, when middleware set any. */
6
+ fetchAsset(path: string, requestHeaders: Headers | null): Promise<Response>;
6
7
  }): Promise<Response | null>;
7
- /** Retarget a Worker asset request without dropping its conditional/range fields. */
8
- export declare function createStaticAssetRequest(assetPath: string, sourceRequest: Request): Request;
8
+ /**
9
+ * Retarget a Worker asset request without dropping its conditional/range
10
+ * fields. `headers` replaces the source request's headers with middleware's
11
+ * overrides, so a removed `Range` or replaced validator reaches the asset fetch.
12
+ */
13
+ export declare function createStaticAssetRequest(assetPath: string, sourceRequest: Request, headers?: Headers | null): Request;
9
14
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { notFoundStaticAssetResponse } from "./http-error-responses.js";
2
- import { readStaticFileSignal } from "./static-file-signal.js";
2
+ import { readStaticFileSignal, readStaticFileSignalRequestHeaders } from "./static-file-signal.js";
3
3
  //#region src/server/worker-utils.ts
4
4
  /**
5
5
  * Shared utilities for Cloudflare Worker entries.
@@ -47,7 +47,7 @@ function buildHeaderRecord(response, omitNames = []) {
47
47
  return headers;
48
48
  }
49
49
  function mergeHeaders(response, extraHeaders, statusOverride) {
50
- const status = statusOverride ?? response.status;
50
+ const status = response.status === 304 ? 304 : statusOverride ?? response.status;
51
51
  const merged = new Headers();
52
52
  for (const [k, v] of Object.entries(extraHeaders)) {
53
53
  if (isContentLengthHeader(k)) continue;
@@ -97,17 +97,22 @@ async function resolveStaticAssetSignal(signalResponse, options) {
97
97
  "content-type",
98
98
  "transfer-encoding"
99
99
  ]);
100
+ const requestHeaders = readStaticFileSignalRequestHeaders(signalResponse);
100
101
  cancelResponseBody(signalResponse);
101
- const assetResponse = await options.fetchAsset(assetPath);
102
+ const assetResponse = await options.fetchAsset(assetPath, requestHeaders);
102
103
  return mergeHeaders(assetResponse, extraHeaders, assetResponse.ok && assetResponse.status !== 206 && signalResponse.status !== 200 ? signalResponse.status : void 0);
103
104
  }
104
- /** Retarget a Worker asset request without dropping its conditional/range fields. */
105
- function createStaticAssetRequest(assetPath, sourceRequest) {
105
+ /**
106
+ * Retarget a Worker asset request without dropping its conditional/range
107
+ * fields. `headers` replaces the source request's headers with middleware's
108
+ * overrides, so a removed `Range` or replaced validator reaches the asset fetch.
109
+ */
110
+ function createStaticAssetRequest(assetPath, sourceRequest, headers = null) {
106
111
  const assetUrl = new URL(assetPath, sourceRequest.url);
107
- if (sourceRequest.method === "GET" || sourceRequest.method === "HEAD") return new Request(assetUrl, sourceRequest);
112
+ if (sourceRequest.method === "GET" || sourceRequest.method === "HEAD") return headers ? new Request(new Request(assetUrl, sourceRequest), { headers }) : new Request(assetUrl, sourceRequest);
108
113
  return new Request(assetUrl, {
109
114
  method: sourceRequest.method,
110
- headers: sourceRequest.headers
115
+ headers: headers ?? sourceRequest.headers
111
116
  });
112
117
  }
113
118
  //#endregion
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { disableSmoothScrollDuringRouteTransition } from "./disable-smooth-scroll.js";
2
3
  import { decodeHashFragment } from "./hash-scroll.js";
3
4
  import { consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted } from "./app-router-scroll-state.js";
4
5
  import * as React$1 from "react";
@@ -110,7 +111,9 @@ var AppRouterScrollTargetInner = class extends React$1.Component {
110
111
  const target = next.element;
111
112
  const consumed = consumeAppRouterScrollIntent(intent, this.props.commitId);
112
113
  if (consumed === null) return;
113
- scrollToElement(target, consumed.hash);
114
+ disableSmoothScrollDuringRouteTransition(() => {
115
+ scrollToElement(target, consumed.hash);
116
+ });
114
117
  target.focus();
115
118
  };
116
119
  componentDidMount() {