vinext 0.1.2 → 0.1.4

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 (243) hide show
  1. package/dist/build/client-build-config.d.ts +11 -2
  2. package/dist/build/client-build-config.js +17 -6
  3. package/dist/build/prerender.d.ts +9 -1
  4. package/dist/build/prerender.js +42 -12
  5. package/dist/build/run-prerender.d.ts +10 -2
  6. package/dist/build/run-prerender.js +15 -1
  7. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  8. package/dist/client/app-nav-failure-handler.js +44 -0
  9. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  10. package/dist/client/pages-router-link-navigation.js +32 -2
  11. package/dist/client/vinext-next-data.d.ts +18 -1
  12. package/dist/client/vinext-next-data.js +2 -0
  13. package/dist/client/window-next.d.ts +2 -1
  14. package/dist/client/window-next.js +12 -1
  15. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  16. package/dist/config/config-matchers.d.ts +11 -1
  17. package/dist/config/config-matchers.js +87 -16
  18. package/dist/config/next-config.d.ts +46 -4
  19. package/dist/config/next-config.js +147 -48
  20. package/dist/config/tsconfig-paths.js +14 -1
  21. package/dist/deploy.d.ts +30 -11
  22. package/dist/deploy.js +200 -112
  23. package/dist/entries/app-browser-entry.d.ts +9 -3
  24. package/dist/entries/app-browser-entry.js +21 -3
  25. package/dist/entries/app-rsc-entry.d.ts +2 -0
  26. package/dist/entries/app-rsc-entry.js +65 -5
  27. package/dist/entries/app-rsc-manifest.js +2 -0
  28. package/dist/entries/app-ssr-entry.js +1 -1
  29. package/dist/entries/pages-client-entry.js +66 -20
  30. package/dist/entries/pages-server-entry.js +47 -31
  31. package/dist/index.js +417 -102
  32. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  33. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  34. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  35. package/dist/plugins/fonts.js +5 -4
  36. package/dist/plugins/optimize-imports.d.ts +2 -1
  37. package/dist/plugins/optimize-imports.js +11 -9
  38. package/dist/plugins/postcss.js +7 -7
  39. package/dist/plugins/strip-server-exports.d.ts +9 -7
  40. package/dist/plugins/strip-server-exports.js +493 -46
  41. package/dist/plugins/typeof-window.d.ts +14 -0
  42. package/dist/plugins/typeof-window.js +150 -0
  43. package/dist/routing/app-route-graph.d.ts +2 -1
  44. package/dist/routing/app-route-graph.js +46 -16
  45. package/dist/routing/file-matcher.d.ts +10 -1
  46. package/dist/routing/file-matcher.js +22 -1
  47. package/dist/routing/pages-router.js +3 -3
  48. package/dist/routing/utils.d.ts +35 -6
  49. package/dist/routing/utils.js +59 -7
  50. package/dist/server/api-handler.d.ts +6 -1
  51. package/dist/server/api-handler.js +21 -15
  52. package/dist/server/app-browser-action-result.d.ts +19 -6
  53. package/dist/server/app-browser-action-result.js +20 -11
  54. package/dist/server/app-browser-entry.js +175 -91
  55. package/dist/server/app-browser-error.d.ts +10 -6
  56. package/dist/server/app-browser-error.js +43 -8
  57. package/dist/server/app-browser-hydration.d.ts +2 -0
  58. package/dist/server/app-browser-hydration.js +1 -0
  59. package/dist/server/app-browser-navigation-controller.d.ts +5 -3
  60. package/dist/server/app-browser-navigation-controller.js +23 -2
  61. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  62. package/dist/server/app-browser-server-action-navigation.js +9 -0
  63. package/dist/server/app-browser-state.d.ts +1 -1
  64. package/dist/server/app-browser-state.js +19 -11
  65. package/dist/server/app-browser-stream.js +86 -43
  66. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  67. package/dist/server/app-elements-wire.d.ts +6 -1
  68. package/dist/server/app-elements-wire.js +14 -4
  69. package/dist/server/app-elements.d.ts +2 -2
  70. package/dist/server/app-elements.js +2 -2
  71. package/dist/server/app-fallback-renderer.d.ts +1 -0
  72. package/dist/server/app-fallback-renderer.js +3 -1
  73. package/dist/server/app-optimistic-routing.js +2 -2
  74. package/dist/server/app-page-boundary-render.d.ts +1 -0
  75. package/dist/server/app-page-boundary-render.js +27 -14
  76. package/dist/server/app-page-cache-render.d.ts +53 -0
  77. package/dist/server/app-page-cache-render.js +91 -0
  78. package/dist/server/app-page-cache.d.ts +16 -2
  79. package/dist/server/app-page-cache.js +62 -1
  80. package/dist/server/app-page-dispatch.d.ts +26 -0
  81. package/dist/server/app-page-dispatch.js +149 -92
  82. package/dist/server/app-page-element-builder.d.ts +1 -0
  83. package/dist/server/app-page-element-builder.js +5 -2
  84. package/dist/server/app-page-execution.d.ts +6 -1
  85. package/dist/server/app-page-execution.js +21 -1
  86. package/dist/server/app-page-probe.d.ts +1 -0
  87. package/dist/server/app-page-probe.js +4 -0
  88. package/dist/server/app-page-render-observation.d.ts +3 -1
  89. package/dist/server/app-page-render-observation.js +17 -1
  90. package/dist/server/app-page-render.d.ts +12 -1
  91. package/dist/server/app-page-render.js +42 -4
  92. package/dist/server/app-page-request.d.ts +2 -0
  93. package/dist/server/app-page-request.js +2 -1
  94. package/dist/server/app-page-route-wiring.d.ts +3 -1
  95. package/dist/server/app-page-route-wiring.js +14 -5
  96. package/dist/server/app-page-stream.d.ts +15 -3
  97. package/dist/server/app-page-stream.js +11 -5
  98. package/dist/server/app-pages-bridge.d.ts +23 -1
  99. package/dist/server/app-pages-bridge.js +26 -17
  100. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  101. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  102. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  103. package/dist/server/app-ppr-fallback-shell.js +8 -1
  104. package/dist/server/app-route-handler-dispatch.js +9 -2
  105. package/dist/server/app-route-handler-policy.d.ts +1 -0
  106. package/dist/server/app-router-entry.js +5 -0
  107. package/dist/server/app-rsc-cache-busting.js +2 -0
  108. package/dist/server/app-rsc-handler.d.ts +28 -0
  109. package/dist/server/app-rsc-handler.js +195 -59
  110. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  111. package/dist/server/app-rsc-route-matching.js +8 -2
  112. package/dist/server/app-segment-config.d.ts +9 -1
  113. package/dist/server/app-segment-config.js +12 -3
  114. package/dist/server/app-server-action-execution.d.ts +1 -0
  115. package/dist/server/app-server-action-execution.js +47 -15
  116. package/dist/server/app-ssr-entry.d.ts +2 -0
  117. package/dist/server/app-ssr-entry.js +84 -39
  118. package/dist/server/before-interactive-head.d.ts +17 -0
  119. package/dist/server/before-interactive-head.js +35 -0
  120. package/dist/server/cache-control.js +4 -0
  121. package/dist/server/csp.js +1 -4
  122. package/dist/server/dev-server.d.ts +2 -2
  123. package/dist/server/dev-server.js +321 -83
  124. package/dist/server/hybrid-route-priority.d.ts +22 -0
  125. package/dist/server/hybrid-route-priority.js +33 -0
  126. package/dist/server/image-optimization.d.ts +18 -9
  127. package/dist/server/image-optimization.js +37 -23
  128. package/dist/server/implicit-tags.d.ts +2 -1
  129. package/dist/server/implicit-tags.js +4 -1
  130. package/dist/server/middleware-matcher.js +12 -3
  131. package/dist/server/middleware-runtime.d.ts +3 -4
  132. package/dist/server/middleware-runtime.js +2 -0
  133. package/dist/server/navigation-planner.d.ts +135 -41
  134. package/dist/server/navigation-planner.js +138 -0
  135. package/dist/server/navigation-trace.d.ts +9 -1
  136. package/dist/server/navigation-trace.js +9 -1
  137. package/dist/server/operation-token.d.ts +40 -0
  138. package/dist/server/operation-token.js +85 -0
  139. package/dist/server/pages-api-route.d.ts +6 -0
  140. package/dist/server/pages-api-route.js +13 -2
  141. package/dist/server/pages-asset-tags.d.ts +2 -1
  142. package/dist/server/pages-asset-tags.js +6 -2
  143. package/dist/server/pages-data-route.d.ts +9 -2
  144. package/dist/server/pages-data-route.js +18 -6
  145. package/dist/server/pages-dev-module-url.d.ts +4 -0
  146. package/dist/server/pages-dev-module-url.js +15 -0
  147. package/dist/server/pages-document-initial-props.d.ts +4 -15
  148. package/dist/server/pages-document-initial-props.js +27 -56
  149. package/dist/server/pages-get-initial-props.d.ts +54 -4
  150. package/dist/server/pages-get-initial-props.js +43 -1
  151. package/dist/server/pages-i18n.js +2 -2
  152. package/dist/server/pages-node-compat.js +2 -2
  153. package/dist/server/pages-page-data.d.ts +11 -2
  154. package/dist/server/pages-page-data.js +207 -34
  155. package/dist/server/pages-page-handler.d.ts +4 -2
  156. package/dist/server/pages-page-handler.js +62 -23
  157. package/dist/server/pages-page-response.d.ts +4 -1
  158. package/dist/server/pages-page-response.js +11 -8
  159. package/dist/server/pages-readiness.js +1 -1
  160. package/dist/server/pages-request-pipeline.d.ts +8 -7
  161. package/dist/server/pages-request-pipeline.js +126 -47
  162. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  163. package/dist/server/pregenerated-concrete-paths.js +2 -19
  164. package/dist/server/prerender-manifest.d.ts +33 -0
  165. package/dist/server/prerender-manifest.js +54 -0
  166. package/dist/server/prerender-route-params.d.ts +1 -2
  167. package/dist/server/prod-server.d.ts +3 -1
  168. package/dist/server/prod-server.js +50 -13
  169. package/dist/server/request-pipeline.d.ts +3 -15
  170. package/dist/server/request-pipeline.js +58 -47
  171. package/dist/server/rsc-stream-hints.d.ts +5 -1
  172. package/dist/server/rsc-stream-hints.js +6 -1
  173. package/dist/server/seed-cache.js +10 -18
  174. package/dist/server/static-file-cache.js +16 -4
  175. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  176. package/dist/shims/app-router-scroll-state.js +14 -2
  177. package/dist/shims/app-router-scroll.d.ts +3 -0
  178. package/dist/shims/app-router-scroll.js +28 -18
  179. package/dist/shims/before-interactive-context.d.ts +14 -3
  180. package/dist/shims/cache-runtime.js +3 -2
  181. package/dist/shims/cache.d.ts +1 -0
  182. package/dist/shims/cache.js +1 -1
  183. package/dist/shims/cdn-cache.d.ts +5 -5
  184. package/dist/shims/document.d.ts +15 -20
  185. package/dist/shims/document.js +5 -8
  186. package/dist/shims/dynamic-preload-chunks.js +6 -4
  187. package/dist/shims/error-boundary.d.ts +2 -0
  188. package/dist/shims/error-boundary.js +7 -0
  189. package/dist/shims/error.js +3 -2
  190. package/dist/shims/error.react-server.d.ts +9 -0
  191. package/dist/shims/error.react-server.js +6 -0
  192. package/dist/shims/fetch-cache.d.ts +3 -1
  193. package/dist/shims/fetch-cache.js +45 -20
  194. package/dist/shims/hash-scroll.js +6 -1
  195. package/dist/shims/headers.js +29 -4
  196. package/dist/shims/image.js +9 -2
  197. package/dist/shims/internal/als-registry.js +28 -1
  198. package/dist/shims/internal/app-route-detection.js +8 -17
  199. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  200. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  201. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  202. package/dist/shims/internal/navigation-untracked.js +55 -0
  203. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  204. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  205. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  206. package/dist/shims/internal/pages-data-target.js +17 -8
  207. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  208. package/dist/shims/internal/pages-router-accessor.js +13 -0
  209. package/dist/shims/internal/router-context.d.ts +2 -1
  210. package/dist/shims/internal/router-context.js +3 -1
  211. package/dist/shims/link.js +47 -19
  212. package/dist/shims/metadata.js +4 -4
  213. package/dist/shims/navigation.d.ts +8 -2
  214. package/dist/shims/navigation.js +63 -31
  215. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  216. package/dist/shims/ppr-fallback-shell.js +28 -7
  217. package/dist/shims/router.d.ts +18 -3
  218. package/dist/shims/router.js +512 -142
  219. package/dist/shims/script.js +8 -4
  220. package/dist/shims/server.d.ts +16 -1
  221. package/dist/shims/server.js +44 -12
  222. package/dist/shims/unified-request-context.js +1 -0
  223. package/dist/utils/built-asset-url.d.ts +4 -0
  224. package/dist/utils/built-asset-url.js +11 -0
  225. package/dist/utils/commonjs-loader.d.ts +16 -0
  226. package/dist/utils/commonjs-loader.js +100 -0
  227. package/dist/utils/deployment-id.d.ts +8 -0
  228. package/dist/utils/deployment-id.js +22 -0
  229. package/dist/utils/has-trailing-comma.d.ts +24 -0
  230. package/dist/utils/has-trailing-comma.js +62 -0
  231. package/dist/utils/html-limited-bots.d.ts +18 -1
  232. package/dist/utils/html-limited-bots.js +23 -1
  233. package/dist/utils/parse-cookie.d.ts +13 -0
  234. package/dist/utils/parse-cookie.js +52 -0
  235. package/dist/utils/path.d.ts +7 -1
  236. package/dist/utils/path.js +9 -1
  237. package/dist/utils/text-stream.d.ts +1 -1
  238. package/dist/utils/text-stream.js +2 -2
  239. package/dist/utils/vite-version.d.ts +12 -1
  240. package/dist/utils/vite-version.js +9 -1
  241. package/package.json +2 -2
  242. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  243. package/dist/shims/internal/parse-cookie-header.js +0 -30
@@ -109,11 +109,42 @@ async function importServerEntryModule(entryPath) {
109
109
  }
110
110
  /** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
111
111
  function readNodeStream(req) {
112
- return new ReadableStream({ start(controller) {
113
- req.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk)));
114
- req.on("end", () => controller.close());
115
- req.on("error", (err) => controller.error(err));
116
- } });
112
+ let cancelled = false;
113
+ let cleanup = () => {};
114
+ return new ReadableStream({
115
+ start(controller) {
116
+ cleanup = () => {
117
+ req.off("data", onData);
118
+ req.off("end", onEnd);
119
+ req.off("error", onError);
120
+ };
121
+ const onData = (chunk) => {
122
+ if (cancelled) return;
123
+ controller.enqueue(new Uint8Array(chunk));
124
+ if ((controller.desiredSize ?? 0) <= 0) req.pause();
125
+ };
126
+ const onEnd = () => {
127
+ cleanup();
128
+ if (!cancelled) controller.close();
129
+ };
130
+ const onError = (error) => {
131
+ cleanup();
132
+ if (!cancelled) controller.error(error);
133
+ };
134
+ req.on("data", onData);
135
+ req.on("end", onEnd);
136
+ req.on("error", onError);
137
+ req.pause();
138
+ },
139
+ pull() {
140
+ if (!cancelled) req.resume();
141
+ },
142
+ cancel() {
143
+ cancelled = true;
144
+ cleanup();
145
+ req.resume();
146
+ }
147
+ });
117
148
  }
118
149
  /** Content types that benefit from compression. */
119
150
  const COMPRESSIBLE_TYPES = new Set([
@@ -201,6 +232,7 @@ function toWebHeaders(headersRecord) {
201
232
  }
202
233
  function appendWebHeader(headers, key, value) {
203
234
  if (value === void 0) return;
235
+ if (key.startsWith(":")) return;
204
236
  if (Array.isArray(value)) {
205
237
  for (const item of value) headers.append(key, item);
206
238
  return;
@@ -513,7 +545,7 @@ function nodeToWebRequest(req, urlOverride, prerenderSecret) {
513
545
  headers
514
546
  };
515
547
  if (hasBody) {
516
- init.body = Readable.toWeb(req);
548
+ init.body = readNodeStream(req);
517
549
  init.duplex = "half";
518
550
  }
519
551
  return new Request(url, init);
@@ -783,7 +815,7 @@ async function startAppRouterServer(options) {
783
815
  }
784
816
  }
785
817
  if (isImageOptimizationPath(pathname)) {
786
- const params = parseImageParams(new URL(rawUrl, "http://localhost"), [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES]);
818
+ const params = parseImageParams(new URL(rawUrl, "http://localhost"), [...imageConfig?.deviceSizes ?? DEFAULT_DEVICE_SIZES, ...imageConfig?.imageSizes ?? DEFAULT_IMAGE_SIZES], imageConfig?.qualities);
787
819
  if (!params) {
788
820
  res.writeHead(400);
789
821
  res.end("Bad Request");
@@ -891,6 +923,7 @@ async function startPagesRouterServer(options) {
891
923
  const pagesImageConfig = vinextConfig?.images ? {
892
924
  dangerouslyAllowSVG: vinextConfig.images.dangerouslyAllowSVG,
893
925
  dangerouslyAllowLocalIP: vinextConfig.images.dangerouslyAllowLocalIP,
926
+ qualities: vinextConfig.images.qualities,
894
927
  contentDispositionType: vinextConfig.images.contentDispositionType,
895
928
  contentSecurityPolicy: vinextConfig.images.contentSecurityPolicy
896
929
  } : void 0;
@@ -960,7 +993,7 @@ async function startPagesRouterServer(options) {
960
993
  return;
961
994
  }
962
995
  if (isImageOptimizationPath(pathname) || isImageOptimizationPath(staticLookupPath)) {
963
- const params = parseImageParams(new URL(rawUrl, "http://localhost"), allowedImageWidths);
996
+ const params = parseImageParams(new URL(rawUrl, "http://localhost"), allowedImageWidths, pagesImageConfig?.qualities);
964
997
  if (!params) {
965
998
  res.writeHead(400);
966
999
  res.end("Bad Request");
@@ -991,6 +1024,7 @@ async function startPagesRouterServer(options) {
991
1024
  }
992
1025
  }
993
1026
  let isDataReq = false;
1027
+ const originalRenderUrl = url;
994
1028
  if (isNextDataPathname(pathname)) {
995
1029
  const dataMatch = pagesBuildId ? parseNextDataPathname(pathname, pagesBuildId) : null;
996
1030
  if (!dataMatch) {
@@ -1005,7 +1039,7 @@ async function startPagesRouterServer(options) {
1005
1039
  const protocol = resolveRequestProtocol(req);
1006
1040
  const hostHeader = resolveRequestHost(req, `${host}:${port}`);
1007
1041
  const rawReqHeaders = nodeHeadersToWebHeaders(req.headers);
1008
- const isDataRequest = rawReqHeaders.get("x-nextjs-data") === "1";
1042
+ const isDataRequest = isDataReq;
1009
1043
  const reqHeaders = filterInternalHeaders(rawReqHeaders);
1010
1044
  const method = req.method ?? "GET";
1011
1045
  const hasBody = method !== "GET" && method !== "HEAD";
@@ -1028,10 +1062,13 @@ async function startPagesRouterServer(options) {
1028
1062
  rawSearch: rawQs,
1029
1063
  matchPageRoute: matchPageRoute ?? null,
1030
1064
  runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
1031
- renderPage: typeof renderPage === "function" ? (request, resolvedUrl, options, stagedHeaders) => renderPage(request, resolvedUrl, ssrManifest, void 0, stagedHeaders, options) : null,
1065
+ renderPage: typeof renderPage === "function" ? (request, resolvedUrl, options, stagedHeaders) => renderPage(request, resolvedUrl, ssrManifest, void 0, stagedHeaders, {
1066
+ ...options,
1067
+ originalUrl: originalRenderUrl
1068
+ }) : null,
1032
1069
  handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext()) : null,
1033
- serveStaticFile: async (requestPathname, stagedHeaders) => {
1034
- if (requestPathname === "/" || requestPathname.startsWith("/api/") || requestPathname.startsWith(`/_next/static/`)) return false;
1070
+ serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
1071
+ if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
1035
1072
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
1036
1073
  }
1037
1074
  });
@@ -1082,4 +1119,4 @@ async function startPagesRouterServer(options) {
1082
1119
  };
1083
1120
  }
1084
1121
  //#endregion
1085
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
1122
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
@@ -76,7 +76,8 @@ type ApplyConfigHeadersOptions = {
76
76
  * (basePath: true) rule for backward compatibility — callers that need to
77
77
  * support `basePath: false` headers must pass this in.
78
78
  */
79
- basePathState?: BasePathMatchState;
79
+ basePathState?: BasePathMatchState; /** Existing framework-generated headers that matching config rules may replace. */
80
+ overwriteExisting?: ReadonlySet<string>;
80
81
  };
81
82
  type StaticFileSignalContext = {
82
83
  headers: Headers | null;
@@ -157,19 +158,6 @@ declare function validateCsrfOrigin(request: Request, allowedOrigins?: string[])
157
158
  */
158
159
  declare function validateServerActionPayload(body: string | FormData): Promise<Response | null>;
159
160
  declare function isOriginAllowed(origin: string, allowed: string[]): boolean;
160
- /**
161
- * Validate an image optimization URL parameter.
162
- *
163
- * Ensures the URL is a relative path that doesn't escape the origin:
164
- * - Must start with "/" but not "//"
165
- * - Backslashes are normalized (browsers treat `\` as `/`)
166
- * - Origin validation as defense-in-depth
167
- *
168
- * @param rawUrl - The raw `url` query parameter value
169
- * @param requestUrl - The full request URL for origin comparison
170
- * @returns An error Response if validation fails, or the normalized image URL
171
- */
172
- declare function validateImageUrl(rawUrl: string | null, requestUrl: string): Response | string;
173
161
  /**
174
162
  * Strip internal `x-middleware-*` headers from a Headers object.
175
163
  *
@@ -219,4 +207,4 @@ declare function cloneRequestWithHeaders(request: Request, headers: Headers): Re
219
207
  */
220
208
  declare function cloneRequestWithUrl(request: Request, url: string): Request;
221
209
  //#endregion
222
- export { HeaderRecord, INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, applyConfigHeadersToHeaderRecord, applyConfigHeadersToResponse, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, filterInternalHeaders, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, stripBasePath, validateCsrfOrigin, validateImageUrl, validateServerActionPayload };
210
+ export { HeaderRecord, INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, applyConfigHeadersToHeaderRecord, applyConfigHeadersToResponse, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, filterInternalHeaders, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, stripBasePath, validateCsrfOrigin, validateServerActionPayload };
@@ -122,7 +122,7 @@ function applyConfigHeadersToResponse(responseHeaders, options) {
122
122
  for (const header of matched) {
123
123
  const lowerName = header.key.toLowerCase();
124
124
  if (lowerName === "vary" || lowerName === "set-cookie") responseHeaders.append(header.key, header.value);
125
- else if (!responseHeaders.has(lowerName)) responseHeaders.set(header.key, header.value);
125
+ else if (options.overwriteExisting?.has(lowerName) || !responseHeaders.has(lowerName)) responseHeaders.set(header.key, header.value);
126
126
  }
127
127
  }
128
128
  /**
@@ -197,9 +197,10 @@ function normalizeTrailingSlash(pathname, basePath, trailingSlash, search) {
197
197
  if (isOpenRedirectShaped(pathname)) return notFoundResponse();
198
198
  const normalizedPathname = normalizeTrailingSlashPathname(pathname, trailingSlash);
199
199
  if (normalizedPathname === null) return null;
200
+ const encodedPathname = normalizedPathname.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@/%]/gu, encodeURIComponent);
200
201
  return new Response(null, {
201
202
  status: 308,
202
- headers: { Location: basePath + normalizedPathname + search }
203
+ headers: { Location: basePath + encodedPathname + search }
203
204
  });
204
205
  }
205
206
  /**
@@ -246,45 +247,74 @@ function validateCsrfOrigin(request, allowedOrigins = []) {
246
247
  * Regular user form fields are ignored entirely.
247
248
  */
248
249
  async function validateServerActionPayload(body) {
249
- const containerRefRe = /"\$([QWi])(\d+)"/g;
250
+ const maxNumericFields = 4096;
251
+ const maxContainerReferences = 16384;
252
+ const maxContainerDepth = 1024;
253
+ const containerRefRe = /"\$([QWi])([0-9a-f]+)(?=[:"])/gi;
250
254
  const fieldRefs = /* @__PURE__ */ new Map();
255
+ let referenceCount = 0;
256
+ const invalidPayloadResponse = () => new Response("Invalid server action payload", {
257
+ status: 400,
258
+ headers: { "Content-Type": "text/plain" }
259
+ });
251
260
  const collectRefs = (fieldKey, text) => {
261
+ if (fieldRefs.has(fieldKey)) return true;
262
+ if (fieldRefs.size >= maxNumericFields) return false;
252
263
  const refs = /* @__PURE__ */ new Set();
253
264
  let match;
254
265
  containerRefRe.lastIndex = 0;
255
- while ((match = containerRefRe.exec(text)) !== null) refs.add(match[2]);
266
+ while ((match = containerRefRe.exec(text)) !== null) {
267
+ const previousSize = refs.size;
268
+ refs.add(String(Number.parseInt(match[2], 16)));
269
+ if (refs.size !== previousSize && ++referenceCount > maxContainerReferences) return false;
270
+ }
256
271
  fieldRefs.set(fieldKey, refs);
272
+ return true;
257
273
  };
258
- if (typeof body === "string") collectRefs("0", body);
259
- else for (const [key, value] of body.entries()) {
274
+ if (typeof body === "string") {
275
+ if (!collectRefs("0", body)) return invalidPayloadResponse();
276
+ } else for (const [key, value] of body.entries()) {
260
277
  if (!/^\d+$/.test(key)) continue;
261
278
  if (typeof value === "string") {
262
- collectRefs(key, value);
279
+ if (!collectRefs(key, value)) return invalidPayloadResponse();
263
280
  continue;
264
281
  }
265
- if (typeof value?.text === "function") collectRefs(key, await value.text());
282
+ if (typeof value?.text === "function") {
283
+ if (!collectRefs(key, await value.text())) return invalidPayloadResponse();
284
+ }
266
285
  }
267
286
  if (fieldRefs.size === 0) return null;
268
287
  const knownFields = new Set(fieldRefs.keys());
269
- for (const refs of fieldRefs.values()) for (const ref of refs) if (!knownFields.has(ref)) return new Response("Invalid server action payload", {
270
- status: 400,
271
- headers: { "Content-Type": "text/plain" }
272
- });
273
- const visited = /* @__PURE__ */ new Set();
274
- const stack = /* @__PURE__ */ new Set();
275
- const hasCycle = (node) => {
276
- if (stack.has(node)) return true;
277
- if (visited.has(node)) return false;
278
- visited.add(node);
279
- stack.add(node);
280
- for (const ref of fieldRefs.get(node) ?? []) if (hasCycle(ref)) return true;
281
- stack.delete(node);
282
- return false;
283
- };
284
- for (const node of fieldRefs.keys()) if (hasCycle(node)) return new Response("Invalid server action payload", {
285
- status: 400,
286
- headers: { "Content-Type": "text/plain" }
287
- });
288
+ for (const refs of fieldRefs.values()) for (const ref of refs) if (!knownFields.has(ref)) return invalidPayloadResponse();
289
+ const state = /* @__PURE__ */ new Map();
290
+ for (const root of fieldRefs.keys()) {
291
+ if (state.has(root)) continue;
292
+ const stack = [{
293
+ node: root,
294
+ refs: [...fieldRefs.get(root) ?? []],
295
+ nextRef: 0
296
+ }];
297
+ state.set(root, "visiting");
298
+ while (stack.length > 0) {
299
+ if (stack.length > maxContainerDepth) return invalidPayloadResponse();
300
+ const frame = stack[stack.length - 1];
301
+ const ref = frame.refs[frame.nextRef++];
302
+ if (ref === void 0) {
303
+ state.set(frame.node, "visited");
304
+ stack.pop();
305
+ continue;
306
+ }
307
+ const refState = state.get(ref);
308
+ if (refState === "visiting") return invalidPayloadResponse();
309
+ if (refState === "visited") continue;
310
+ state.set(ref, "visiting");
311
+ stack.push({
312
+ node: ref,
313
+ refs: [...fieldRefs.get(ref) ?? []],
314
+ nextRef: 0
315
+ });
316
+ }
317
+ }
288
318
  return null;
289
319
  }
290
320
  /**
@@ -329,25 +359,6 @@ function isOriginAllowed(origin, allowed) {
329
359
  return false;
330
360
  }
331
361
  /**
332
- * Validate an image optimization URL parameter.
333
- *
334
- * Ensures the URL is a relative path that doesn't escape the origin:
335
- * - Must start with "/" but not "//"
336
- * - Backslashes are normalized (browsers treat `\` as `/`)
337
- * - Origin validation as defense-in-depth
338
- *
339
- * @param rawUrl - The raw `url` query parameter value
340
- * @param requestUrl - The full request URL for origin comparison
341
- * @returns An error Response if validation fails, or the normalized image URL
342
- */
343
- function validateImageUrl(rawUrl, requestUrl) {
344
- const imgUrl = rawUrl?.replaceAll("\\", "/") ?? null;
345
- if (!imgUrl || !imgUrl.startsWith("/") || imgUrl.startsWith("//")) return new Response(!rawUrl ? "Missing url parameter" : "Only relative URLs allowed", { status: 400 });
346
- const url = new URL(requestUrl);
347
- if (new URL(imgUrl, url.origin).origin !== url.origin) return new Response("Only relative URLs allowed", { status: 400 });
348
- return imgUrl;
349
- }
350
- /**
351
362
  * Strip internal `x-middleware-*` headers from a Headers object.
352
363
  *
353
364
  * Middleware uses `x-middleware-*` headers as internal signals (e.g.
@@ -465,4 +476,4 @@ function cloneRequestWithUrl(request, url) {
465
476
  return cloned;
466
477
  }
467
478
  //#endregion
468
- export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, applyConfigHeadersToHeaderRecord, applyConfigHeadersToResponse, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, filterInternalHeaders, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, stripBasePath, validateCsrfOrigin, validateImageUrl, validateServerActionPayload };
479
+ export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, applyConfigHeadersToHeaderRecord, applyConfigHeadersToResponse, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, filterInternalHeaders, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, stripBasePath, validateCsrfOrigin, validateServerActionPayload };
@@ -1,6 +1,10 @@
1
1
  //#region src/server/rsc-stream-hints.d.ts
2
2
  declare function normalizeReactFlightPreloadHints(stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array>;
3
3
  type RscRawRenderer = (model: unknown, options?: unknown) => ReadableStream<Uint8Array>;
4
+ type RscRawPrerenderer = (model: unknown, options?: unknown) => Promise<{
5
+ prelude: ReadableStream<Uint8Array>;
6
+ }>;
4
7
  declare function createRscRenderer(render: RscRawRenderer): RscRawRenderer;
8
+ declare function createRscPrerenderer(prerender: RscRawPrerenderer): RscRawPrerenderer;
5
9
  //#endregion
6
- export { createRscRenderer, normalizeReactFlightPreloadHints };
10
+ export { RscRawPrerenderer, RscRawRenderer, createRscPrerenderer, createRscRenderer, normalizeReactFlightPreloadHints };
@@ -32,5 +32,10 @@ function normalizeReactFlightPreloadHints(stream) {
32
32
  function createRscRenderer(render) {
33
33
  return (model, options) => normalizeReactFlightPreloadHints(render(model, options));
34
34
  }
35
+ function createRscPrerenderer(prerender) {
36
+ return async (model, options) => {
37
+ return { prelude: normalizeReactFlightPreloadHints((await prerender(model, options)).prelude) };
38
+ };
39
+ }
35
40
  //#endregion
36
- export { createRscRenderer, normalizeReactFlightPreloadHints };
41
+ export { createRscPrerenderer, createRscRenderer, normalizeReactFlightPreloadHints };
@@ -1,8 +1,8 @@
1
- import { normalizePathnameForRouteMatch } from "../routing/utils.js";
2
- import { normalizePath } from "./normalize-path.js";
3
1
  import { isrCacheKey, isrSetPrerenderedAppPage } from "./isr-cache.js";
4
2
  import { buildAppPageCacheTags } from "./app-page-cache.js";
5
3
  import { getOutputPath, getRscOutputPath } from "../utils/prerender-output-paths.js";
4
+ import { addPregeneratedConcretePath, clearPregeneratedConcretePaths, normalizePregeneratedPathname } from "./pregenerated-concrete-paths.js";
5
+ import { getRenderedAppRoutes, isFallbackShellArtifactPath, readPrerenderManifest } from "./prerender-manifest.js";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  //#region src/server/seed-cache.ts
@@ -46,26 +46,21 @@ import path from "node:path";
46
46
  * @returns The number of routes seeded (0 if no manifest or no renderable routes).
47
47
  */
48
48
  async function seedMemoryCacheFromPrerender(serverDir, options) {
49
- const manifestPath = path.join(serverDir, "vinext-prerender.json");
50
- if (!fs.existsSync(manifestPath)) return 0;
51
- let manifest;
52
- try {
53
- manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
54
- } catch (err) {
55
- console.warn("[vinext] Failed to parse vinext-prerender.json, skipping cache seeding:", err);
56
- return 0;
57
- }
49
+ clearPregeneratedConcretePaths();
50
+ const manifest = readPrerenderManifest(path.join(serverDir, "vinext-prerender.json"));
51
+ if (!manifest) return 0;
58
52
  const { buildId, routes } = manifest;
59
53
  if (!buildId || !Array.isArray(routes)) return 0;
60
54
  const trailingSlash = manifest.trailingSlash ?? false;
61
55
  const prerenderDir = path.join(serverDir, "prerendered-routes");
62
56
  const writeAppPageEntry = options?.writeAppPageEntry ?? createDefaultAppPageEntryWriter();
63
57
  let seeded = 0;
64
- for (const route of routes) {
65
- if (route.status !== "rendered") continue;
66
- if (route.router !== "app") continue;
58
+ const appRoutes = getRenderedAppRoutes(routes);
59
+ for (const route of appRoutes) {
60
+ const concretePathname = route.path ?? route.route;
61
+ if (!isFallbackShellArtifactPath(concretePathname, route)) addPregeneratedConcretePath(route.route, concretePathname);
67
62
  const artifactPathname = route.path ?? route.route;
68
- const cachePathname = normalizePrerenderCachePathname(artifactPathname);
63
+ const cachePathname = normalizePregeneratedPathname(artifactPathname);
69
64
  const baseKey = isrCacheKey("app", cachePathname, buildId);
70
65
  const htmlKey = options?.buildAppPageHtmlKey?.(cachePathname) ?? baseKey + ":html";
71
66
  const rscKey = options?.buildAppPageRscKey?.(cachePathname) ?? baseKey + ":rsc";
@@ -79,9 +74,6 @@ async function seedMemoryCacheFromPrerender(serverDir, options) {
79
74
  }
80
75
  return seeded;
81
76
  }
82
- function normalizePrerenderCachePathname(pathname) {
83
- return normalizePath(normalizePathnameForRouteMatch(pathname));
84
- }
85
77
  function createDefaultAppPageEntryWriter() {
86
78
  return (key, data, metadata) => isrSetPrerenderedAppPage(key, data, metadata);
87
79
  }
@@ -1,5 +1,5 @@
1
1
  import { normalizePathSeparators } from "../utils/path.js";
2
- import "../utils/asset-prefix.js";
2
+ import { ASSET_PREFIX_URL_DIR } from "../utils/asset-prefix.js";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs/promises";
5
5
  //#region src/server/static-file-cache.ts
@@ -23,6 +23,7 @@ const CONTENT_TYPES = {
23
23
  ".css": "text/css",
24
24
  ".html": "text/html",
25
25
  ".json": "application/json",
26
+ ".txt": "text/plain; charset=utf-8",
26
27
  ".png": "image/png",
27
28
  ".jpg": "image/jpeg",
28
29
  ".jpeg": "image/jpeg",
@@ -165,9 +166,20 @@ var StaticFileCache = class StaticFileCache {
165
166
  function etagFromFilenameHash(relativePath, ext) {
166
167
  const basename = path.basename(relativePath, ext);
167
168
  const lastDash = basename.lastIndexOf("-");
168
- if (lastDash === -1 || lastDash === basename.length - 1) return null;
169
- const suffix = basename.slice(lastDash + 1);
170
- return suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix) ? `W/"${suffix}"` : null;
169
+ if (lastDash !== -1 && lastDash !== basename.length - 1) {
170
+ const suffix = basename.slice(lastDash + 1);
171
+ if (suffix.length >= 6 && suffix.length <= 12 && /^[A-Za-z0-9_-]+$/.test(suffix)) return `W/"${suffix}"`;
172
+ }
173
+ const normalizedPath = normalizePathSeparators(relativePath);
174
+ const managedMediaSegment = `${ASSET_PREFIX_URL_DIR}/media/`;
175
+ if (normalizedPath.startsWith(managedMediaSegment) || normalizedPath.includes(`/${managedMediaSegment}`)) {
176
+ const lastDot = basename.lastIndexOf(".");
177
+ if (lastDot !== -1) {
178
+ const suffix = basename.slice(lastDot + 1);
179
+ if (/^[0-9a-f]{8}$/.test(suffix)) return `W/"${suffix}"`;
180
+ }
181
+ }
182
+ return null;
171
183
  }
172
184
  function buildVariant(info, baseHeaders, encoding) {
173
185
  return {
@@ -3,11 +3,13 @@ type AppRouterScrollIntent = Readonly<{
3
3
  commitId: number | null;
4
4
  hash: string | null;
5
5
  id: number;
6
+ targetHoistedInHead: boolean;
6
7
  }>;
7
8
  declare function beginAppRouterScrollIntent(hash: string | null): AppRouterScrollIntent;
8
9
  declare function clearAppRouterScrollIntent(): void;
9
10
  declare function getPendingAppRouterScrollIntent(): AppRouterScrollIntent | null;
10
11
  declare function claimAppRouterScrollIntentForCommit(expected: AppRouterScrollIntent | null | undefined, commitId: number): void;
12
+ declare function markAppRouterScrollIntentHeadHoisted(expected: AppRouterScrollIntent | null | undefined, commitId: number): void;
11
13
  declare function consumeAppRouterScrollIntent(expected: AppRouterScrollIntent | null | undefined, commitId?: number): AppRouterScrollIntent | null;
12
14
  //#endregion
13
- export { AppRouterScrollIntent, beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent };
15
+ export { AppRouterScrollIntent, beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
@@ -14,7 +14,8 @@ function beginAppRouterScrollIntent(hash) {
14
14
  const intent = {
15
15
  commitId: null,
16
16
  hash,
17
- id: store.nextId
17
+ id: store.nextId,
18
+ targetHoistedInHead: false
18
19
  };
19
20
  store.pending = intent;
20
21
  return intent;
@@ -35,6 +36,17 @@ function claimAppRouterScrollIntentForCommit(expected, commitId) {
35
36
  commitId
36
37
  };
37
38
  }
39
+ function markAppRouterScrollIntentHeadHoisted(expected, commitId) {
40
+ const store = getScrollIntentStore();
41
+ const intent = store.pending;
42
+ if (expected === null || expected === void 0 || intent === null) return;
43
+ if (intent.id !== expected.id) return;
44
+ if (intent.commitId !== commitId) return;
45
+ store.pending = {
46
+ ...intent,
47
+ targetHoistedInHead: true
48
+ };
49
+ }
38
50
  function consumeAppRouterScrollIntent(expected, commitId) {
39
51
  if (expected === null || expected === void 0) return null;
40
52
  const store = getScrollIntentStore();
@@ -46,4 +58,4 @@ function consumeAppRouterScrollIntent(expected, commitId) {
46
58
  return intent;
47
59
  }
48
60
  //#endregion
49
- export { beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent };
61
+ export { beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
@@ -5,9 +5,12 @@ declare class AppRouterScrollTargetInner extends React$1.Component<{
5
5
  children: React$1.ReactNode;
6
6
  commitId: number | null;
7
7
  }> {
8
+ scheduledCommitId: number | null;
9
+ schedulePotentialScroll: () => void;
8
10
  handlePotentialScroll: () => void;
9
11
  componentDidMount(): void;
10
12
  componentDidUpdate(): void;
13
+ componentWillUnmount(): void;
11
14
  render(): React$1.ReactNode;
12
15
  }
13
16
  declare function AppRouterScrollCommitProvider({
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { decodeHashFragment } from "./hash-scroll.js";
3
- import { consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent } from "./app-router-scroll-state.js";
3
+ import { consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted } from "./app-router-scroll-state.js";
4
4
  import * as React$1 from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import * as ReactDOM from "react-dom";
@@ -46,8 +46,7 @@ function topOfElementInViewport(element, viewportHeight) {
46
46
  function getHashFragmentDomNode(hash) {
47
47
  const fragment = decodeHashFragment(hash.startsWith("#") ? hash.slice(1) : hash);
48
48
  if (fragment === "top") return document.body;
49
- const element = document.getElementById(fragment) ?? document.getElementsByName(fragment)[0];
50
- return element instanceof HTMLElement ? element : null;
49
+ return document.getElementById(fragment) ?? document.getElementsByName(fragment)[0] ?? null;
51
50
  }
52
51
  function isInDocumentHead(node) {
53
52
  const head = node.ownerDocument?.head;
@@ -55,7 +54,7 @@ function isInDocumentHead(node) {
55
54
  }
56
55
  function findNextScrollTarget(node) {
57
56
  if (!(node instanceof Element)) return null;
58
- if (isInDocumentHead(node)) return { kind: "document-top" };
57
+ if (isInDocumentHead(node)) return null;
59
58
  let target = node;
60
59
  while (!(target instanceof HTMLElement) || shouldSkipElement(target)) {
61
60
  if (target.nextElementSibling === null) return null;
@@ -82,34 +81,45 @@ function scrollToElement(target, hash) {
82
81
  });
83
82
  }
84
83
  var AppRouterScrollTargetInner = class extends React$1.Component {
84
+ scheduledCommitId = null;
85
+ schedulePotentialScroll = () => {
86
+ const commitId = this.props.commitId;
87
+ this.scheduledCommitId = commitId;
88
+ queueMicrotask(() => {
89
+ if (this.scheduledCommitId !== commitId) return;
90
+ this.handlePotentialScroll();
91
+ });
92
+ };
85
93
  handlePotentialScroll = () => {
86
94
  const intent = getPendingAppRouterScrollIntent();
87
95
  if (intent === null) return;
88
96
  if (this.props.commitId === null || intent.commitId !== this.props.commitId) return;
89
- let target;
90
- if (intent.hash !== null) {
91
- target = getHashFragmentDomNode(intent.hash);
92
- if (target === null) return;
93
- } else {
94
- const next = findNextScrollTarget(findDOMNode(this));
95
- if (next === null) return;
96
- if (next.kind === "document-top") {
97
- if (consumeAppRouterScrollIntent(intent, this.props.commitId) === null) return;
98
- document.documentElement.scrollTop = 0;
97
+ let node;
98
+ if (intent.hash !== null) node = getHashFragmentDomNode(intent.hash);
99
+ else node = null;
100
+ if (node === null) {
101
+ node = findDOMNode(this);
102
+ if (node !== null && isInDocumentHead(node)) {
103
+ markAppRouterScrollIntentHeadHoisted(intent, this.props.commitId);
99
104
  return;
100
105
  }
101
- target = next.element;
102
106
  }
107
+ const next = findNextScrollTarget(node);
108
+ if (next === null) return;
109
+ const target = next.element;
103
110
  const consumed = consumeAppRouterScrollIntent(intent, this.props.commitId);
104
111
  if (consumed === null) return;
105
112
  scrollToElement(target, consumed.hash);
106
- target.focus({ preventScroll: true });
113
+ target.focus();
107
114
  };
108
115
  componentDidMount() {
109
- this.handlePotentialScroll();
116
+ this.schedulePotentialScroll();
110
117
  }
111
118
  componentDidUpdate() {
112
- this.handlePotentialScroll();
119
+ this.schedulePotentialScroll();
120
+ }
121
+ componentWillUnmount() {
122
+ this.scheduledCommitId = null;
113
123
  }
114
124
  render() {
115
125
  return this.props.children;
@@ -2,17 +2,28 @@ import React from "react";
2
2
 
3
3
  //#region src/shims/before-interactive-context.d.ts
4
4
  /**
5
- * Inline `<Script strategy="beforeInteractive">` content captured during SSR.
5
+ * A `<Script strategy="beforeInteractive">` captured during SSR.
6
6
  *
7
7
  * The Script shim hands these records to the SSR pipeline through
8
8
  * `BeforeInteractiveContext` instead of rendering the `<script>` tag inline.
9
9
  * The pipeline then emits the captured tag immediately after `<head>` opens,
10
10
  * so the script runs before any React-hoisted stylesheets or modulepreload
11
11
  * links. Matches the standard no-flash dark-mode pattern.
12
+ *
13
+ * Both inline (`children`/`dangerouslySetInnerHTML`) and external (`src`)
14
+ * beforeInteractive scripts flow through here, mirroring Next.js which registers
15
+ * inline and src beforeInteractive scripts equally in the App Router runtime
16
+ * (`(self.__next_s=...).push(...)`). An entry has `src` set for external
17
+ * scripts and `innerHTML` set for inline scripts (never both).
12
18
  */
13
19
  type BeforeInteractiveInlineScript = {
14
- /** Optional id attribute. */id?: string; /** Pre-escaped inline content (already passed through `escapeInlineContent`). */
15
- innerHTML: string; /** Nonce to emit on the `<script>` tag, when CSP is enabled. */
20
+ /** Optional id attribute. */id?: string; /** External script URL. Set for src beforeInteractive scripts. */
21
+ src?: string;
22
+ /**
23
+ * Pre-escaped inline content (already passed through `escapeInlineContent`).
24
+ * Set for inline beforeInteractive scripts; omitted for src scripts.
25
+ */
26
+ innerHTML?: string; /** Nonce to emit on the `<script>` tag, when CSP is enabled. */
16
27
  nonce?: string;
17
28
  /**
18
29
  * Additional HTML attributes to emit on the tag. Booleans render as the
@@ -1,6 +1,7 @@
1
1
  import { getOrCreateAls } from "./internal/als-registry.js";
2
2
  import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation } from "./unified-request-context.js";
3
3
  import { VINEXT_RSC_MARKER_HEADER } from "../server/headers.js";
4
+ import { trackPprFallbackShellCacheTask } from "./ppr-fallback-shell.js";
4
5
  import { markDynamicUsage } from "./headers.js";
5
6
  import { _registerCacheContextAccessor, _setRequestScopedCacheLife, cacheLifeProfiles, getDataCacheHandler } from "./cache.js";
6
7
  import { addCollectedRequestTags, getCurrentFetchSoftTags } from "./fetch-cache.js";
@@ -243,7 +244,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
243
244
  const cacheVariant = variant ?? "";
244
245
  const omitAppPageSearchParamsFromFirstArg = options.appPageDefaultExport === true;
245
246
  const isDev = typeof process !== "undefined" && process.env.NODE_ENV === "development";
246
- const cachedFn = async (...args) => {
247
+ const cachedFn = (...args) => trackPprFallbackShellCacheTask(async () => {
247
248
  const rsc = await getRscModule();
248
249
  const keySeed = getUseCacheKeySeed();
249
250
  let cacheKey;
@@ -320,7 +321,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
320
321
  });
321
322
  } catch {}
322
323
  return result;
323
- };
324
+ }, cacheVariant);
324
325
  Object.defineProperty(cachedFn, "length", {
325
326
  value: fn.length,
326
327
  configurable: true