vinext 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +21 -9
  2. package/dist/build/client-build-config.d.ts +10 -0
  3. package/dist/build/client-build-config.js +18 -2
  4. package/dist/build/pages-client-assets-module.d.ts +8 -0
  5. package/dist/build/pages-client-assets-module.js +21 -1
  6. package/dist/build/prerender-paths.js +5 -3
  7. package/dist/build/run-prerender.js +1 -1
  8. package/dist/build/ssr-manifest.d.ts +2 -0
  9. package/dist/build/ssr-manifest.js +6 -2
  10. package/dist/check.js +7 -6
  11. package/dist/cli.js +2 -1
  12. package/dist/client/client-redirect-matcher.d.ts +14 -0
  13. package/dist/client/client-redirect-matcher.js +37 -0
  14. package/dist/client/client-redirects.d.ts +27 -0
  15. package/dist/client/client-redirects.js +28 -0
  16. package/dist/client/client-rewrites.d.ts +2 -1
  17. package/dist/client/client-rewrites.js +1 -1
  18. package/dist/config/config-matchers.d.ts +26 -11
  19. package/dist/config/config-matchers.js +78 -81
  20. package/dist/config/destination-params.d.ts +47 -0
  21. package/dist/config/destination-params.js +116 -0
  22. package/dist/config/next-config.d.ts +7 -0
  23. package/dist/config/next-config.js +3 -0
  24. package/dist/entries/app-rsc-entry.d.ts +4 -0
  25. package/dist/entries/app-rsc-entry.js +20 -6
  26. package/dist/entries/pages-client-entry.js +3 -1
  27. package/dist/entries/pages-server-entry.js +13 -37
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +134 -43
  30. package/dist/init-cloudflare.d.ts +9 -0
  31. package/dist/init-cloudflare.js +107 -31
  32. package/dist/init-platform.d.ts +4 -0
  33. package/dist/init-platform.js +27 -2
  34. package/dist/init.d.ts +1 -0
  35. package/dist/init.js +67 -3
  36. package/dist/plugins/ast-scope.d.ts +7 -0
  37. package/dist/plugins/ast-scope.js +59 -3
  38. package/dist/plugins/cache-flight-codec.d.ts +1 -1
  39. package/dist/plugins/dynamic-preload-metadata.d.ts +1 -1
  40. package/dist/plugins/dynamic-preload-metadata.js +9 -8
  41. package/dist/plugins/ignore-dynamic-requests.d.ts +1 -1
  42. package/dist/plugins/ignore-dynamic-requests.js +3 -4
  43. package/dist/plugins/middleware-server-only.d.ts +5 -4
  44. package/dist/plugins/middleware-server-only.js +8 -5
  45. package/dist/plugins/pages-node-externals.js +1 -1
  46. package/dist/plugins/rsc-scan-glob-imports.d.ts +19 -0
  47. package/dist/plugins/rsc-scan-glob-imports.js +50 -0
  48. package/dist/plugins/strip-server-exports.js +262 -98
  49. package/dist/plugins/transform-result.d.ts +9 -5
  50. package/dist/plugins/transitive-externals.js +1 -1
  51. package/dist/plugins/typeof-window.d.ts +2 -2
  52. package/dist/plugins/use-cache-callable.js +8 -2
  53. package/dist/plugins/worker-server-only-imports.d.ts +20 -0
  54. package/dist/plugins/worker-server-only-imports.js +31 -0
  55. package/dist/plugins/worker-use-server.d.ts +25 -0
  56. package/dist/plugins/worker-use-server.js +129 -0
  57. package/dist/server/api-handler.d.ts +1 -0
  58. package/dist/server/api-handler.js +2 -1
  59. package/dist/server/app-browser-action-result.d.ts +1 -1
  60. package/dist/server/app-browser-action-result.js +9 -2
  61. package/dist/server/app-browser-navigation-controller.js +1 -1
  62. package/dist/server/app-browser-server-action-client.js +1 -1
  63. package/dist/server/app-middleware.d.ts +11 -0
  64. package/dist/server/app-middleware.js +17 -2
  65. package/dist/server/app-page-cache.d.ts +1 -1
  66. package/dist/server/app-page-cache.js +1 -1
  67. package/dist/server/app-page-dispatch.d.ts +3 -1
  68. package/dist/server/app-page-dispatch.js +4 -3
  69. package/dist/server/app-page-ppr-runtime.js +1 -1
  70. package/dist/server/app-page-render-observation.js +1 -1
  71. package/dist/server/app-page-request.js +53 -15
  72. package/dist/server/app-page-route-wiring.js +2 -2
  73. package/dist/server/app-pages-bridge.d.ts +1 -1
  74. package/dist/server/app-pages-bridge.js +2 -3
  75. package/dist/server/app-prerender-endpoints.js +9 -6
  76. package/dist/server/app-request-stage-dispatch.js +6 -2
  77. package/dist/server/app-request-stage-independent-entry.js +25 -5
  78. package/dist/server/app-response-stage-entry.js +10 -2
  79. package/dist/server/app-route-handler-cache.d.ts +1 -0
  80. package/dist/server/app-route-handler-cache.js +1 -0
  81. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  82. package/dist/server/app-route-handler-dispatch.js +5 -2
  83. package/dist/server/app-route-handler-execution.d.ts +1 -0
  84. package/dist/server/app-route-handler-execution.js +1 -0
  85. package/dist/server/app-route-handler-runtime.d.ts +4 -1
  86. package/dist/server/app-route-handler-runtime.js +45 -24
  87. package/dist/server/app-router-entry.js +24 -5
  88. package/dist/server/app-rsc-cache-busting.js +1 -1
  89. package/dist/server/app-rsc-errors.js +20 -16
  90. package/dist/server/app-rsc-handler.d.ts +7 -0
  91. package/dist/server/app-rsc-handler.js +85 -35
  92. package/dist/server/app-rsc-response-stage.js +4 -2
  93. package/dist/server/app-segment-config.d.ts +2 -1
  94. package/dist/server/app-server-action-execution.d.ts +2 -1
  95. package/dist/server/app-server-action-execution.js +61 -22
  96. package/dist/server/app-ssr-entry.js +2 -1
  97. package/dist/server/app-worker-stages.d.ts +5 -0
  98. package/dist/server/app-worker-stages.js +1 -1
  99. package/dist/server/cacheability-request.js +2 -2
  100. package/dist/server/client-build-files.d.ts +16 -0
  101. package/dist/server/client-build-files.js +36 -0
  102. package/dist/server/client-reuse-manifest.js +1 -1
  103. package/dist/server/dev-server.js +38 -27
  104. package/dist/server/dev-stack-sourcemap.js +18 -23
  105. package/dist/server/document-initial-head.d.ts +0 -11
  106. package/dist/server/document-initial-head.js +1 -1
  107. package/dist/server/forwarded-middleware-context.d.ts +5 -0
  108. package/dist/server/forwarded-middleware-context.js +22 -0
  109. package/dist/server/headers.d.ts +4 -1
  110. package/dist/server/headers.js +4 -1
  111. package/dist/server/http-conditional.d.ts +11 -0
  112. package/dist/server/http-conditional.js +10 -1
  113. package/dist/server/http-error-responses.d.ts +1 -1
  114. package/dist/server/image-optimization.d.ts +38 -0
  115. package/dist/server/image-optimization.js +93 -4
  116. package/dist/server/isr-cache.d.ts +10 -0
  117. package/dist/server/isr-cache.js +41 -6
  118. package/dist/server/metadata-route-response.js +9 -8
  119. package/dist/server/middleware-matcher.d.ts +2 -0
  120. package/dist/server/middleware-matcher.js +23 -2
  121. package/dist/server/middleware-runtime.d.ts +13 -0
  122. package/dist/server/middleware-runtime.js +33 -20
  123. package/dist/server/middleware.d.ts +1 -1
  124. package/dist/server/middleware.js +3 -1
  125. package/dist/server/multi-stage.d.ts +11 -0
  126. package/dist/server/normalize-path.d.ts +0 -13
  127. package/dist/server/normalize-path.js +1 -34
  128. package/dist/server/pages-api-route.d.ts +3 -2
  129. package/dist/server/pages-api-route.js +4 -3
  130. package/dist/server/pages-cache-tag.d.ts +12 -0
  131. package/dist/server/pages-cache-tag.js +19 -0
  132. package/dist/server/pages-client-assets.d.ts +2 -0
  133. package/dist/server/pages-data-route.d.ts +10 -0
  134. package/dist/server/pages-data-route.js +19 -2
  135. package/dist/server/pages-document-asset-props.d.ts +5 -1
  136. package/dist/server/pages-document-asset-props.js +13 -5
  137. package/dist/server/pages-document-splice.d.ts +19 -0
  138. package/dist/server/pages-document-splice.js +55 -0
  139. package/dist/server/pages-i18n.d.ts +12 -1
  140. package/dist/server/pages-i18n.js +18 -4
  141. package/dist/server/pages-node-compat.d.ts +16 -5
  142. package/dist/server/pages-node-compat.js +20 -12
  143. package/dist/server/pages-page-data.d.ts +32 -24
  144. package/dist/server/pages-page-data.js +50 -120
  145. package/dist/server/pages-page-handler.d.ts +0 -6
  146. package/dist/server/pages-page-handler.js +188 -126
  147. package/dist/server/pages-page-response.d.ts +12 -6
  148. package/dist/server/pages-page-response.js +41 -34
  149. package/dist/server/pages-request-pipeline.d.ts +10 -2
  150. package/dist/server/pages-request-pipeline.js +37 -18
  151. package/dist/server/pages-request-stage-entry.d.ts +6 -0
  152. package/dist/server/pages-request-stage-entry.js +71 -12
  153. package/dist/server/pages-response-stage-entry.js +4 -0
  154. package/dist/server/pages-revalidate.js +1 -1
  155. package/dist/server/pages-router-entry.js +4 -3
  156. package/dist/server/prerender-manifest.d.ts +3 -3
  157. package/dist/server/prerender-manifest.js +4 -3
  158. package/dist/server/prerender-work-unit-setup.js +4 -0
  159. package/dist/server/prod-server.d.ts +12 -0
  160. package/dist/server/prod-server.js +199 -69
  161. package/dist/server/proper-error.d.ts +4 -0
  162. package/dist/server/proper-error.js +31 -0
  163. package/dist/server/request-pipeline.d.ts +4 -1
  164. package/dist/server/request-pipeline.js +3 -2
  165. package/dist/server/revalidation-request.d.ts +1 -0
  166. package/dist/server/revalidation-request.js +1 -1
  167. package/dist/server/rsc-stream-hints.js +31 -2
  168. package/dist/server/seed-cache.d.ts +1 -0
  169. package/dist/server/seed-cache.js +5 -4
  170. package/dist/server/server-reference-loader.d.ts +4 -0
  171. package/dist/server/server-reference-loader.js +29 -0
  172. package/dist/server/socket-error-backstop.d.ts +9 -1
  173. package/dist/server/socket-error-backstop.js +46 -17
  174. package/dist/server/static-file-signal.d.ts +15 -4
  175. package/dist/server/static-file-signal.js +46 -12
  176. package/dist/server/worker-image-optimization.d.ts +34 -0
  177. package/dist/server/worker-image-optimization.js +86 -0
  178. package/dist/server/worker-stages.d.ts +1 -1
  179. package/dist/server/worker-stages.js +2 -2
  180. package/dist/server/worker-utils.d.ts +8 -3
  181. package/dist/server/worker-utils.js +12 -7
  182. package/dist/shims/app-router-scroll.js +4 -1
  183. package/dist/shims/app-router-tree-context.d.ts +5 -0
  184. package/dist/shims/app-router-tree-context.js +27 -0
  185. package/dist/shims/cache-callable-runtime.d.ts +4 -2
  186. package/dist/shims/cache-callable-runtime.js +21 -12
  187. package/dist/shims/cache-handler.js +1 -9
  188. package/dist/shims/cache-runtime.d.ts +6 -1
  189. package/dist/shims/cache-runtime.js +2 -1
  190. package/dist/shims/cache.d.ts +2 -2
  191. package/dist/shims/cache.js +40 -13
  192. package/dist/shims/cdn-cache.d.ts +8 -0
  193. package/dist/shims/cdn-cache.js +17 -1
  194. package/dist/shims/disable-smooth-scroll.d.ts +3 -0
  195. package/dist/shims/disable-smooth-scroll.js +36 -0
  196. package/dist/shims/document-placeholders.d.ts +19 -0
  197. package/dist/shims/document-placeholders.js +45 -0
  198. package/dist/shims/document.js +21 -5
  199. package/dist/shims/dynamic-preload-chunks.d.ts +1 -0
  200. package/dist/shims/dynamic-preload-chunks.js +13 -9
  201. package/dist/shims/dynamic.d.ts +1 -1
  202. package/dist/shims/dynamic.js +60 -8
  203. package/dist/shims/fetch-cache-state.d.ts +66 -0
  204. package/dist/shims/fetch-cache-state.js +92 -0
  205. package/dist/shims/fetch-cache.d.ts +3 -67
  206. package/dist/shims/fetch-cache.js +160 -125
  207. package/dist/shims/head.d.ts +10 -6
  208. package/dist/shims/head.js +15 -13
  209. package/dist/shims/image.js +2 -1
  210. package/dist/shims/internal/work-store-scope.d.ts +5 -0
  211. package/dist/shims/internal/work-store-scope.js +24 -0
  212. package/dist/shims/navigation.js +31 -5
  213. package/dist/shims/request-context.d.ts +2 -0
  214. package/dist/shims/request-state-types.d.ts +2 -1
  215. package/dist/shims/router.js +12 -20
  216. package/dist/shims/server.d.ts +16 -0
  217. package/dist/shims/server.js +24 -4
  218. package/dist/shims/unified-request-context.d.ts +1 -1
  219. package/dist/shims/url-utils.d.ts +2 -0
  220. package/dist/shims/url-utils.js +3 -1
  221. package/dist/utils/client-runtime-metadata.js +1 -1
  222. package/dist/utils/hash.d.ts +7 -0
  223. package/dist/utils/hash.js +11 -1
  224. package/dist/utils/package-name.d.ts +6 -0
  225. package/dist/utils/package-name.js +12 -1
  226. package/dist/utils/plugin-rsc-version.d.ts +4 -0
  227. package/dist/utils/plugin-rsc-version.js +15 -0
  228. package/dist/utils/prerender-output-paths.js +15 -4
  229. package/dist/utils/public-file-path.d.ts +10 -2
  230. package/dist/utils/public-file-path.js +13 -9
  231. package/dist/utils/public-routes.js +2 -2
  232. package/dist/utils/react-version.d.ts +3 -0
  233. package/dist/utils/react-version.js +1 -1
  234. package/package.json +7 -3
@@ -1,4 +1,5 @@
1
1
  import { VINEXT_PRERENDER_SECRET_HEADER, VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
2
+ import { assetPrefixPathname } from "../utils/asset-prefix.js";
2
3
  import { runWithExecutionContext } from "../shims/request-context.js";
3
4
  import "./server-globals.js";
4
5
  import "./app-rsc-vary.js";
@@ -8,16 +9,20 @@ import { cloneRequestWithHeaders, filterInternalHeaders, guardProtocolRelativeUr
8
9
  import { getExplicitCdnCacheAdapter } from "../shims/cdn-cache-state.js";
9
10
  import { applyCdnResponseIdentityHeaders, validateCdnRequest } from "./cache-control.js";
10
11
  import { registerLazyDataCacheHandler } from "../shims/cache-handler.js";
11
- import { getImageOptimizer, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
12
+ import { getImageOptimizer, readImageOptimizationSignal } from "./image-optimization.js";
12
13
  import { consumeFrameworkRequestRoute, traceFrameworkRequest } from "./request-tracing.js";
13
14
  import { createStaticAssetRequest, resolveStaticAssetSignal } from "./worker-utils.js";
14
15
  import { readTrustedPrerenderStateFromHeaders } from "./prerender-route-params.js";
16
+ import { resolveClientBuildFile } from "./client-build-files.js";
15
17
  import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
16
18
  import { createWorkerPrerenderDiscoveryContext, createWorkerPrerenderReadinessResponse } from "./worker-prerender-discovery.js";
17
- import requestRscHandler, { __ensureHybridPagesApplication, __ensureInstrumentation, __imageAllowedWidths, __imageConfig, __prerenderSecret } from "virtual:vinext-app-request-entry";
19
+ import requestRscHandler, { __assetPrefix, __basePath, __clientBuildFiles, __ensureHybridPagesApplication, __ensureInstrumentation, __imageAllowedWidths, __imageConfig, __prerenderSecret } from "virtual:vinext-app-request-entry";
18
20
  import * as configuredCdnCacheAdapters from "virtual:vinext-cdn-cache-adapter";
19
21
  import { registerConfiguredImageOptimizer } from "virtual:vinext-image-adapters";
20
22
  //#region src/server/app-request-stage-independent-entry.ts
23
+ const clientBuildFiles = new Set(__clientBuildFiles);
24
+ const assetPathPrefix = assetPrefixPathname(__assetPrefix);
25
+ const rejectResponseStage = () => Promise.resolve(new Response(null, { status: 404 }));
21
26
  function handleRequestStage(request, env, ctx, dispatchResponseStage) {
22
27
  const originalRequest = request;
23
28
  const handleStage = async () => {
@@ -42,6 +47,7 @@ async function handleRequest(request, env, platformCtx, dispatchResponseStage, a
42
47
  (await import("virtual:vinext-cache-adapters")).registerConfiguredCacheAdapters(env);
43
48
  });
44
49
  registerConfiguredImageOptimizer(env);
50
+ const requestCtx = ctx;
45
51
  ctx = createWorkerPrerenderDiscoveryContext(ctx, request, __prerenderSecret);
46
52
  const readinessResponse = createWorkerPrerenderReadinessResponse(ctx, request);
47
53
  let didValidateCdnRequest = false;
@@ -69,7 +75,7 @@ async function handleRequest(request, env, platformCtx, dispatchResponseStage, a
69
75
  if (cdnValidationResponse) return cdnValidationResponse;
70
76
  }
71
77
  const url = new URL(request.url);
72
- if (isImageOptimizationPath(url.pathname) && assets && getImageOptimizer()) return handleConfiguredImageOptimization(request, (assetPath) => Promise.resolve(assets.fetch(new Request(new URL(assetPath, request.url)))), __imageAllowedWidths, __imageConfig);
78
+ const servesImages = Boolean(assets) && getImageOptimizer() !== null;
73
79
  const protocolRelativeGuard = guardProtocolRelativeUrl(url.pathname, url.search);
74
80
  if (protocolRelativeGuard) return protocolRelativeGuard;
75
81
  try {
@@ -77,6 +83,10 @@ async function handleRequest(request, env, platformCtx, dispatchResponseStage, a
77
83
  } catch {
78
84
  return badRequestResponse();
79
85
  }
86
+ if (assets && (request.method === "GET" || request.method === "HEAD")) {
87
+ const clientBuildFile = resolveClientBuildFile(url.pathname, __basePath, clientBuildFiles);
88
+ if (clientBuildFile) return await assets.fetch(createStaticAssetRequest(clientBuildFile, request));
89
+ }
80
90
  const trustedPrerenderState = readTrustedPrerenderStateFromHeaders(request.headers, __prerenderSecret);
81
91
  const filteredHeaders = ctx.isInternalPagesRevalidation ? new Headers(request.headers) : filterInternalHeaders(request.headers);
82
92
  filteredHeaders.delete(VINEXT_PRERENDER_SECRET_HEADER);
@@ -94,8 +104,18 @@ async function handleRequest(request, env, platformCtx, dispatchResponseStage, a
94
104
  };
95
105
  const handle = () => requestRscHandler(request, ctx, trackedDispatchResponseStage, probeMode, ctx.isPrerenderPathDiscovery === true, trustedPrerenderState);
96
106
  let response = await runWithExecutionContext(ctx, handle);
97
- if (assets) {
98
- const assetResponse = await resolveStaticAssetSignal(response, { fetchAsset: (path) => Promise.resolve(assets.fetch(createStaticAssetRequest(path, request))) });
107
+ const imageSignal = servesImages ? readImageOptimizationSignal(response) : null;
108
+ if (imageSignal && assets) {
109
+ const { fetchSignalledPublicFile, serveWorkerImageOptimization } = await import("./worker-image-optimization.js");
110
+ response = await serveWorkerImageOptimization(request, imageSignal, __imageAllowedWidths, __imageConfig, {
111
+ assets,
112
+ basePath: __basePath,
113
+ assetPathPrefix,
114
+ clientBuildFiles,
115
+ fetchPublicFile: async (sourceRequest) => fetchSignalledPublicFile(await runWithExecutionContext(requestCtx, () => requestRscHandler(sourceRequest, requestCtx, rejectResponseStage, null, false, null)), sourceRequest, assets)
116
+ });
117
+ } else if (assets) {
118
+ const assetResponse = await resolveStaticAssetSignal(response, { fetchAsset: (path, requestHeaders) => Promise.resolve(assets.fetch(createStaticAssetRequest(path, request, requestHeaders))) });
99
119
  if (assetResponse) response = assetResponse;
100
120
  }
101
121
  if (probeMode && probeRoute && !responseStageDispatched) {
@@ -1,8 +1,11 @@
1
1
  import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
2
  import { runWithExecutionContext } from "../shims/request-context.js";
3
+ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
3
4
  import { serializeStaticFileSignalForTransport } from "./static-file-signal.js";
5
+ import { attachRequestCfMetadata } from "./request-pipeline.js";
4
6
  import { validateCdnRequest } from "./cache-control.js";
5
7
  import { ensureFetchPatch } from "../shims/fetch-cache.js";
8
+ import { serializeImageOptimizationSignalForTransport } from "./image-optimization.js";
6
9
  import { serializeResponseStageLinkProvenance } from "./app-response-header-provenance.js";
7
10
  import { attachFrameworkRequestError, attachFrameworkRequestRoute, captureFrameworkRequestRoute, clearFrameworkRequestError } from "./request-tracing.js";
8
11
  import { isAppWorkerResponseStageProps } from "./app-worker-stages.js";
@@ -26,10 +29,11 @@ async function invokeCacheFunction(invocation, env, platformCtx, dispatchRequest
26
29
  executionContext,
27
30
  rootParams: invocation.rootParams
28
31
  });
29
- await runWithRequestContext(context, () => invokeRegisteredCacheFunction(invocation, loadServerAction));
32
+ await runWithRequestContext(context, () => runInWorkStoreScope(() => invokeRegisteredCacheFunction(invocation, loadServerAction)));
30
33
  }
31
34
  async function handleResponseStage(request, env, platformCtx, props, dispatchRequestStage, options = { cache: "bypass" }) {
32
35
  if (!isAppWorkerResponseStageProps(props)) return new Response("Invalid vinext App response stage", { status: 400 });
36
+ if (props.kind === "app-full-request" && props.headRequest) request = attachRequestCfMetadata(new Request(request, { method: "HEAD" }), request);
33
37
  if (props.requestOrigin !== new URL(request.url).origin) return new Response("Invalid vinext App response stage", { status: 400 });
34
38
  const currentBuildId = process.env.__VINEXT_BUILD_ID ?? null;
35
39
  if (props.buildId !== currentBuildId) return new Response("Incompatible vinext App response stage", { status: 409 });
@@ -41,6 +45,10 @@ async function handleResponseStage(request, env, platformCtx, props, dispatchReq
41
45
  ...ctx,
42
46
  isPrerenderPathDiscovery: true
43
47
  };
48
+ else if (props.kind === "hybrid-pages" && options.regeneratesStoredEntry === true) ctx = {
49
+ ...ctx,
50
+ isStoredEntryRegeneration: true
51
+ };
44
52
  return withResponseStageCacheability({
45
53
  buildId: process.env.__VINEXT_BUILD_ID,
46
54
  cache: options.cache,
@@ -76,7 +84,7 @@ async function handleResponseStage(request, env, platformCtx, props, dispatchReq
76
84
  failure = error;
77
85
  result = new Response("Internal Server Error", { status: 500 });
78
86
  }
79
- const serialized = serializeStaticFileSignalForTransport(result, props.staticFileSignalToken);
87
+ const serialized = serializeImageOptimizationSignalForTransport(serializeStaticFileSignalForTransport(result, props.staticFileSignalToken), props.staticFileSignalToken);
80
88
  return attachFrameworkRequestRoute(failed ? attachFrameworkRequestError(serialized, failure) : clearFrameworkRequestError(serialized), route);
81
89
  }
82
90
  const render = () => rscHandler.handleResponseStage(request, cacheabilityContext, props, options);
@@ -19,6 +19,7 @@ type ReadAppRouteHandlerCacheOptions = {
19
19
  handlerFn: AppRouteHandlerFunction;
20
20
  i18n?: NextI18nConfig | null;
21
21
  trailingSlash?: boolean;
22
+ skipTrailingSlashRedirect?: boolean;
22
23
  isAutoHead: boolean;
23
24
  appendResponseLink?: boolean;
24
25
  isrDebug?: AppRouteDebugLogger;
@@ -49,6 +49,7 @@ async function readAppRouteHandlerCacheResponse(options) {
49
49
  handlerFn: options.handlerFn,
50
50
  i18n: options.i18n,
51
51
  trailingSlash: options.trailingSlash,
52
+ skipTrailingSlashRedirect: options.skipTrailingSlashRedirect,
52
53
  markDynamicUsage: options.markDynamicUsage,
53
54
  params: options.params === null ? null : makeThenableParams(options.params),
54
55
  request: new Request(options.requestUrl, { method: "GET" }),
@@ -31,6 +31,7 @@ type DispatchAppRouteHandlerOptions = {
31
31
  isrRouteKey: (pathname: string) => string;
32
32
  isrSet: RouteHandlerCacheSetter;
33
33
  trailingSlash?: boolean;
34
+ skipTrailingSlashRedirect?: boolean;
34
35
  middlewareContext: RouteHandlerMiddlewareContext;
35
36
  middlewareRequestHeaders?: Headers | null;
36
37
  /**
@@ -1,6 +1,7 @@
1
1
  import { closeAfterResponse, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
2
  import "../utils/protocol-headers.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
+ import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
4
5
  import { reportRequestError } from "./instrumentation.js";
5
6
  import { beginRouteCacheability, getRouteCacheabilityDynamicReason } from "../shims/cacheability-classification.js";
6
7
  import { applyCdnResponseBuildIdentityHeaders } from "./cache-control.js";
@@ -41,7 +42,7 @@ async function runInRouteHandlerRevalidationContext(options, renderFn) {
41
42
  executionContext: getRequestExecutionContext(),
42
43
  unstableCacheRevalidation: "foreground"
43
44
  });
44
- const revalidation = runWithRequestContext(requestContext, async () => {
45
+ const revalidation = runWithRequestContext(requestContext, () => runInWorkStoreScope(async () => {
45
46
  ensureFetchPatch();
46
47
  setCurrentFetchSoftTags(buildRouteHandlerPageCacheTags(options.cleanPathname, [], options.routeSegments));
47
48
  setCurrentFetchCacheMode(options.fetchCacheMode);
@@ -52,7 +53,7 @@ async function runInRouteHandlerRevalidationContext(options, renderFn) {
52
53
  } finally {
53
54
  await _drainPendingRevalidations();
54
55
  }
55
- });
56
+ }));
56
57
  try {
57
58
  await revalidation;
58
59
  } finally {
@@ -122,6 +123,7 @@ async function dispatchAppRouteHandlerImpl(options) {
122
123
  handlerFn: resolvedHandlerFn,
123
124
  i18n: options.i18n,
124
125
  trailingSlash: options.trailingSlash,
126
+ skipTrailingSlashRedirect: options.skipTrailingSlashRedirect,
125
127
  isAutoHead,
126
128
  appendResponseLink,
127
129
  isrDebug: options.isrDebug,
@@ -184,6 +186,7 @@ async function dispatchAppRouteHandlerImpl(options) {
184
186
  handlerFn: resolvedHandlerFn,
185
187
  i18n: options.i18n,
186
188
  trailingSlash: options.trailingSlash,
189
+ skipTrailingSlashRedirect: options.skipTrailingSlashRedirect,
187
190
  isAutoHead,
188
191
  initialDraftModeCookie,
189
192
  isDraftMode,
@@ -47,6 +47,7 @@ type RunAppRouteHandlerOptions = {
47
47
  i18n?: NextI18nConfig | null;
48
48
  isDraftMode?: boolean;
49
49
  trailingSlash?: boolean;
50
+ skipTrailingSlashRedirect?: boolean;
50
51
  markDynamicUsage: MarkAppRouteDynamicUsageFn;
51
52
  middlewareRequestHeaders?: Headers | null;
52
53
  /**
@@ -112,6 +112,7 @@ async function runAppRouteHandler(options) {
112
112
  basePath: options.basePath,
113
113
  i18n: options.i18n,
114
114
  trailingSlash: options.trailingSlash,
115
+ skipTrailingSlashRedirect: options.skipTrailingSlashRedirect,
115
116
  middlewareHeaders: options.middlewareRequestHeaders,
116
117
  onDynamicAccess() {
117
118
  options.markDynamicUsage();
@@ -17,13 +17,16 @@ export declare function buildRouteHandlerAllowHeader(exportedMethods: readonly s
17
17
  export declare function isKnownDynamicAppRoute(pattern: string): boolean;
18
18
  export declare function markKnownDynamicAppRoute(pattern: string): void;
19
19
  type RequestDynamicAccess = "request.headers" | "request.cookies" | "request.cf" | "request.ip" | "request.geo" | "request.url" | "request.body" | "request.blob" | "request.bytes" | "request.json" | "request.text" | "request.arrayBuffer" | "request.formData";
20
- type NextUrlDynamicAccess = "nextUrl.search" | "nextUrl.searchParams" | "nextUrl.url" | "nextUrl.href" | "nextUrl.toJSON" | "nextUrl.toString" | "nextUrl.origin";
20
+ type NextUrlDynamicAccess = "nextUrl.search" | "nextUrl.searchParams" | "nextUrl.url" | "nextUrl.href" | "nextUrl.toJSON" | "nextUrl.toString" | "nextUrl.origin" | HostDerivedNextUrlDynamicAccess;
21
+ type HostDerivedNextUrlDynamicAccess = "nextUrl.protocol" | "nextUrl.username" | "nextUrl.password" | "nextUrl.host" | "nextUrl.hostname" | "nextUrl.port" | DomainLocaleNextUrlDynamicAccess;
22
+ type DomainLocaleNextUrlDynamicAccess = "nextUrl.locale" | "nextUrl.defaultLocale" | "nextUrl.domainLocale";
21
23
  type AppRouteDynamicRequestAccess = RequestDynamicAccess | NextUrlDynamicAccess;
22
24
  type AppRouteRequestMode = "auto" | "force-static" | "error";
23
25
  type TrackedAppRouteRequestOptions = {
24
26
  basePath?: string;
25
27
  i18n?: NextI18nConfig | null;
26
28
  trailingSlash?: boolean;
29
+ skipTrailingSlashRedirect?: boolean;
27
30
  middlewareHeaders?: Headers | null;
28
31
  onDynamicAccess?: (access: AppRouteDynamicRequestAccess) => void;
29
32
  requestMode?: AppRouteRequestMode;
@@ -64,11 +64,12 @@ function getRequestProperty(target, prop, receiver, builtInProperties) {
64
64
  return bindMethodIfNeeded(Reflect.get(target, prop, bindingTarget), bindingTarget);
65
65
  }
66
66
  function buildNextConfig(options) {
67
- if (!options.basePath && !options.i18n && !options.trailingSlash) return null;
67
+ if (!options.basePath && !options.i18n && !options.trailingSlash && !options.skipTrailingSlashRedirect) return null;
68
68
  return {
69
69
  basePath: options.basePath,
70
70
  i18n: options.i18n ?? void 0,
71
- trailingSlash: options.trailingSlash
71
+ trailingSlash: options.trailingSlash,
72
+ skipTrailingSlashRedirect: options.skipTrailingSlashRedirect
72
73
  };
73
74
  }
74
75
  function createForceStaticNextRequest(input, nextConfig) {
@@ -128,21 +129,41 @@ function createTrackedAppRouteRequest(request, options = {}) {
128
129
  didAccessDynamicRequest = true;
129
130
  options.onDynamicAccess?.(access);
130
131
  };
132
+ const isNextDynamicNextUrlProperty = (prop) => {
133
+ switch (prop) {
134
+ case "search":
135
+ case "searchParams":
136
+ case "url":
137
+ case "href":
138
+ case "toJSON":
139
+ case "toString":
140
+ case "origin": return true;
141
+ default: return false;
142
+ }
143
+ };
144
+ const hasI18nDomains = (options.i18n?.domains?.length ?? 0) > 0;
145
+ const isHostDerivedNextUrlProperty = (prop) => {
146
+ switch (prop) {
147
+ case "protocol":
148
+ case "username":
149
+ case "password":
150
+ case "host":
151
+ case "hostname":
152
+ case "port": return true;
153
+ case "locale":
154
+ case "defaultLocale":
155
+ case "domainLocale": return hasI18nDomains;
156
+ default: return false;
157
+ }
158
+ };
131
159
  const wrapNextUrl = (nextUrl) => {
132
160
  return new Proxy(nextUrl, { get(target, prop) {
133
- switch (prop) {
134
- case "search":
135
- case "searchParams":
136
- case "url":
137
- case "href":
138
- case "toJSON":
139
- case "toString":
140
- case "origin":
141
- markDynamicAccess(`nextUrl.${String(prop)}`);
142
- return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
143
- case "clone": return () => wrapNextUrl(target.clone());
144
- default: return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
161
+ if (isNextDynamicNextUrlProperty(prop) || isHostDerivedNextUrlProperty(prop)) {
162
+ markDynamicAccess(`nextUrl.${String(prop)}`);
163
+ return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
145
164
  }
165
+ if (prop === "clone") return () => wrapNextUrl(target.clone());
166
+ return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
146
167
  } });
147
168
  };
148
169
  const wrapForceStaticNextUrl = (nextUrl) => {
@@ -165,18 +186,18 @@ function createTrackedAppRouteRequest(request, options = {}) {
165
186
  throw new Error(options.staticGenerationErrorMessage?.(expression) ?? `Route handler with \`dynamic = "error"\` used ${expression}.`);
166
187
  };
167
188
  const wrapRequireStaticNextUrl = (nextUrl) => {
189
+ let staticNextUrl = null;
168
190
  return new Proxy(nextUrl, { get(target, prop) {
169
- switch (prop) {
170
- case "search":
171
- case "searchParams":
172
- case "url":
173
- case "href":
174
- case "toJSON":
175
- case "toString":
176
- case "origin": return throwStaticGenerationError(`nextUrl.${String(prop)}`);
177
- case "clone": return () => wrapRequireStaticNextUrl(target.clone());
178
- default: return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
191
+ if (isNextDynamicNextUrlProperty(prop)) return throwStaticGenerationError(`nextUrl.${String(prop)}`);
192
+ if (isHostDerivedNextUrlProperty(prop)) {
193
+ if (staticNextUrl === null) {
194
+ staticNextUrl = target.clone();
195
+ staticNextUrl.href = cleanStaticUrl(target.href);
196
+ }
197
+ return Reflect.get(staticNextUrl, prop, staticNextUrl);
179
198
  }
199
+ if (prop === "clone") return () => wrapRequireStaticNextUrl(target.clone());
200
+ return bindMethodIfNeeded(Reflect.get(target, prop, target), target);
180
201
  } });
181
202
  };
182
203
  const wrapRequest = (rawInput) => {
@@ -8,17 +8,19 @@ import { badRequestResponse, notFoundResponse, notFoundStaticAssetResponse } fro
8
8
  import { cloneRequestWithHeaders, filterInternalHeaders, guardProtocolRelativeUrl } from "./request-pipeline.js";
9
9
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
10
10
  import { applyCdnResponseIdentityHeaders, validateCdnRequest } from "./cache-control.js";
11
- import { getImageOptimizer, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
11
+ import { getImageOptimizer, readImageOptimizationSignal } from "./image-optimization.js";
12
12
  import { traceFrameworkRequest } from "./request-tracing.js";
13
13
  import { createStaticAssetRequest, resolveStaticAssetSignal } from "./worker-utils.js";
14
14
  import { readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
15
+ import { resolveClientBuildFile } from "./client-build-files.js";
15
16
  import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
16
17
  import { createWorkerPrerenderDiscoveryContext, createWorkerPrerenderReadinessResponse } from "./worker-prerender-discovery.js";
17
18
  import { registerConfiguredImageOptimizer } from "virtual:vinext-image-adapters";
18
19
  import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
19
- import rscHandler, { __assetPrefix, __basePath, __cacheabilityManifest, __ensureHybridPagesApplication, __ensureInstrumentation, __imageAllowedWidths, __imageConfig, __prerenderSecret } from "virtual:vinext-rsc-entry";
20
+ import rscHandler, { __assetPrefix, __basePath, __cacheabilityManifest, __clientBuildFiles, __ensureHybridPagesApplication, __ensureInstrumentation, __imageAllowedWidths, __imageConfig, __prerenderSecret } from "virtual:vinext-rsc-entry";
20
21
  //#region src/server/app-router-entry.ts
21
22
  const __workerBasePath = typeof __basePath === "string" ? __basePath : "";
23
+ const __workerClientBuildFiles = new Set(Array.isArray(__clientBuildFiles) ? __clientBuildFiles : []);
22
24
  const __workerAssetPathPrefix = assetPrefixPathname(typeof __assetPrefix === "string" ? __assetPrefix : "");
23
25
  function isPotentialCompletedAdmissionRequest(request) {
24
26
  if (request.method !== "GET" && request.method !== "HEAD") return false;
@@ -81,7 +83,7 @@ async function handleRequest(request, env, platformCtx) {
81
83
  const cdnValidationResponse = await validateCdnRequest(request);
82
84
  if (cdnValidationResponse) return cdnValidationResponse;
83
85
  const url = new URL(request.url);
84
- if (isImageOptimizationPath(url.pathname) && assets && getImageOptimizer()) return handleConfiguredImageOptimization(request, (assetPath) => Promise.resolve(assets.fetch(new Request(new URL(assetPath, request.url)))), __imageAllowedWidths, __imageConfig);
86
+ const servesImages = Boolean(assets) && getImageOptimizer() !== null;
85
87
  const protocolRelativeGuard = guardProtocolRelativeUrl(url.pathname, url.search);
86
88
  if (protocolRelativeGuard) return protocolRelativeGuard;
87
89
  try {
@@ -89,6 +91,10 @@ async function handleRequest(request, env, platformCtx) {
89
91
  } catch {
90
92
  return badRequestResponse();
91
93
  }
94
+ if (assets && (request.method === "GET" || request.method === "HEAD")) {
95
+ const clientBuildFile = resolveClientBuildFile(url.pathname, __workerBasePath, __workerClientBuildFiles);
96
+ if (clientBuildFile) return await assets.fetch(createStaticAssetRequest(clientBuildFile, request));
97
+ }
92
98
  const missingBuildAsset = isNextStaticPath(url.pathname, __workerBasePath, __workerAssetPathPrefix);
93
99
  {
94
100
  const trustedPrerenderRouteParams = ctx.hostRuntime === "node" ? readTrustedPrerenderRouteParams(request) : null;
@@ -103,8 +109,21 @@ async function handleRequest(request, env, platformCtx) {
103
109
  const result = await (ctx ? runWithExecutionContext(ctx, handleFn) : handleFn());
104
110
  if (result instanceof Response) {
105
111
  let response = result;
106
- if (assets) {
107
- const assetResponse = await resolveStaticAssetSignal(response, { fetchAsset: (path) => Promise.resolve(assets.fetch(createStaticAssetRequest(path, request))) });
112
+ const imageSignal = servesImages ? readImageOptimizationSignal(response) : null;
113
+ if (imageSignal && assets) {
114
+ const { fetchSignalledPublicFile, serveWorkerImageOptimization } = await import("./worker-image-optimization.js");
115
+ response = await serveWorkerImageOptimization(request, imageSignal, __imageAllowedWidths, __imageConfig, {
116
+ assets,
117
+ basePath: __workerBasePath,
118
+ assetPathPrefix: __workerAssetPathPrefix,
119
+ clientBuildFiles: __workerClientBuildFiles,
120
+ fetchPublicFile: async (sourceRequest) => {
121
+ const sourceResult = await runWithExecutionContext(requestCtx, () => rscHandler(sourceRequest, requestCtx));
122
+ return sourceResult instanceof Response ? fetchSignalledPublicFile(sourceResult, sourceRequest, assets) : null;
123
+ }
124
+ });
125
+ } else if (assets) {
126
+ const assetResponse = await resolveStaticAssetSignal(response, { fetchAsset: (path, requestHeaders) => Promise.resolve(assets.fetch(createStaticAssetRequest(path, request, requestHeaders))) });
108
127
  if (assetResponse) response = assetResponse;
109
128
  }
110
129
  return finalizeCacheabilityResponse ? finalizeCacheabilityResponse(response, ctx) : response;
@@ -1,7 +1,7 @@
1
1
  import { NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERCEPTION_ID_HEADER, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_RSC_STATE_FINGERPRINT_HEADER, VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
2
2
  import { NEXTJS_DEPLOYMENT_ID_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER } from "./headers.js";
3
- import { fnv1a64 } from "../utils/hash.js";
4
3
  import { APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL, parseAppRscRenderMode } from "./app-rsc-render-mode.js";
4
+ import { fnv1a64 } from "../utils/hash.js";
5
5
  import { applyDeploymentIdHeader, getDeploymentId } from "../utils/deployment-id.js";
6
6
  import { createAppRscStateFingerprint } from "./app-rsc-state-fingerprint.js";
7
7
  //#region src/server/app-rsc-cache-busting.ts
@@ -1,6 +1,7 @@
1
1
  import { isNavigationSignalError } from "../utils/navigation-signal.js";
2
2
  import { resolveAppPageSpecialError } from "./app-page-execution.js";
3
3
  import { isAppRenderAbortError } from "./app-render-abort-error.js";
4
+ import { getProperError } from "./proper-error.js";
4
5
  //#region src/server/app-rsc-errors.ts
5
6
  const ORIGINAL_SERVER_ERROR = Symbol.for("vinext.originalServerError");
6
7
  function hasDigest(error) {
@@ -23,11 +24,14 @@ function getDigestForWellKnownError(error) {
23
24
  const digest = String(error.digest);
24
25
  if (isNavigationSignalError(error) || digest === BAILOUT_TO_CSR_DIGEST || digest === DYNAMIC_SERVER_USAGE_DIGEST) return digest;
25
26
  }
26
- function getThrownValueMessage(error) {
27
- return error instanceof Error ? error.message : String(error);
28
- }
29
- function getThrownValueStack(error) {
30
- return error instanceof Error ? error.stack || "" : "";
27
+ /**
28
+ * Next.js hashes a thrown string as is, and other values once they are an
29
+ * Error (packages/next/src/server/app-render/create-error-handler.tsx).
30
+ */
31
+ function getThrownValueDigest(error) {
32
+ if (typeof error === "string") return errorDigest(error);
33
+ const properError = getProperError(error);
34
+ return errorDigest(properError.message + (properError.stack || ""));
31
35
  }
32
36
  /**
33
37
  * djb2 hash matching Next.js's string-hash package for RSC error digests.
@@ -41,7 +45,7 @@ function sanitizeErrorForClient(error, nodeEnv = process.env.NODE_ENV) {
41
45
  if (resolveAppPageSpecialError(error)) return error;
42
46
  if (nodeEnv !== "production") return error;
43
47
  const sanitized = /* @__PURE__ */ new Error("An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.");
44
- sanitized.digest = hasDigest(error) ? String(error.digest) : errorDigest(getThrownValueMessage(error) + getThrownValueStack(error));
48
+ sanitized.digest = hasDigest(error) ? String(error.digest) : getThrownValueDigest(error);
45
49
  Object.defineProperty(sanitized, ORIGINAL_SERVER_ERROR, {
46
50
  configurable: false,
47
51
  enumerable: false,
@@ -55,6 +59,7 @@ function createRscOnErrorHandler(options) {
55
59
  return (error) => {
56
60
  const nodeEnv = options.nodeEnv ?? process.env.NODE_ENV;
57
61
  const reportableError = error && typeof error === "object" && ORIGINAL_SERVER_ERROR in error ? Reflect.get(error, ORIGINAL_SERVER_ERROR) : error;
62
+ if (typeof reportableError === "string") return hasDigest(error) ? String(error.digest) : errorDigest(reportableError);
58
63
  if (isAppRenderAbortError(error) || isAppRenderAbortError(reportableError)) return;
59
64
  const wellKnownDigest = getDigestForWellKnownError(error) ?? getDigestForWellKnownError(reportableError);
60
65
  if (wellKnownDigest !== void 0) return wellKnownDigest;
@@ -62,22 +67,21 @@ function createRscOnErrorHandler(options) {
62
67
  console.error("[vinext] RSC serialization error: a non-plain object was passed from a Server Component to a Client Component.\n\nCommon causes:\n * Passing a module namespace (import * as X) directly as a prop.\n Unlike Next.js (webpack), Vite produces real ESM module namespace objects\n which are not serializable. Fix: pass individual values instead,\n e.g. <Comp value={module.value} />\n * Passing a class instance (new Foo()) as a prop.\n Fix: convert to a plain object, e.g. { id: foo.id, name: foo.name }\n * Passing a Date, Map, or Set. Use .toISOString(), [...map.entries()], etc.\n * Passing Object.create(null). Use { ...obj } to restore a prototype.\n\nOriginal error:", error.message);
63
68
  return;
64
69
  }
65
- if (options.requestInfo && options.errorContext && error) options.reportRequestError(reportableError, options.requestInfo, options.errorContext);
70
+ const thrownHasDigest = hasDigest(error);
71
+ const properError = getProperError(reportableError);
72
+ const digest = thrownHasDigest ? String(error.digest) : errorDigest(properError.message + (properError.stack || ""));
73
+ if (!hasDigest(properError) && (properError !== reportableError || options.attachDigest !== false)) try {
74
+ Object.assign(properError, { digest });
75
+ } catch {}
76
+ if (options.requestInfo && options.errorContext) options.reportRequestError(properError, options.requestInfo, options.errorContext);
66
77
  if (process.env.VINEXT_PRERENDER === "1" && error) {
67
78
  if (!loggedPrerenderErrors?.has(reportableError)) {
68
79
  (loggedPrerenderErrors ??= /* @__PURE__ */ new Set()).add(reportableError);
69
80
  const route = options.requestInfo?.path ?? options.errorContext?.routePath ?? "<unknown>";
70
81
  console.error(`[vinext] Error prerendering route ${JSON.stringify(route)}:`, reportableError);
71
82
  }
72
- } else if (nodeEnv !== "production" && error && !hasDigest(error)) console.error("[vinext] Server render error:", reportableError);
73
- if (hasDigest(error)) return String(error.digest);
74
- if (error) {
75
- const digest = errorDigest(getThrownValueMessage(error) + getThrownValueStack(error));
76
- if (options.attachDigest !== false && error instanceof Error) try {
77
- Object.assign(error, { digest });
78
- } catch {}
79
- return digest;
80
- }
83
+ } else if (nodeEnv !== "production" && !thrownHasDigest) console.error("[vinext] Server render error:", properError);
84
+ return digest;
81
85
  };
82
86
  }
83
87
  //#endregion
@@ -204,6 +204,11 @@ export type CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {
204
204
  fallback: NextRewrite[];
205
205
  };
206
206
  draftModeSecret: string;
207
+ /**
208
+ * Dev-only key the hybrid Pages handler seals x-vinext-mw-ctx with. Null
209
+ * outside `vite dev`, so a forwarded middleware context is never honored.
210
+ */
211
+ devForwardedMiddlewareContextKey?: string | null;
207
212
  dispatchMatchedPage: (options: DispatchMatchedPageOptions<TRoute>) => Promise<Response>;
208
213
  dispatchMatchedRouteHandler: (options: DispatchMatchedRouteHandlerOptions<TRoute>) => Promise<Response>;
209
214
  /**
@@ -246,6 +251,8 @@ export type CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {
246
251
  renderPagesFallback?: (options: RenderPagesFallbackOptions) => Promise<Response | null>;
247
252
  rootParamNamesByPattern?: RootParamNamesMap;
248
253
  setNavigationContext: (context: NavigationContextValue) => void;
254
+ /** next.config `skipTrailingSlashRedirect`: serve both slash forms without a 308. */
255
+ skipTrailingSlashRedirect?: boolean;
249
256
  staticParamsMap: StaticParamsMap;
250
257
  trailingSlash: boolean;
251
258
  validateDevRequestOrigin?: (request: Request) => Response | null;