vinext 1.0.1 → 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 (334) hide show
  1. package/README.md +93 -10
  2. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  3. package/dist/build/app-page-static-eligibility.js +82 -0
  4. package/dist/build/client-build-config.d.ts +10 -0
  5. package/dist/build/client-build-config.js +18 -2
  6. package/dist/build/pages-client-assets-module.d.ts +8 -0
  7. package/dist/build/pages-client-assets-module.js +21 -1
  8. package/dist/build/prerender-paths.js +8 -81
  9. package/dist/build/prerender.js +33 -8
  10. package/dist/build/run-prerender.js +1 -1
  11. package/dist/build/server-manifest.d.ts +7 -0
  12. package/dist/build/server-manifest.js +11 -1
  13. package/dist/build/ssr-manifest.d.ts +2 -0
  14. package/dist/build/ssr-manifest.js +6 -2
  15. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  16. package/dist/cache/cache-adapters-virtual.js +3 -3
  17. package/dist/check.js +7 -6
  18. package/dist/cli.js +2 -1
  19. package/dist/client/client-redirect-matcher.d.ts +14 -0
  20. package/dist/client/client-redirect-matcher.js +37 -0
  21. package/dist/client/client-redirects.d.ts +27 -0
  22. package/dist/client/client-redirects.js +28 -0
  23. package/dist/client/client-rewrites.d.ts +2 -1
  24. package/dist/client/client-rewrites.js +1 -1
  25. package/dist/client/navigation-runtime.d.ts +11 -0
  26. package/dist/client/navigation-runtime.js +1 -1
  27. package/dist/config/config-matchers.d.ts +26 -11
  28. package/dist/config/config-matchers.js +81 -82
  29. package/dist/config/destination-params.d.ts +47 -0
  30. package/dist/config/destination-params.js +116 -0
  31. package/dist/config/next-config.d.ts +14 -0
  32. package/dist/config/next-config.js +21 -3
  33. package/dist/entries/app-rsc-entry.d.ts +6 -0
  34. package/dist/entries/app-rsc-entry.js +84 -49
  35. package/dist/entries/pages-client-entry.js +3 -1
  36. package/dist/entries/pages-server-entry.js +13 -37
  37. package/dist/index.d.ts +7 -2
  38. package/dist/index.js +193 -73
  39. package/dist/init-cloudflare.d.ts +9 -0
  40. package/dist/init-cloudflare.js +107 -31
  41. package/dist/init-platform.d.ts +4 -0
  42. package/dist/init-platform.js +27 -2
  43. package/dist/init.d.ts +1 -0
  44. package/dist/init.js +67 -3
  45. package/dist/plugins/ast-scope.d.ts +7 -0
  46. package/dist/plugins/ast-scope.js +59 -3
  47. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  48. package/dist/plugins/cache-flight-codec.js +68 -0
  49. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  50. package/dist/plugins/commonjs-esm-facade.js +80 -0
  51. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  52. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  53. package/dist/plugins/fonts.d.ts +8 -11
  54. package/dist/plugins/fonts.js +71 -34
  55. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  56. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  57. package/dist/plugins/middleware-server-only.d.ts +5 -4
  58. package/dist/plugins/middleware-server-only.js +8 -5
  59. package/dist/plugins/pages-node-externals.js +1 -1
  60. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  61. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  62. package/dist/plugins/strip-server-exports.js +262 -98
  63. package/dist/plugins/transform-result.d.ts +9 -5
  64. package/dist/plugins/transitive-externals.js +1 -1
  65. package/dist/plugins/typeof-window.d.ts +2 -2
  66. package/dist/plugins/use-cache-callable.js +8 -2
  67. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  68. package/dist/plugins/worker-server-only-imports.js +31 -0
  69. package/dist/plugins/worker-use-server.d.ts +25 -0
  70. package/dist/plugins/worker-use-server.js +129 -0
  71. package/dist/routing/file-matcher.d.ts +1 -0
  72. package/dist/routing/file-matcher.js +1 -1
  73. package/dist/server/api-handler.d.ts +1 -0
  74. package/dist/server/api-handler.js +19 -8
  75. package/dist/server/app-browser-action-result.d.ts +1 -1
  76. package/dist/server/app-browser-action-result.js +9 -2
  77. package/dist/server/app-browser-entry.js +11 -7
  78. package/dist/server/app-browser-history-controller.d.ts +2 -0
  79. package/dist/server/app-browser-history-controller.js +8 -0
  80. package/dist/server/app-browser-interception-context.js +26 -2
  81. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  82. package/dist/server/app-browser-navigation-controller.js +94 -11
  83. package/dist/server/app-browser-server-action-client.js +1 -1
  84. package/dist/server/app-fallback-renderer.d.ts +2 -0
  85. package/dist/server/app-fallback-renderer.js +1 -0
  86. package/dist/server/app-history-state.d.ts +3 -0
  87. package/dist/server/app-history-state.js +7 -0
  88. package/dist/server/app-middleware.d.ts +11 -0
  89. package/dist/server/app-middleware.js +19 -3
  90. package/dist/server/app-optimistic-routing.js +1 -1
  91. package/dist/server/app-page-boundary-render.d.ts +5 -0
  92. package/dist/server/app-page-boundary-render.js +6 -4
  93. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  94. package/dist/server/app-page-cache-finalizer.js +41 -7
  95. package/dist/server/app-page-cache-render.d.ts +18 -0
  96. package/dist/server/app-page-cache-render.js +63 -43
  97. package/dist/server/app-page-cache.d.ts +6 -1
  98. package/dist/server/app-page-cache.js +28 -8
  99. package/dist/server/app-page-dispatch.d.ts +12 -2
  100. package/dist/server/app-page-dispatch.js +51 -33
  101. package/dist/server/app-page-element-builder.d.ts +7 -0
  102. package/dist/server/app-page-element-builder.js +27 -42
  103. package/dist/server/app-page-execution.d.ts +15 -12
  104. package/dist/server/app-page-execution.js +30 -40
  105. package/dist/server/app-page-head.d.ts +5 -0
  106. package/dist/server/app-page-head.js +9 -3
  107. package/dist/server/app-page-ppr-runtime.js +1 -1
  108. package/dist/server/app-page-probe.d.ts +0 -109
  109. package/dist/server/app-page-probe.js +3 -136
  110. package/dist/server/app-page-render-identity.d.ts +0 -1
  111. package/dist/server/app-page-render-identity.js +1 -1
  112. package/dist/server/app-page-render-observation.js +3 -3
  113. package/dist/server/app-page-render.d.ts +13 -5
  114. package/dist/server/app-page-render.js +219 -119
  115. package/dist/server/app-page-request.d.ts +0 -1
  116. package/dist/server/app-page-request.js +54 -17
  117. package/dist/server/app-page-response.d.ts +4 -3
  118. package/dist/server/app-page-response.js +4 -3
  119. package/dist/server/app-page-route-wiring.d.ts +17 -5
  120. package/dist/server/app-page-route-wiring.js +57 -35
  121. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  122. package/dist/server/app-page-rsc-render-status.js +54 -0
  123. package/dist/server/app-page-stream.d.ts +15 -8
  124. package/dist/server/app-page-stream.js +6 -0
  125. package/dist/server/app-pages-bridge.d.ts +1 -1
  126. package/dist/server/app-pages-bridge.js +2 -3
  127. package/dist/server/app-prerender-endpoints.js +9 -6
  128. package/dist/server/app-request-stage-dispatch.js +7 -3
  129. package/dist/server/app-request-stage-independent-entry.js +29 -9
  130. package/dist/server/app-response-header-provenance.d.ts +2 -0
  131. package/dist/server/app-response-header-provenance.js +10 -4
  132. package/dist/server/app-response-stage-entry.js +10 -2
  133. package/dist/server/app-route-handler-cache.d.ts +1 -0
  134. package/dist/server/app-route-handler-cache.js +1 -0
  135. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  136. package/dist/server/app-route-handler-dispatch.js +6 -3
  137. package/dist/server/app-route-handler-execution.d.ts +1 -0
  138. package/dist/server/app-route-handler-execution.js +1 -0
  139. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  140. package/dist/server/app-route-handler-runtime.js +45 -24
  141. package/dist/server/app-router-entry.js +27 -8
  142. package/dist/server/app-rsc-cache-busting.js +1 -1
  143. package/dist/server/app-rsc-errors.js +20 -16
  144. package/dist/server/app-rsc-handler.d.ts +11 -0
  145. package/dist/server/app-rsc-handler.js +291 -125
  146. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  147. package/dist/server/app-rsc-request-normalization.js +4 -2
  148. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  149. package/dist/server/app-rsc-response-stage.js +10 -6
  150. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  151. package/dist/server/app-rsc-route-matching.js +36 -19
  152. package/dist/server/app-segment-config.d.ts +2 -1
  153. package/dist/server/app-server-action-execution.d.ts +2 -1
  154. package/dist/server/app-server-action-execution.js +62 -23
  155. package/dist/server/app-ssr-entry.d.ts +3 -2
  156. package/dist/server/app-ssr-entry.js +8 -5
  157. package/dist/server/app-ssr-stream.d.ts +6 -1
  158. package/dist/server/app-ssr-stream.js +34 -15
  159. package/dist/server/app-worker-stages.d.ts +7 -2
  160. package/dist/server/app-worker-stages.js +4 -4
  161. package/dist/server/cacheability-request.js +11 -7
  162. package/dist/server/client-build-files.d.ts +16 -0
  163. package/dist/server/client-build-files.js +36 -0
  164. package/dist/server/client-reuse-manifest.js +1 -1
  165. package/dist/server/config-headers.js +2 -0
  166. package/dist/server/dev-server.js +50 -29
  167. package/dist/server/dev-stack-sourcemap.js +18 -23
  168. package/dist/server/document-initial-head.d.ts +0 -11
  169. package/dist/server/document-initial-head.js +1 -1
  170. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  171. package/dist/server/forwarded-middleware-context.js +22 -0
  172. package/dist/server/headers.d.ts +18 -1
  173. package/dist/server/headers.js +19 -2
  174. package/dist/server/http-conditional.d.ts +11 -0
  175. package/dist/server/http-conditional.js +10 -1
  176. package/dist/server/http-error-responses.d.ts +1 -1
  177. package/dist/server/image-optimization.d.ts +54 -0
  178. package/dist/server/image-optimization.js +118 -9
  179. package/dist/server/isr-cache.d.ts +10 -0
  180. package/dist/server/isr-cache.js +43 -8
  181. package/dist/server/metadata-route-response.js +9 -8
  182. package/dist/server/middleware-matcher-pattern.js +8 -4
  183. package/dist/server/middleware-matcher.d.ts +2 -0
  184. package/dist/server/middleware-matcher.js +23 -2
  185. package/dist/server/middleware-response-headers.js +2 -0
  186. package/dist/server/middleware-runtime.d.ts +13 -0
  187. package/dist/server/middleware-runtime.js +33 -20
  188. package/dist/server/middleware.d.ts +1 -1
  189. package/dist/server/middleware.js +3 -1
  190. package/dist/server/multi-stage.d.ts +19 -0
  191. package/dist/server/navigation-planner.js +1 -1
  192. package/dist/server/node-response-signal.d.ts +16 -0
  193. package/dist/server/node-response-signal.js +26 -0
  194. package/dist/server/normalize-path.d.ts +0 -13
  195. package/dist/server/normalize-path.js +1 -34
  196. package/dist/server/open-redirect.d.ts +47 -0
  197. package/dist/server/open-redirect.js +74 -1
  198. package/dist/server/pages-api-route.d.ts +3 -2
  199. package/dist/server/pages-api-route.js +4 -3
  200. package/dist/server/pages-cache-tag.d.ts +12 -0
  201. package/dist/server/pages-cache-tag.js +19 -0
  202. package/dist/server/pages-client-assets.d.ts +2 -0
  203. package/dist/server/pages-data-route.d.ts +10 -0
  204. package/dist/server/pages-data-route.js +19 -2
  205. package/dist/server/pages-document-asset-props.d.ts +5 -1
  206. package/dist/server/pages-document-asset-props.js +13 -5
  207. package/dist/server/pages-document-splice.d.ts +19 -0
  208. package/dist/server/pages-document-splice.js +55 -0
  209. package/dist/server/pages-i18n.d.ts +12 -1
  210. package/dist/server/pages-i18n.js +18 -4
  211. package/dist/server/pages-node-compat.d.ts +16 -5
  212. package/dist/server/pages-node-compat.js +20 -12
  213. package/dist/server/pages-page-data.d.ts +32 -24
  214. package/dist/server/pages-page-data.js +50 -120
  215. package/dist/server/pages-page-handler.d.ts +0 -6
  216. package/dist/server/pages-page-handler.js +209 -130
  217. package/dist/server/pages-page-response.d.ts +12 -6
  218. package/dist/server/pages-page-response.js +41 -34
  219. package/dist/server/pages-request-pipeline.d.ts +10 -2
  220. package/dist/server/pages-request-pipeline.js +37 -18
  221. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  222. package/dist/server/pages-request-stage-entry.js +79 -16
  223. package/dist/server/pages-response-stage-entry.js +4 -0
  224. package/dist/server/pages-revalidate.js +1 -1
  225. package/dist/server/pages-router-entry.js +4 -3
  226. package/dist/server/prerender-manifest.d.ts +3 -3
  227. package/dist/server/prerender-manifest.js +4 -3
  228. package/dist/server/prerender-work-unit-setup.js +4 -0
  229. package/dist/server/prod-server.d.ts +20 -7
  230. package/dist/server/prod-server.js +343 -129
  231. package/dist/server/proper-error.d.ts +4 -0
  232. package/dist/server/proper-error.js +31 -0
  233. package/dist/server/request-pipeline.d.ts +24 -9
  234. package/dist/server/request-pipeline.js +24 -9
  235. package/dist/server/response-compression.d.ts +43 -0
  236. package/dist/server/response-compression.js +111 -0
  237. package/dist/server/revalidation-request.d.ts +1 -0
  238. package/dist/server/revalidation-request.js +1 -1
  239. package/dist/server/rsc-stream-hints.js +31 -2
  240. package/dist/server/seed-cache.d.ts +1 -0
  241. package/dist/server/seed-cache.js +15 -11
  242. package/dist/server/server-reference-loader.d.ts +4 -0
  243. package/dist/server/server-reference-loader.js +29 -0
  244. package/dist/server/socket-error-backstop.d.ts +23 -1
  245. package/dist/server/socket-error-backstop.js +69 -17
  246. package/dist/server/static-file-signal.d.ts +15 -4
  247. package/dist/server/static-file-signal.js +46 -12
  248. package/dist/server/worker-image-optimization.d.ts +34 -0
  249. package/dist/server/worker-image-optimization.js +86 -0
  250. package/dist/server/worker-stages.d.ts +1 -1
  251. package/dist/server/worker-stages.js +2 -2
  252. package/dist/server/worker-utils.d.ts +8 -3
  253. package/dist/server/worker-utils.js +12 -7
  254. package/dist/shims/app-router-scroll.js +4 -1
  255. package/dist/shims/app-router-tree-context.d.ts +5 -0
  256. package/dist/shims/app-router-tree-context.js +27 -0
  257. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  258. package/dist/shims/cache-adapter-instantiate.js +118 -0
  259. package/dist/shims/cache-callable-runtime.d.ts +6 -42
  260. package/dist/shims/cache-callable-runtime.js +65 -174
  261. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  262. package/dist/shims/cache-flight-arguments.js +56 -0
  263. package/dist/shims/cache-handler.js +1 -9
  264. package/dist/shims/cache-request-state.d.ts +15 -0
  265. package/dist/shims/cache-request-state.js +31 -8
  266. package/dist/shims/cache-runtime.d.ts +12 -20
  267. package/dist/shims/cache-runtime.js +166 -225
  268. package/dist/shims/cache.d.ts +4 -4
  269. package/dist/shims/cache.js +44 -16
  270. package/dist/shims/cacheability-classification.d.ts +7 -0
  271. package/dist/shims/cdn-cache.d.ts +8 -0
  272. package/dist/shims/cdn-cache.js +17 -1
  273. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  274. package/dist/shims/disable-smooth-scroll.js +36 -0
  275. package/dist/shims/document-placeholders.d.ts +19 -0
  276. package/dist/shims/document-placeholders.js +45 -0
  277. package/dist/shims/document.js +21 -5
  278. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  279. package/dist/shims/dynamic-preload-chunks.js +13 -9
  280. package/dist/shims/dynamic.d.ts +1 -1
  281. package/dist/shims/dynamic.js +60 -8
  282. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  283. package/dist/shims/error-boundary-navigation.js +19 -1
  284. package/dist/shims/error-boundary.d.ts +10 -10
  285. package/dist/shims/error-boundary.js +26 -7
  286. package/dist/shims/fetch-cache-state.d.ts +66 -0
  287. package/dist/shims/fetch-cache-state.js +92 -0
  288. package/dist/shims/fetch-cache.d.ts +3 -61
  289. package/dist/shims/fetch-cache.js +161 -108
  290. package/dist/shims/form.js +1 -1
  291. package/dist/shims/head.d.ts +10 -6
  292. package/dist/shims/head.js +15 -13
  293. package/dist/shims/headers.d.ts +13 -0
  294. package/dist/shims/headers.js +47 -4
  295. package/dist/shims/image.js +2 -1
  296. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  297. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  298. package/dist/shims/internal/headers-state.d.ts +0 -1
  299. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  300. package/dist/shims/internal/work-store-scope.js +24 -0
  301. package/dist/shims/metadata.js +3 -3
  302. package/dist/shims/navigation-errors.d.ts +6 -0
  303. package/dist/shims/navigation-errors.js +11 -1
  304. package/dist/shims/navigation.d.ts +7 -0
  305. package/dist/shims/navigation.js +45 -9
  306. package/dist/shims/request-context.d.ts +11 -0
  307. package/dist/shims/request-state-types.d.ts +2 -1
  308. package/dist/shims/router.js +12 -20
  309. package/dist/shims/server.d.ts +16 -0
  310. package/dist/shims/server.js +24 -4
  311. package/dist/shims/thenable-params.d.ts +4 -0
  312. package/dist/shims/thenable-params.js +25 -3
  313. package/dist/shims/unified-request-context.d.ts +1 -1
  314. package/dist/shims/unified-request-context.js +1 -0
  315. package/dist/shims/url-utils.d.ts +2 -0
  316. package/dist/shims/url-utils.js +3 -1
  317. package/dist/utils/client-runtime-metadata.js +1 -1
  318. package/dist/utils/hash.d.ts +7 -0
  319. package/dist/utils/hash.js +11 -1
  320. package/dist/utils/mdx-scan.d.ts +5 -0
  321. package/dist/utils/mdx-scan.js +8 -1
  322. package/dist/utils/package-name.d.ts +6 -0
  323. package/dist/utils/package-name.js +12 -1
  324. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  325. package/dist/utils/plugin-rsc-version.js +15 -0
  326. package/dist/utils/prerender-output-paths.js +15 -4
  327. package/dist/utils/public-file-path.d.ts +10 -2
  328. package/dist/utils/public-file-path.js +13 -9
  329. package/dist/utils/public-routes.js +2 -2
  330. package/dist/utils/react-version.d.ts +3 -0
  331. package/dist/utils/react-version.js +1 -1
  332. package/dist/utils/regex-safety.d.ts +10 -0
  333. package/dist/utils/regex-safety.js +70 -18
  334. package/package.json +9 -5
@@ -48,7 +48,10 @@ const VINEXT_PRERENDER_READINESS_PATH = "/__vinext/prerender/readiness";
48
48
  const VINEXT_PRERENDER_READINESS_HEADER = "X-Vinext-Prerender-Readiness";
49
49
  /** TPR (Tailored Per-Request) revalidation interval in seconds. */
50
50
  const VINEXT_REVALIDATE_HEADER = "x-vinext-revalidate";
51
- /** Actual Pages cache tag regenerated by an authenticated on-demand request. */
51
+ /**
52
+ * Actual Pages cache tag regenerated by an authenticated on-demand request.
53
+ * URI-encoded, because a decoded path can end in whitespace that Headers trims.
54
+ */
52
55
  const VINEXT_REVALIDATED_CACHE_TAG_HEADER = "x-vinext-revalidated-cache-tag";
53
56
  /** Marker on cached ISR entries indicating RSC payload (value "1"). */
54
57
  const VINEXT_RSC_MARKER_HEADER = "x-vinext-rsc";
@@ -58,6 +61,12 @@ const VINEXT_PARAMS_HEADER = "X-Vinext-Params";
58
61
  const VINEXT_DYNAMIC_STALE_TIME_HEADER = "X-Vinext-Dynamic-Stale-Time";
59
62
  /** Marks an RSC body carrying completion metadata after the Flight payload. */
60
63
  const VINEXT_RSC_COMPLETION_METADATA_HEADER = "X-Vinext-Rsc-Completion-Metadata";
64
+ /**
65
+ * Carries the stored status of an App page RSC response whose 401, 403 or 404
66
+ * is its page's notFound(), forbidden() or unauthorized(). Middleware and
67
+ * config headers can't set it, and the request stage strips it.
68
+ */
69
+ const VINEXT_SPECIAL_ERROR_STATUS_HEADER = "x-vinext-special-error-status";
61
70
  /** URL-encoded rendered path and search after middleware/config rewrites. */
62
71
  const VINEXT_RENDERED_PATH_AND_SEARCH_HEADER = "X-Vinext-Rendered-Path-And-Search";
63
72
  /** Prerender-only JSON side channel carrying request cacheLife metadata. */
@@ -66,6 +75,14 @@ const VINEXT_PRERENDER_CACHE_LIFE_HEADER = "x-vinext-prerender-cache-life";
66
75
  const VINEXT_PRERENDER_REWRITTEN_HEADER = "x-vinext-prerender-rewritten";
67
76
  /** Marks a local prerender-server 500 that originated from a thrown render error. */
68
77
  const VINEXT_PRERENDER_RENDER_ERROR_HEADER = "x-vinext-prerender-render-error";
78
+ /**
79
+ * Prerender-only marker: the response's 401, 403, 404 or redirect is its page's
80
+ * notFound(), forbidden(), unauthorized() or redirect(), which escaped the shell.
81
+ * Its value is the JSON of the headers to store: a redirect's own location, or
82
+ * `{}`. Only the prerender server sets it, from the special error the page
83
+ * recorded on its ctx, and it drops one that the response carries.
84
+ */
85
+ const VINEXT_PRERENDER_SPECIAL_ERROR_HEADER = "x-vinext-prerender-special-error";
69
86
  /** Internal marker persisted only inside metadata-route APP_ROUTE cache values. */
70
87
  const VINEXT_METADATA_ROUTE_CACHE_HEADER = "x-vinext-metadata-route-cache";
71
88
  /** Disabled-by-default client hint describing already-held App Router payload entries. */
@@ -175,4 +192,4 @@ const VINEXT_INTERNAL_HEADERS = [
175
192
  VINEXT_TRACE_ROUTE_HEADER.toLowerCase()
176
193
  ];
177
194
  //#endregion
178
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_CACHEABILITY_PROBE_ROUTE_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_EXPECTED_WORKER_VERSION_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERCEPTION_ID_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_REWRITTEN_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_COMPLETION_METADATA_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_STATE_FINGERPRINT_HEADER, VINEXT_RSC_VARY_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER, VINEXT_TRACE_BUFFERED_BODY_HEADER, VINEXT_TRACE_ERROR_HEADER, VINEXT_TRACE_ROUTE_HEADER, isVinextRscVaryField };
195
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHEABILITY_PROBE_HEADER, VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_CACHEABILITY_PROBE_ROUTE_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_EXPECTED_WORKER_VERSION_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERCEPTION_ID_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_READINESS_HEADER, VINEXT_PRERENDER_READINESS_PATH, VINEXT_PRERENDER_RENDER_ERROR_HEADER, VINEXT_PRERENDER_REWRITTEN_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECIAL_ERROR_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_COMPLETION_METADATA_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_STATE_FINGERPRINT_HEADER, VINEXT_RSC_VARY_HEADER, VINEXT_SPECIAL_ERROR_STATUS_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER, VINEXT_TRACE_BUFFERED_BODY_HEADER, VINEXT_TRACE_ERROR_HEADER, VINEXT_TRACE_ROUTE_HEADER, isVinextRscVaryField };
@@ -6,6 +6,17 @@
6
6
  * validator with the same opaque tag compare equal.
7
7
  */
8
8
  export declare function matchesIfNoneMatch(ifNoneMatch: string | undefined, etag: string): boolean;
9
+ /**
10
+ * Whether a page answers its request with a 304, as Next.js's
11
+ * `sendEtagResponse` does with `fresh(req.headers, { etag })`. Next.js gives a
12
+ * page no Last-Modified, so a request with If-Modified-Since gets the page, and
13
+ * a request `Cache-Control: no-cache` always does.
14
+ */
15
+ export declare function isPageNotModified(request: {
16
+ cacheControl?: string;
17
+ ifModifiedSince?: string;
18
+ ifNoneMatch?: string;
19
+ }, etag: string): boolean;
9
20
  /** Test an If-Match field using Next.js's weak/strong-equivalent comparison. */
10
21
  export declare function matchesIfMatch(ifMatch: string | undefined, etag: string | undefined): boolean;
11
22
  type StaticConditionalHeaders = {
@@ -31,6 +31,15 @@ function matchesIfNoneMatch(ifNoneMatch, etag) {
31
31
  }
32
32
  return sawTag && matched;
33
33
  }
34
+ /**
35
+ * Whether a page answers its request with a 304, as Next.js's
36
+ * `sendEtagResponse` does with `fresh(req.headers, { etag })`. Next.js gives a
37
+ * page no Last-Modified, so a request with If-Modified-Since gets the page, and
38
+ * a request `Cache-Control: no-cache` always does.
39
+ */
40
+ function isPageNotModified(request, etag) {
41
+ return !request.ifModifiedSince && !/(?:^|,)\s*no-cache\s*(?:,|$)/.test(request.cacheControl ?? "") && matchesIfNoneMatch(request.ifNoneMatch, etag);
42
+ }
34
43
  /** Test an If-Match field using Next.js's weak/strong-equivalent comparison. */
35
44
  function matchesIfMatch(ifMatch, etag) {
36
45
  if (!ifMatch) return false;
@@ -114,4 +123,4 @@ function joinHeader(value) {
114
123
  return Array.isArray(value) ? value.join(", ") : value;
115
124
  }
116
125
  //#endregion
117
- export { evaluateStaticPreconditions, matchesIfMatch, matchesIfNoneMatch };
126
+ export { evaluateStaticPreconditions, isPageNotModified, matchesIfMatch, matchesIfNoneMatch };
@@ -9,7 +9,7 @@
9
9
  * `new Response(...)` boilerplate.
10
10
  *
11
11
  * Sites with route-specific bodies (e.g. `"404 - API route not found"`,
12
- * `"Image not found"`, generated worker templates) intentionally remain inline
12
+ * generated worker templates) intentionally remain inline
13
13
  * because their bodies are either tested-against fixtures or run inside template
14
14
  * strings that have no access to runtime imports.
15
15
  *
@@ -82,6 +82,44 @@ export type ParseImageParamsOptions = {
82
82
  isDev?: boolean;
83
83
  };
84
84
  export declare function resolveDevImageRedirect(requestUrl: URL, allowedWidths?: number[], allowedQualities?: number[], options?: ParseImageParamsOptions): string | null;
85
+ export type ImageOptimizationSignal = {
86
+ /** Middleware response headers to send with the image. */
87
+ middlewareHeaders: Headers;
88
+ /** The request headers after middleware's overrides, when it set any. */
89
+ requestHeaders: Headers | null;
90
+ /** The image query after middleware and rewrites. */
91
+ search: string;
92
+ };
93
+ /**
94
+ * Mark the App Router's image endpoint response once the request has passed
95
+ * middleware and rewrites. A host that serves images itself (the Node
96
+ * production server) replaces the response with the image; other hosts send
97
+ * the response unchanged. The symbol carries the signal across the built RSC
98
+ * module boundary, so response headers cannot forge it.
99
+ */
100
+ export declare function markImageOptimizationSignal(response: Response, signal: ImageOptimizationSignal): Response;
101
+ /** Return the signal of a framework-created image endpoint response. */
102
+ export declare function readImageOptimizationSignal(response: Response): ImageOptimizationSignal | null;
103
+ /**
104
+ * Carry a signal through the response-stage transport, which drops the
105
+ * symbol. The signal rides in the body of the bodiless carrier, with the
106
+ * carrier's status in the token-authenticated marker.
107
+ */
108
+ export declare function serializeImageOptimizationSignalForTransport(response: Response, token: string): Response;
109
+ /** Restore and consume a signal returned by the trusted response-stage wrapper. */
110
+ export declare function restoreImageOptimizationSignalFromTransport(response: Response, token: string): Promise<Response>;
111
+ /**
112
+ * Whether a local source's pipeline status carries the file's bytes. A no-body
113
+ * success such as 204 serves no file, which Next.js rejects as an invalid
114
+ * image.
115
+ */
116
+ export declare function isImageSourceBodyStatus(status: number): boolean;
117
+ /**
118
+ * Body Next.js answers, with a 400, for a local source that yields no image:
119
+ * a missing file, or anything middleware or routing answers in its place.
120
+ */
121
+ export declare const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
122
+ export declare function invalidImageSourceResponse(): Response;
85
123
  /**
86
124
  * Parse and validate image optimization query parameters.
87
125
  * Returns null if the request is malformed.
@@ -117,6 +155,22 @@ export declare const IMAGE_CONTENT_SECURITY_POLICY = "script-src 'none'; frame-s
117
155
  * Returns false for SVG (unless dangerouslyAllowSVG is true), HTML, or any non-image type.
118
156
  */
119
157
  export declare function isSafeImageContentType(contentType: string | null, dangerouslyAllowSVG?: boolean): boolean;
158
+ /**
159
+ * Cache state for a successful image response, sent as both `X-Nextjs-Cache`
160
+ * and `X-Vinext-Cache`.
161
+ *
162
+ * Next.js keeps optimized images in an image cache and labels each 200 with
163
+ * where the bytes came from: `MISS` when the optimizer ran for this request,
164
+ * `HIT` or `STALE` when the cache served them. vinext has no image cache on any
165
+ * runtime, so every successful response is transformed (or passed through) from
166
+ * the source for the request that receives it. Next.js behaves the same way
167
+ * when its image cache is disabled (`images.maximumDiskCacheSize: 0`): every
168
+ * 200 is a `MISS`. `HIT` and `STALE` need a cache of optimized images.
169
+ *
170
+ * Next.js sets the header after its ETag check, so a 304 and error responses
171
+ * go out without it. Only full image responses carry either header here too.
172
+ */
173
+ export declare const IMAGE_RESPONSE_CACHE_STATE = "MISS";
120
174
  /**
121
175
  * Handlers for image optimization I/O operations.
122
176
  * Workers provide these callbacks to adapt their specific bindings.
@@ -1,4 +1,5 @@
1
1
  import { badRequestResponse } from "./http-error-responses.js";
2
+ import { setCacheStateHeaders } from "./cache-headers.js";
2
3
  //#region src/server/image-optimization.ts
3
4
  /**
4
5
  * Image optimization request handler.
@@ -77,6 +78,93 @@ function resolveDevImageRedirect(requestUrl, allowedWidths = [...DEFAULT_DEVICE_
77
78
  if (resolved.origin !== requestUrl.origin) return null;
78
79
  return resolved.pathname + resolved.search;
79
80
  }
81
+ const IMAGE_OPTIMIZATION_SIGNAL = Symbol.for("vinext.image-optimization-signal");
82
+ /**
83
+ * Mark the App Router's image endpoint response once the request has passed
84
+ * middleware and rewrites. A host that serves images itself (the Node
85
+ * production server) replaces the response with the image; other hosts send
86
+ * the response unchanged. The symbol carries the signal across the built RSC
87
+ * module boundary, so response headers cannot forge it.
88
+ */
89
+ function markImageOptimizationSignal(response, signal) {
90
+ Object.defineProperty(response, IMAGE_OPTIMIZATION_SIGNAL, { value: signal });
91
+ return response;
92
+ }
93
+ /** Return the signal of a framework-created image endpoint response. */
94
+ function readImageOptimizationSignal(response) {
95
+ const signal = Reflect.get(response, IMAGE_OPTIMIZATION_SIGNAL);
96
+ if (typeof signal !== "object" || signal === null) return null;
97
+ const { middlewareHeaders, requestHeaders, search } = signal;
98
+ return middlewareHeaders instanceof Headers && (requestHeaders === null || requestHeaders instanceof Headers) && typeof search === "string" ? {
99
+ middlewareHeaders,
100
+ requestHeaders,
101
+ search
102
+ } : null;
103
+ }
104
+ const IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER = "x-vinext-stage-image-optimization";
105
+ function isHeaderEntries(value) {
106
+ return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
107
+ }
108
+ /**
109
+ * Carry a signal through the response-stage transport, which drops the
110
+ * symbol. The signal rides in the body of the bodiless carrier, with the
111
+ * carrier's status in the token-authenticated marker.
112
+ */
113
+ function serializeImageOptimizationSignalForTransport(response, token) {
114
+ const signal = readImageOptimizationSignal(response);
115
+ if (signal === null) return response;
116
+ const headers = new Headers(response.headers);
117
+ headers.set(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER, `${token}:${response.status}`);
118
+ return new Response(JSON.stringify({
119
+ middlewareHeaders: [...signal.middlewareHeaders],
120
+ requestHeaders: signal.requestHeaders ? [...signal.requestHeaders] : null,
121
+ search: signal.search
122
+ }), { headers });
123
+ }
124
+ /** Restore and consume a signal returned by the trusted response-stage wrapper. */
125
+ async function restoreImageOptimizationSignalFromTransport(response, token) {
126
+ const marker = response.headers.get(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER);
127
+ if (marker === null || response.status < 200 || response.status > 599) return response;
128
+ const headers = new Headers(response.headers);
129
+ headers.delete(IMAGE_OPTIMIZATION_SIGNAL_TRANSPORT_HEADER);
130
+ const prefix = `${token}:`;
131
+ const status = marker.startsWith(prefix) ? Number(marker.slice(prefix.length)) : NaN;
132
+ if (!Number.isInteger(status) || status < 200 || status > 599) {
133
+ const body = response.status === 204 || response.status === 205 || response.status === 304 ? null : response.body;
134
+ return new Response(body, {
135
+ headers,
136
+ status: response.status,
137
+ statusText: response.statusText
138
+ });
139
+ }
140
+ const { middlewareHeaders, requestHeaders, search } = JSON.parse(await response.text()) ?? {};
141
+ if (!isHeaderEntries(middlewareHeaders) || requestHeaders !== null && !isHeaderEntries(requestHeaders) || typeof search !== "string") throw new Error("Invalid image optimization signal from the response stage");
142
+ return markImageOptimizationSignal(new Response(null, {
143
+ headers,
144
+ status
145
+ }), {
146
+ middlewareHeaders: new Headers(middlewareHeaders),
147
+ requestHeaders: requestHeaders ? new Headers(requestHeaders) : null,
148
+ search
149
+ });
150
+ }
151
+ /**
152
+ * Whether a local source's pipeline status carries the file's bytes. A no-body
153
+ * success such as 204 serves no file, which Next.js rejects as an invalid
154
+ * image.
155
+ */
156
+ function isImageSourceBodyStatus(status) {
157
+ return status >= 200 && status < 300 && status !== 204 && status !== 205;
158
+ }
159
+ /**
160
+ * Body Next.js answers, with a 400, for a local source that yields no image:
161
+ * a missing file, or anything middleware or routing answers in its place.
162
+ */
163
+ const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
164
+ function invalidImageSourceResponse() {
165
+ return new Response(INVALID_IMAGE_SOURCE_MESSAGE, { status: 400 });
166
+ }
167
+ const RECURSIVE_IMAGE_URL_RE = /\/_(?:next|vinext)\/image(?:$|\/)/;
80
168
  /**
81
169
  * Parse and validate image optimization query parameters.
82
170
  * Returns null if the request is malformed.
@@ -112,7 +200,9 @@ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFA
112
200
  if (!normalizedUrl.startsWith("/") || normalizedUrl.startsWith("//")) return null;
113
201
  try {
114
202
  const base = "https://localhost";
115
- if (new URL(normalizedUrl, base).origin !== base) return null;
203
+ const resolved = new URL(normalizedUrl, base);
204
+ if (resolved.origin !== base) return null;
205
+ if (RECURSIVE_IMAGE_URL_RE.test(decodeURIComponent(resolved.pathname))) return null;
116
206
  } catch {
117
207
  return null;
118
208
  }
@@ -182,11 +272,32 @@ function setImageSecurityHeaders(headers, config) {
182
272
  headers.set("X-Content-Type-Options", "nosniff");
183
273
  headers.set("Content-Disposition", config?.contentDispositionType === "attachment" ? "attachment" : "inline");
184
274
  }
185
- function createPassthroughImageResponse(source, config) {
186
- const headers = new Headers(source.headers);
275
+ /**
276
+ * Cache state for a successful image response, sent as both `X-Nextjs-Cache`
277
+ * and `X-Vinext-Cache`.
278
+ *
279
+ * Next.js keeps optimized images in an image cache and labels each 200 with
280
+ * where the bytes came from: `MISS` when the optimizer ran for this request,
281
+ * `HIT` or `STALE` when the cache served them. vinext has no image cache on any
282
+ * runtime, so every successful response is transformed (or passed through) from
283
+ * the source for the request that receives it. Next.js behaves the same way
284
+ * when its image cache is disabled (`images.maximumDiskCacheSize: 0`): every
285
+ * 200 is a `MISS`. `HIT` and `STALE` need a cache of optimized images.
286
+ *
287
+ * Next.js sets the header after its ETag check, so a 304 and error responses
288
+ * go out without it. Only full image responses carry either header here too.
289
+ */
290
+ const IMAGE_RESPONSE_CACHE_STATE = "MISS";
291
+ /** Headers shared by every successful image response, transformed or not. */
292
+ function setImageResponseHeaders(headers, config) {
187
293
  headers.set("Cache-Control", IMAGE_CACHE_CONTROL);
188
294
  headers.set("Vary", "Accept");
189
295
  setImageSecurityHeaders(headers, config);
296
+ setCacheStateHeaders(headers, IMAGE_RESPONSE_CACHE_STATE);
297
+ }
298
+ function createPassthroughImageResponse(source, config) {
299
+ const headers = new Headers(source.headers);
300
+ setImageResponseHeaders(headers, config);
190
301
  return new Response(source.body, {
191
302
  status: 200,
192
303
  headers
@@ -204,7 +315,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
204
315
  if (!params) return badRequestResponse();
205
316
  const { imageUrl, width, quality } = params;
206
317
  const source = await handlers.fetchAsset(imageUrl, request);
207
- if (!source.ok || !source.body) return new Response("Image not found", { status: 404 });
318
+ if (!source.ok || !source.body) return invalidImageSourceResponse();
208
319
  const format = negotiateImageFormat(request.headers.get("Accept"));
209
320
  const sourceContentType = source.headers.get("Content-Type");
210
321
  if (!isSafeImageContentType(sourceContentType, imageConfig?.dangerouslyAllowSVG)) return new Response("The requested resource is not an allowed image type", { status: 400 });
@@ -216,9 +327,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
216
327
  quality
217
328
  });
218
329
  const headers = new Headers(transformed.headers);
219
- headers.set("Cache-Control", IMAGE_CACHE_CONTROL);
220
- headers.set("Vary", "Accept");
221
- setImageSecurityHeaders(headers, imageConfig);
330
+ setImageResponseHeaders(headers, imageConfig);
222
331
  if (!isSafeImageContentType(headers.get("Content-Type"), imageConfig?.dangerouslyAllowSVG)) headers.set("Content-Type", format);
223
332
  return new Response(transformed.body, {
224
333
  status: 200,
@@ -232,7 +341,7 @@ async function handleImageOptimization(request, handlers, allowedWidths, imageCo
232
341
  } catch (e) {
233
342
  console.error("[vinext] Image fallback error, refetching source image:", e);
234
343
  const refetchedSource = await handlers.fetchAsset(imageUrl, request);
235
- if (!refetchedSource.ok || !refetchedSource.body) return new Response("Image not found", { status: 404 });
344
+ if (!refetchedSource.ok || !refetchedSource.body) return invalidImageSourceResponse();
236
345
  if (!isSafeImageContentType(refetchedSource.headers.get("Content-Type"), imageConfig?.dangerouslyAllowSVG)) return new Response("The requested resource is not an allowed image type", { status: 400 });
237
346
  return createPassthroughImageResponse(refetchedSource, imageConfig);
238
347
  }
@@ -287,4 +396,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
287
396
  }, allowedWidths, imageConfig);
288
397
  }
289
398
  //#endregion
290
- export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, isImageOptimizationPath, isSafeImageContentType, negotiateImageFormat, parseImageParams, resolveDevImageRedirect, setImageOptimizer };
399
+ export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
@@ -112,7 +112,17 @@ export declare function pagesIsrCacheKey(pathname: string, buildId?: string, i18
112
112
  * Cloudflare KV key under its 512-byte limit for long pathnames.
113
113
  */
114
114
  export declare function appIsrCacheKey(pathname: string, suffix: string, buildId?: string | undefined): string;
115
+ /**
116
+ * Compute the ISR key for a PPR fallback shell artifact, such as the HTML for
117
+ * `/en/[slug]`. Shells get their own namespace: a request for the literal path
118
+ * `/en/%5Bslug%5D` decodes to the same pathname, and its render must not be
119
+ * stored where every unknown `[slug]` reads its shell. The namespace is a
120
+ * prefix of its own rather than a part after the build ID, which can contain
121
+ * `:` and so could spell it.
122
+ */
123
+ export declare function appIsrFallbackShellKey(pathname: string, suffix: "html" | "rsc", buildId?: string | undefined): string;
115
124
  export declare function appIsrHtmlKey(pathname: string): string;
125
+ export declare function appIsrFallbackShellHtmlKey(pathname: string): string;
116
126
  /**
117
127
  * Build the ISR cache key for an RSC payload.
118
128
  *
@@ -1,15 +1,29 @@
1
1
  import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER } from "../utils/protocol-headers.js";
2
2
  import { normalizePathnameForRouteMatch } from "../routing/utils.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
+ import { isInterceptionMatchedUrlPath } from "./normalize-path.js";
4
5
  import { reportRequestError } from "./instrumentation.js";
5
6
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
6
- import { fnv1a64 } from "../utils/hash.js";
7
7
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
8
8
  import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./app-rsc-render-mode.js";
9
- import { normalizeAppPageInterceptionProofPathname } from "./app-page-render-identity.js";
10
9
  import { getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret } from "./revalidation-request.js";
10
+ import { createHash } from "node:crypto";
11
11
  //#region src/server/isr-cache.ts
12
12
  /**
13
+ * ISR (Incremental Static Regeneration) cache layer.
14
+ *
15
+ * Wraps the pluggable CacheHandler with stale-while-revalidate semantics:
16
+ * - Fresh hit: serve immediately
17
+ * - Stale hit: serve immediately + trigger background regeneration
18
+ * - Miss: render synchronously, cache, serve
19
+ *
20
+ * Background regeneration is deduped — only one regeneration per cache key
21
+ * runs at a time, preventing thundering herd on popular pages.
22
+ *
23
+ * This layer works with any CacheHandler backend (memory, Redis, KV, etc.)
24
+ * because it only uses the standard get/set interface.
25
+ */
26
+ /**
13
27
  * Get a cache entry with staleness information.
14
28
  *
15
29
  * Returns { value, isStale: false } for fresh entries,
@@ -151,12 +165,19 @@ function buildAppPageCacheValue(html, rscData, status, renderObservation, header
151
165
  function normalizeCachePathname(pathname) {
152
166
  return pathname === "/" ? "/" : pathname.replace(/\/$/, "");
153
167
  }
168
+ /**
169
+ * Digest a request-controlled key part. It must be collision resistant: a
170
+ * crafted value that shared a weaker hash with another would share its entry.
171
+ */
172
+ function digestKeyPart(value) {
173
+ return createHash("sha256").update(value).digest("hex");
174
+ }
154
175
  function buildCacheKey(prefix, pathname, suffix) {
155
176
  const normalized = normalizeCachePathname(pathname);
156
177
  const suffixPart = suffix ? `:${suffix}` : "";
157
178
  const key = `${prefix}:${normalized}${suffixPart}`;
158
179
  if (key.length <= 200) return key;
159
- return `${prefix}:__hash:${fnv1a64(normalized)}${suffixPart}`;
180
+ return `${prefix}:__hash:${digestKeyPart(normalized)}${suffixPart}`;
160
181
  }
161
182
  /**
162
183
  * Compute an ISR cache key for a given router type and pathname.
@@ -179,11 +200,25 @@ function pagesIsrCacheKey(pathname, buildId, i18nCacheVariant) {
179
200
  function appIsrCacheKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
180
201
  return buildCacheKey(buildId ? `app:v2:${buildId}` : "app:v2", pathname, suffix);
181
202
  }
203
+ /**
204
+ * Compute the ISR key for a PPR fallback shell artifact, such as the HTML for
205
+ * `/en/[slug]`. Shells get their own namespace: a request for the literal path
206
+ * `/en/%5Bslug%5D` decodes to the same pathname, and its render must not be
207
+ * stored where every unknown `[slug]` reads its shell. The namespace is a
208
+ * prefix of its own rather than a part after the build ID, which can contain
209
+ * `:` and so could spell it.
210
+ */
211
+ function appIsrFallbackShellKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
212
+ return buildCacheKey(buildId ? `app-fallback-shell:v1:${buildId}` : "app-fallback-shell:v1", pathname, suffix);
213
+ }
182
214
  function appIsrHtmlKey(pathname) {
183
215
  return appIsrCacheKey(pathname, "html");
184
216
  }
217
+ function appIsrFallbackShellHtmlKey(pathname) {
218
+ return appIsrFallbackShellKey(pathname, "html");
219
+ }
185
220
  function normalizeInterceptionContextForCacheKey(interceptionContext) {
186
- return normalizeAppPageInterceptionProofPathname(interceptionContext);
221
+ return isInterceptionMatchedUrlPath(interceptionContext) ? interceptionContext : null;
187
222
  }
188
223
  /**
189
224
  * Build the ISR cache key for an RSC payload.
@@ -200,9 +235,9 @@ function appIsrRscKey(pathname, mountedSlotsHeader, renderMode = APP_RSC_RENDER_
200
235
  const normalizedMountedSlotsHeader = normalizeMountedSlotsHeader(mountedSlotsHeader);
201
236
  const sourceVariant = interceptionContext === void 0 || interceptionContext === null ? null : normalizeInterceptionContextForCacheKey(interceptionContext);
202
237
  const variant = [
203
- sourceVariant ? `source:${fnv1a64(sourceVariant)}` : null,
204
- interceptionId ? `selector:${fnv1a64(interceptionId)}` : null,
205
- normalizedMountedSlotsHeader ? `slots:${fnv1a64(normalizedMountedSlotsHeader)}` : null,
238
+ sourceVariant ? `source:${digestKeyPart(sourceVariant)}` : null,
239
+ interceptionId ? `selector:${digestKeyPart(interceptionId)}` : null,
240
+ normalizedMountedSlotsHeader ? `slots:${digestKeyPart(normalizedMountedSlotsHeader)}` : null,
206
241
  getRscRenderModeCacheVariant(renderMode)
207
242
  ].filter((part) => part !== null).join(":");
208
243
  return appIsrCacheKey(pathname, variant ? `rsc:${variant}` : "rsc");
@@ -211,4 +246,4 @@ function appIsrRouteKey(pathname) {
211
246
  return appIsrCacheKey(pathname, pathname !== "/" && pathname.endsWith("/") ? "route:trailing-slash" : "route");
212
247
  }
213
248
  //#endregion
214
- export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
249
+ export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrFallbackShellHtmlKey, appIsrFallbackShellKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, coalesceOnDemandRevalidation, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheControl, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, pagesIsrCacheKey, resolveRouteExpireSeconds, triggerBackgroundRegeneration };
@@ -3,6 +3,7 @@ import { closeAfterResponse, createRequestContext, runWithRequestContext } from
3
3
  import { decodeMatchedParams } from "../routing/utils.js";
4
4
  import { getRequestExecutionContext } from "../shims/request-context.js";
5
5
  import { VINEXT_METADATA_ROUTE_CACHE_HEADER } from "./headers.js";
6
+ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
6
7
  import { notFoundResponse } from "./http-error-responses.js";
7
8
  import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
8
9
  import { NEVER_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy } from "./cache-control.js";
@@ -215,7 +216,7 @@ async function writeRenderedMetadataRoute(options, route, routeKey, rendered, pr
215
216
  async function runMetadataRouteRegeneration(options, route, render, routeKey, previousEntry, executionContext) {
216
217
  const requestContext = createRequestContext({ executionContext });
217
218
  try {
218
- await runWithRequestContext(requestContext, async () => {
219
+ await runWithRequestContext(requestContext, () => runInWorkStoreScope(async () => {
219
220
  ensureFetchPatch();
220
221
  beginMetadataRouteCacheability(route);
221
222
  setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
@@ -225,7 +226,7 @@ async function runMetadataRouteRegeneration(options, route, render, routeKey, pr
225
226
  } finally {
226
227
  rendered.response.body?.cancel().catch(() => {});
227
228
  }
228
- });
229
+ }));
229
230
  } finally {
230
231
  await closeAfterResponse(requestContext);
231
232
  }
@@ -512,6 +513,12 @@ async function handleMetadataRouteRequest(options) {
512
513
  decodeMatchedParams(runtimeParams);
513
514
  match.params = runtimeParams;
514
515
  }
516
+ const render = async () => {
517
+ setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
518
+ return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
519
+ };
520
+ const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
521
+ if (cached.response) return cached.response;
515
522
  if (route.isDynamic && isImageMetadataRoute(route) && functions.dynamicParams === false) {
516
523
  const validationParams = { ...rawParams };
517
524
  canonicalizeAppPageParams(validationParams);
@@ -526,12 +533,6 @@ async function handleMetadataRouteRequest(options) {
526
533
  });
527
534
  if (dynamicParamsResponse) return dynamicParamsResponse;
528
535
  }
529
- const render = async () => {
530
- setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
531
- return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
532
- };
533
- const cached = await readMatchedPrerenderedMetadataRouteResponse(options, route, render);
534
- if (cached.response) return cached.response;
535
536
  const rendered = await render();
536
537
  await writeMetadataRouteMiss(options, route, rendered, cached.cachedEntry);
537
538
  return rendered.response;
@@ -1,4 +1,4 @@
1
- import { analyzeRegexSafety, regexAtomsMayOverlap } from "../utils/regex-safety.js";
1
+ import { analyzeRegexSafety, mayHaveNamedGroups, regexAtomsMayOverlap } from "../utils/regex-safety.js";
2
2
  import { middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath } from "./middleware-path-to-regexp.js";
3
3
  //#region src/server/middleware-matcher-pattern.ts
4
4
  const MATCHER_OBJECT_KEYS = /* @__PURE__ */ new Set([
@@ -138,8 +138,11 @@ function hasOverlappingSequentialRepetition(pattern) {
138
138
  }
139
139
  return false;
140
140
  }
141
- function unsafeTokenReason(token) {
142
- const regexSafetyIssue = analyzeRegexSafety(token.pattern, { ignoreCase: true });
141
+ function unsafeTokenReason(token, namedGroups) {
142
+ const regexSafetyIssue = analyzeRegexSafety(token.pattern, {
143
+ ignoreCase: true,
144
+ namedGroups
145
+ });
143
146
  if (regexSafetyIssue) {
144
147
  if (regexSafetyIssue === "analysis budget exceeded") return `parameter "${token.name}" exceeds the regex analysis budget`;
145
148
  return `parameter "${token.name}" contains ${regexSafetyIssue}`;
@@ -150,9 +153,10 @@ function unsafeTokenReason(token) {
150
153
  return null;
151
154
  }
152
155
  function validateTokens(tokens) {
156
+ const namedGroups = tokens.some((token) => typeof token !== "string" && mayHaveNamedGroups(token.pattern));
153
157
  for (const token of tokens) {
154
158
  if (typeof token === "string") continue;
155
- const reason = unsafeTokenReason(token);
159
+ const reason = unsafeTokenReason(token, namedGroups);
156
160
  if (reason) return reason;
157
161
  }
158
162
  return null;
@@ -7,6 +7,8 @@ export type MiddlewareLocaleMatchContext = {
7
7
  defaultLocale: string;
8
8
  } | {
9
9
  kind: "internal" | "literal";
10
+ } | {
11
+ kind: "next-data";
10
12
  };
11
13
  export declare function matchesMiddleware(pathname: string, matcher: MatcherConfig | undefined, request?: Request, i18nConfig?: NextI18nConfig | null, localeContext?: MiddlewareLocaleMatchContext): boolean;
12
14
  /**
@@ -49,6 +49,7 @@ function matchesMiddlewarePathname(pathname, matcher, i18nConfig, localeContext)
49
49
  return false;
50
50
  }
51
51
  function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
52
+ if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, pattern, i18nConfig, true);
52
53
  if (!i18nConfig) return matchPattern(pathname, pattern);
53
54
  if (localeContext) {
54
55
  if (localeContext.kind === "internal") return false;
@@ -57,9 +58,25 @@ function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
57
58
  return matchPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
58
59
  }
59
60
  function matchObjectMatcher(pathname, matcher, i18nConfig, localeContext) {
61
+ if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, matcher.source, i18nConfig, matcher.locale !== false);
60
62
  if (matcher.locale !== false) return matchMatcherPattern(pathname, matcher.source, i18nConfig, localeContext);
61
63
  return matchPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
62
64
  }
65
+ function matchNextDataPattern(pathname, source, i18nConfig, localized) {
66
+ if (compiledPattern(source) === UNSAFE_MATCHER_PATTERN) return true;
67
+ return matchPattern(pathname, nextDataMatcherSource(source, i18nConfig, localized));
68
+ }
69
+ /**
70
+ * The source Next.js compiles for a matcher: an optional `_next/data/<buildId>`
71
+ * prefix, an internal locale segment under i18n, and an optional data suffix.
72
+ * Next.js also accepts App Router transport suffixes, which end in `.rsc` and
73
+ * so can never match a `.json` data URL.
74
+ * https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
75
+ */
76
+ function nextDataMatcherSource(source, i18nConfig, localized) {
77
+ const isRoot = source === "/";
78
+ return `/:nextData(_next/data/[^/]{1,})?${i18nConfig && localized ? `/:nextInternalLocale((?!_next/)[^/.]{1,})${isRoot ? "" : source}` : source}${isRoot ? `(${i18nConfig ? "|\\.json|" : ""}/?index|/?index\\.json)?` : "{(\\.json)}?"}`;
79
+ }
63
80
  function stripLocalePrefix(pathname, i18nConfig) {
64
81
  if (pathname === "/") return null;
65
82
  const lowerFirstSegment = pathname.split("/")[1]?.toLowerCase();
@@ -71,13 +88,17 @@ function stripFirstPathSegment(pathname) {
71
88
  }
72
89
  function matchPattern(pathname, pattern) {
73
90
  if (pattern === "/" && (pathname === "//" || pathname === "/?" || pathname === "/#")) return false;
91
+ const compiled = compiledPattern(pattern);
92
+ if (compiled === UNSAFE_MATCHER_PATTERN) return true;
93
+ return compiled.test(pathname);
94
+ }
95
+ function compiledPattern(pattern) {
74
96
  let cached = _mwPatternCache.get(pattern);
75
97
  if (cached === void 0) {
76
98
  cached = compileMatcherPattern(pattern);
77
99
  _mwPatternCache.set(pattern, cached);
78
100
  }
79
- if (cached === UNSAFE_MATCHER_PATTERN) return true;
80
- return cached.test(pathname);
101
+ return cached;
81
102
  }
82
103
  function compileMatcherPattern(pattern) {
83
104
  const result = compileMiddlewareMatcherPattern(pattern);
@@ -1,3 +1,4 @@
1
+ import "./headers.js";
1
2
  //#region src/server/middleware-response-headers.ts
2
3
  const ADDITIVE_RESPONSE_HEADER_NAMES = /* @__PURE__ */ new Set(["set-cookie", "vary"]);
3
4
  function mergeVaryHeader(target, value) {
@@ -28,6 +29,7 @@ function mergeMiddlewareResponseHeaders(target, middlewareHeaders) {
28
29
  if (!middlewareHeaders) return;
29
30
  for (const [key, value] of middlewareHeaders) {
30
31
  const lowerName = key.toLowerCase();
32
+ if (lowerName === "x-vinext-special-error-status") continue;
31
33
  if (lowerName === "link" && !value) continue;
32
34
  if (lowerName === "vary") {
33
35
  mergeVaryHeader(target, value);