vinext 1.0.0-beta.11 → 1.0.0-beta.13

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 (170) hide show
  1. package/README.md +39 -12
  2. package/dist/build/action-owner-manifest.js +23 -5
  3. package/dist/build/lifecycle.d.ts +42 -0
  4. package/dist/build/lifecycle.js +246 -0
  5. package/dist/build/nitro-route-rules.js +1 -1
  6. package/dist/build/pages-client-assets-module.d.ts +0 -3
  7. package/dist/build/pages-client-assets-module.js +1 -13
  8. package/dist/build/precompress.js +1 -1
  9. package/dist/build/prerender-paths.d.ts +1 -1
  10. package/dist/build/prerender-paths.js +6 -6
  11. package/dist/build/prerender-server-pool.js +1 -1
  12. package/dist/build/prerender.d.ts +1 -1
  13. package/dist/build/prerender.js +5 -5
  14. package/dist/build/preview-credentials.d.ts +1 -1
  15. package/dist/build/preview-credentials.js +2 -2
  16. package/dist/build/report.js +1 -1
  17. package/dist/build/run-prerender.d.ts +2 -2
  18. package/dist/build/run-prerender.js +5 -5
  19. package/dist/build/standalone.js +2 -2
  20. package/dist/cli-args.d.ts +5 -13
  21. package/dist/cli-args.js +2 -45
  22. package/dist/cli-dev-config.d.ts +6 -2
  23. package/dist/cli-dev-config.js +160 -3
  24. package/dist/cli.js +104 -439
  25. package/dist/config/config-matchers.js +1 -1
  26. package/dist/config/next-config.d.ts +2 -2
  27. package/dist/config/next-config.js +5 -5
  28. package/dist/config/prerender.d.ts +3 -0
  29. package/dist/config/prerender.js +8 -2
  30. package/dist/entries/app-browser-entry.js +1 -1
  31. package/dist/entries/app-rsc-entry.d.ts +2 -0
  32. package/dist/entries/app-rsc-entry.js +25 -5
  33. package/dist/entries/pages-client-entry.d.ts +1 -0
  34. package/dist/entries/pages-client-entry.js +17 -2
  35. package/dist/index.d.ts +6 -8
  36. package/dist/index.js +282 -105
  37. package/dist/init-cloudflare.d.ts +3 -2
  38. package/dist/init-cloudflare.js +117 -5
  39. package/dist/init-platform.d.ts +2 -0
  40. package/dist/init-platform.js +9 -3
  41. package/dist/init.d.ts +7 -5
  42. package/dist/init.js +41 -22
  43. package/dist/plugins/dynamic-preload-metadata.js +1 -1
  44. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  45. package/dist/plugins/fonts.js +1 -1
  46. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  47. package/dist/plugins/import-meta-url.js +1 -1
  48. package/dist/plugins/instrumentation-client.js +1 -1
  49. package/dist/plugins/og-assets.js +32 -13
  50. package/dist/plugins/og-harfbuzz.d.ts +24 -0
  51. package/dist/plugins/og-harfbuzz.js +260 -0
  52. package/dist/plugins/optimize-imports.js +1 -1
  53. package/dist/plugins/require-condition-resolution.js +2 -2
  54. package/dist/plugins/require-context.js +2 -2
  55. package/dist/plugins/typeof-window.js +1 -1
  56. package/dist/plugins/use-cache-callable.d.ts +0 -2
  57. package/dist/plugins/use-cache-callable.js +40 -22
  58. package/dist/plugins/worker-image-imports.js +1 -1
  59. package/dist/routing/app-route-graph.js +1 -1
  60. package/dist/routing/app-router.js +1 -1
  61. package/dist/routing/pages-router.js +1 -1
  62. package/dist/server/app-action-forwarding.js +1 -1
  63. package/dist/server/app-browser-entry.js +1 -0
  64. package/dist/server/app-browser-error.js +1 -1
  65. package/dist/server/app-elements-wire.js +2 -2
  66. package/dist/server/app-middleware.js +1 -1
  67. package/dist/server/app-optimistic-routing.js +1 -1
  68. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  69. package/dist/server/app-page-cache-finalizer.js +14 -2
  70. package/dist/server/app-page-dispatch.js +1 -1
  71. package/dist/server/app-page-element-builder.js +3 -2
  72. package/dist/server/app-page-probe.js +3 -3
  73. package/dist/server/app-page-render.js +1 -0
  74. package/dist/server/app-page-request.d.ts +2 -0
  75. package/dist/server/app-page-request.js +16 -12
  76. package/dist/server/app-request-stage-dispatch.js +1 -1
  77. package/dist/server/app-request-stage-independent-entry.js +2 -2
  78. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  79. package/dist/server/app-route-handler-dispatch.js +3 -2
  80. package/dist/server/app-route-handler-execution.d.ts +1 -0
  81. package/dist/server/app-route-handler-execution.js +2 -2
  82. package/dist/server/app-route-handler-runtime.js +1 -1
  83. package/dist/server/app-router-entry.js +2 -2
  84. package/dist/server/app-rsc-handler.d.ts +9 -2
  85. package/dist/server/app-rsc-handler.js +30 -10
  86. package/dist/server/app-rsc-response-stage.js +6 -3
  87. package/dist/server/app-rsc-route-matching.js +1 -1
  88. package/dist/server/app-server-action-execution.js +35 -6
  89. package/dist/server/app-ssr-entry.js +7 -2
  90. package/dist/server/app-ssr-stream.js +25 -6
  91. package/dist/server/app-worker-stages.d.ts +4 -1
  92. package/dist/server/app-worker-stages.js +4 -4
  93. package/dist/server/artifact-compatibility.js +1 -1
  94. package/dist/server/client-reuse-manifest.js +1 -1
  95. package/dist/server/dev-lockfile.d.ts +1 -1
  96. package/dist/server/dev-lockfile.js +4 -1
  97. package/dist/server/dev-server.d.ts +1 -1
  98. package/dist/server/dev-server.js +28 -6
  99. package/dist/server/dev-stack-sourcemap.js +1 -1
  100. package/dist/server/html.d.ts +0 -20
  101. package/dist/server/html.js +16 -1
  102. package/dist/server/instrumentation.d.ts +4 -9
  103. package/dist/server/instrumentation.js +10 -15
  104. package/dist/server/isr-cache.js +3 -3
  105. package/dist/server/metadata-route-response.d.ts +1 -0
  106. package/dist/server/metadata-route-response.js +65 -0
  107. package/dist/server/middleware-runtime.js +3 -3
  108. package/dist/server/pages-asset-tags.d.ts +1 -0
  109. package/dist/server/pages-asset-tags.js +37 -2
  110. package/dist/server/pages-client-assets.d.ts +7 -0
  111. package/dist/server/pages-data-route.js +1 -1
  112. package/dist/server/pages-dev-hydration.d.ts +6 -0
  113. package/dist/server/pages-dev-hydration.js +19 -7
  114. package/dist/server/pages-document-initial-props.d.ts +1 -0
  115. package/dist/server/pages-document-initial-props.js +1 -1
  116. package/dist/server/pages-page-data.js +2 -2
  117. package/dist/server/pages-page-handler.js +5 -2
  118. package/dist/server/pages-page-response.d.ts +1 -0
  119. package/dist/server/pages-page-response.js +3 -2
  120. package/dist/server/pages-request-pipeline.js +3 -3
  121. package/dist/server/pages-request-stage-entry.js +3 -3
  122. package/dist/server/pages-revalidate.js +1 -1
  123. package/dist/server/prerender-route-params.js +1 -1
  124. package/dist/server/prod-server.js +19 -13
  125. package/dist/server/request-pipeline.js +2 -2
  126. package/dist/server/rsc-stream-hints.d.ts +12 -0
  127. package/dist/server/rsc-stream-hints.js +70 -35
  128. package/dist/server/seed-cache.js +3 -4
  129. package/dist/server/socket-error-backstop.js +1 -1
  130. package/dist/shims/cache-callable-runtime.d.ts +38 -0
  131. package/dist/shims/cache-callable-runtime.js +162 -11
  132. package/dist/shims/cache-runtime.d.ts +6 -8
  133. package/dist/shims/cache-runtime.js +89 -24
  134. package/dist/shims/constants.js +1 -1
  135. package/dist/shims/dynamic-preload-chunks.js +6 -2
  136. package/dist/shims/font-local.js +1 -1
  137. package/dist/shims/form.js +1 -1
  138. package/dist/shims/head.js +1 -1
  139. package/dist/shims/headers.js +2 -2
  140. package/dist/shims/image-config.js +2 -0
  141. package/dist/shims/internal/app-page-props-cache-key.d.ts +29 -0
  142. package/dist/shims/internal/app-page-props-cache-key.js +53 -1
  143. package/dist/shims/internal/format-url-object.d.ts +7 -0
  144. package/dist/shims/internal/format-url-object.js +53 -0
  145. package/dist/shims/internal/normalize-router-href.d.ts +15 -0
  146. package/dist/shims/internal/normalize-router-href.js +41 -0
  147. package/dist/shims/internal/pages-data-target.d.ts +4 -1
  148. package/dist/shims/internal/pages-data-target.js +28 -35
  149. package/dist/shims/internal/route-pattern-for-warning.d.ts +2 -2
  150. package/dist/shims/internal/route-pattern-for-warning.js +3 -3
  151. package/dist/shims/link.js +31 -92
  152. package/dist/shims/metadata.js +5 -4
  153. package/dist/shims/navigation.js +2 -2
  154. package/dist/shims/router-state.js +1 -1
  155. package/dist/shims/router.d.ts +8 -7
  156. package/dist/shims/router.js +312 -148
  157. package/dist/shims/script-nonce-context.d.ts +4 -1
  158. package/dist/shims/script-nonce-context.js +15 -6
  159. package/dist/shims/server.js +3 -3
  160. package/dist/utils/built-asset-url.js +1 -1
  161. package/dist/utils/client-runtime-metadata.d.ts +6 -0
  162. package/dist/utils/client-runtime-metadata.js +16 -0
  163. package/dist/utils/lazy-chunks.js +1 -1
  164. package/dist/utils/project.d.ts +1 -11
  165. package/dist/utils/project.js +20 -29
  166. package/dist/utils/react-version.d.ts +3 -1
  167. package/dist/utils/react-version.js +19 -1
  168. package/dist/utils/vite-cli-invocation.d.ts +21 -0
  169. package/dist/utils/vite-cli-invocation.js +144 -0
  170. package/package.json +10 -2
@@ -1,11 +1,11 @@
1
+ import { VINEXT_MW_CTX_HEADER } from "../utils/protocol-headers.js";
2
+ import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-request-headers.js";
3
+ import { parseEdgeRequestCookieHeader } from "../utils/parse-cookie.js";
4
+ import { isExternalUrl } from "../utils/external-url.js";
1
5
  import { splitPathSegments } from "../routing/utils.js";
2
6
  import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
3
- import { parseEdgeRequestCookieHeader } from "../utils/parse-cookie.js";
4
- import { VINEXT_MW_CTX_HEADER } from "../utils/protocol-headers.js";
5
7
  import { NEXT_ROUTER_STATE_TREE_HEADER, VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
6
8
  import { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER } from "./headers.js";
7
- import { buildRequestHeadersFromMiddlewareResponse } from "../utils/middleware-request-headers.js";
8
- import { isExternalUrl } from "../utils/external-url.js";
9
9
  import { internalServerErrorResponse, payloadTooLargeResponse } from "./http-error-responses.js";
10
10
  import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayload } from "./request-pipeline.js";
11
11
  import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
@@ -324,6 +324,31 @@ function dedupePendingCookies(cookies) {
324
324
  function isAppServerActionFunction(action) {
325
325
  return typeof action === "function";
326
326
  }
327
+ function normalizeDevServerReferenceId(id) {
328
+ const exportSeparator = id.indexOf("#");
329
+ if (exportSeparator === -1) return id;
330
+ const moduleId = id.slice(0, exportSeparator);
331
+ const cacheTag = moduleId.indexOf("$$cache=");
332
+ return (cacheTag === -1 ? moduleId : moduleId.slice(0, cacheTag)) + id.slice(exportSeparator);
333
+ }
334
+ function requiresRegisteredServerReferenceMatch(actionId) {
335
+ const exportSeparator = actionId.indexOf("#");
336
+ if (exportSeparator === -1) return true;
337
+ return !/^[0-9a-f]{12}$/.test(actionId.slice(0, exportSeparator));
338
+ }
339
+ function matchesRegisteredServerReference(action, actionId) {
340
+ const registeredId = Reflect.get(action, "$$id");
341
+ if (typeof registeredId !== "string") return false;
342
+ const normalizedRegisteredId = normalizeDevServerReferenceId(registeredId);
343
+ const normalizedActionId = normalizeDevServerReferenceId(actionId);
344
+ if (normalizedRegisteredId === normalizedActionId) return true;
345
+ const registeredSeparator = normalizedRegisteredId.indexOf("#");
346
+ const actionSeparator = normalizedActionId.indexOf("#");
347
+ if (registeredSeparator === -1 || actionSeparator === -1) return false;
348
+ if (normalizedRegisteredId.slice(0, registeredSeparator) !== normalizedActionId.slice(0, actionSeparator)) return false;
349
+ const registeredExport = normalizedRegisteredId.slice(registeredSeparator + 1);
350
+ return !/^\$\$vinext_cache_[0-9a-f]{64}$/.test(registeredExport);
351
+ }
327
352
  function getServerActionFailureMessage(error) {
328
353
  return error instanceof Error && error.message ? error.message : String(error);
329
354
  }
@@ -542,6 +567,10 @@ async function handleProgressiveServerActionRequest(options) {
542
567
  });
543
568
  return null;
544
569
  }
570
+ if (directActionId && requiresRegisteredServerReferenceMatch(directActionId) && !matchesRegisteredServerReference(action, directActionId)) return createActionNotFoundResponse(directActionId, {
571
+ clearRequestContext: options.clearRequestContext,
572
+ getAndClearPendingCookies: options.getAndClearPendingCookies
573
+ });
545
574
  const decodedActionId = Reflect.get(action, "$$id");
546
575
  if (typeof decodedActionId === "string" && decodedActionId !== directActionId && options.forwardAction) {
547
576
  const forwardResponse = await options.forwardAction(decodedActionId);
@@ -717,7 +746,7 @@ async function handleServerActionRscRequest(options) {
717
746
  });
718
747
  throw error;
719
748
  }
720
- if (!isAppServerActionFunction(loadedAction)) return createActionNotFoundResponse(options.actionId, {
749
+ if (!isAppServerActionFunction(loadedAction) || requiresRegisteredServerReferenceMatch(options.actionId) && !matchesRegisteredServerReference(loadedAction, options.actionId)) return createActionNotFoundResponse(options.actionId, {
721
750
  clearRequestContext: options.clearRequestContext,
722
751
  getAndClearPendingCookies: options.getAndClearPendingCookies
723
752
  });
@@ -747,7 +776,7 @@ async function handleServerActionRscRequest(options) {
747
776
  });
748
777
  throw error;
749
778
  }
750
- if (!isAppServerActionFunction(loadedAction)) return createActionNotFoundResponse(options.actionId, {
779
+ if (!isAppServerActionFunction(loadedAction) || requiresRegisteredServerReferenceMatch(options.actionId) && !matchesRegisteredServerReference(loadedAction, options.actionId)) return createActionNotFoundResponse(options.actionId, {
751
780
  clearRequestContext: options.clearRequestContext,
752
781
  getAndClearPendingCookies: options.getAndClearPendingCookies
753
782
  });
@@ -153,7 +153,7 @@ function extractBootstrapModuleUrl(bootstrapScriptContent) {
153
153
  }
154
154
  function buildModulePreloadHtml(bootstrapModuleUrl, nonce) {
155
155
  if (!bootstrapModuleUrl) return "";
156
- return `<link rel="modulepreload"${createNonceAttribute(nonce)} href="${escapeHtmlAttr(bootstrapModuleUrl)}" />\n`;
156
+ return `<link rel="modulepreload"${createNonceAttribute(nonce)} href="${escapeHtmlAttr(bootstrapModuleUrl)}" crossorigin="${pagesClientAssets.crossOrigin ?? ""}" />\n`;
157
157
  }
158
158
  function buildHeadInjectionHtml(navContext, bootstrapModuleUrl, formState, insertedHTML, fontHTML, dynamicStaleTimeSeconds, searchParamsFromBrowser, scriptNonce) {
159
159
  const navPayload = {
@@ -168,6 +168,7 @@ function requireNavigationContext(navContext) {
168
168
  }
169
169
  async function handleSsr(rscStream, navContext, fontData, options) {
170
170
  return runWithNavigationContext(async () => {
171
+ const assetCrossOrigin = pagesClientAssets.crossOrigin ?? "";
171
172
  const ssrNavigationContext = {
172
173
  ...requireNavigationContext(navContext),
173
174
  isStaticGeneration: options?.isStaticGeneration,
@@ -206,6 +207,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
206
207
  function VinextFlightRoot() {
207
208
  for (const moduleUrl of pagesClientAssets.appBootstrapPreinitModules ?? []) preinitModule(moduleUrl, {
208
209
  as: "script",
210
+ crossOrigin: assetCrossOrigin,
209
211
  nonce: options?.scriptNonce
210
212
  });
211
213
  if (!flightRoot) flightRoot = createFromReadableStream(ssrStream);
@@ -236,7 +238,10 @@ async function handleSsr(rscStream, navContext, fontData, options) {
236
238
  const maxHeadersLength = options?.reactMaxHeadersLength ?? DEFAULT_REACT_MAX_HEADERS_LENGTH;
237
239
  const captureHeaders = maxHeadersLength > 0;
238
240
  const renderOptions = {
239
- bootstrapModules: bootstrapModuleUrl ? [bootstrapModuleUrl] : void 0,
241
+ bootstrapModules: bootstrapModuleUrl ? [{
242
+ src: bootstrapModuleUrl,
243
+ crossOrigin: assetCrossOrigin
244
+ }] : void 0,
240
245
  formState: options?.formState ?? null,
241
246
  nonce: options?.scriptNonce,
242
247
  onHeaders: captureHeaders ? (headers) => {
@@ -2,6 +2,7 @@ import { createInlineScriptTag, escapeHtmlAttr, htmlTokenListContains, safeJsonS
2
2
  import { bytesToBase64, concatUint8Arrays } from "./app-rsc-embedded-chunks.js";
3
3
  import { NAVIGATION_RUNTIME_SYMBOL_DESCRIPTION } from "../client/navigation-runtime.js";
4
4
  //#region src/server/app-ssr-stream.ts
5
+ const RSC_EMBED_TEXT_BATCH_MAX_LENGTH = 65536;
5
6
  function waitAtLeastOneReactRenderTask() {
6
7
  return new Promise((resolve) => setTimeout(resolve, 0));
7
8
  }
@@ -43,6 +44,10 @@ function createRscEmbedTransform(embedStream, optionsOrNonce = {}) {
43
44
  const rawChunks = [];
44
45
  let reading = false;
45
46
  let mirroredNextFlightBootstrap = false;
47
+ const textDecoder = new TextDecoder("utf-8", {
48
+ fatal: true,
49
+ ignoreBOM: true
50
+ });
46
51
  async function pumpReader() {
47
52
  if (reading) return;
48
53
  reading = true;
@@ -52,11 +57,7 @@ function createRscEmbedTransform(embedStream, optionsOrNonce = {}) {
52
57
  if (result.done) break;
53
58
  rawChunks.push(result.value);
54
59
  try {
55
- const text = new TextDecoder("utf-8", {
56
- fatal: true,
57
- ignoreBOM: true
58
- }).decode(result.value);
59
- pendingChunks.push(text);
60
+ pendingChunks.push(textDecoder.decode(result.value));
60
61
  } catch {
61
62
  pendingChunks.push([3, bytesToBase64(result.value)]);
62
63
  }
@@ -74,8 +75,26 @@ function createRscEmbedTransform(embedStream, optionsOrNonce = {}) {
74
75
  if (pendingChunks.length === 0) return "";
75
76
  const chunks = pendingChunks;
76
77
  pendingChunks = [];
78
+ const embeddedChunks = [];
79
+ let textChunks = [];
80
+ let textLength = 0;
81
+ const flushTextChunks = () => {
82
+ if (textChunks.length === 0) return;
83
+ embeddedChunks.push(textChunks.join(""));
84
+ textChunks = [];
85
+ textLength = 0;
86
+ };
87
+ for (const chunk of chunks) if (typeof chunk === "string") {
88
+ if (textLength > 0 && textLength + chunk.length > RSC_EMBED_TEXT_BATCH_MAX_LENGTH) flushTextChunks();
89
+ textChunks.push(chunk);
90
+ textLength += chunk.length;
91
+ } else {
92
+ flushTextChunks();
93
+ embeddedChunks.push(chunk);
94
+ }
95
+ flushTextChunks();
77
96
  let scripts = "";
78
- for (const chunk of chunks) {
97
+ for (const chunk of embeddedChunks) {
79
98
  scripts += createInlineScriptTag(createNavigationRuntimeRscChunkScript(chunk), options.scriptNonce);
80
99
  if (options.mirrorNextFlight) {
81
100
  if (!mirroredNextFlightBootstrap) {
@@ -2,7 +2,7 @@ import { VinextResponseStageCacheability, VinextResponseStageDispatchOptions, Vi
2
2
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
3
3
  import { TrustedPrerenderState } from "./prerender-route-params.js";
4
4
  //#region src/server/app-worker-stages.d.ts
5
- export declare const APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION = 8;
5
+ export declare const APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION = 10;
6
6
  export declare const APP_METADATA_RESPONSE_STAGE_NO_MATCH_HEADER = "x-vinext-app-metadata-stage-no-match";
7
7
  type AppPageParams = Record<string, string | string[]>;
8
8
  type AppWorkerResponseStageEnvelope = {
@@ -24,6 +24,8 @@ type AppFullRequestWorkerResponseStageProps = AppWorkerResponseStageEnvelope & {
24
24
  export type AppMatchedWorkerResponseStageProps = AppWorkerResponseStageEnvelope & {
25
25
  kind: "app-page" | "app-route-handler";
26
26
  bypassInterceptionContextCache: boolean;
27
+ /** Origin-cache pathname; differs from cleanPathname after an internal rewrite. */
28
+ cachePathname: string;
27
29
  canUseCanonicalLoadingShell: boolean;
28
30
  canonicalPathname: string;
29
31
  cleanPathname: string;
@@ -56,6 +58,7 @@ type AppMetadataWorkerResponseStageProps = AppWorkerResponseStageEnvelope & {
56
58
  mountedSlotsHeader: string | null;
57
59
  renderMode: AppRscRenderMode;
58
60
  resolvedUrl: string;
61
+ routePathname: string;
59
62
  };
60
63
  type HybridPagesWorkerResponseStageProps = AppWorkerResponseStageEnvelope & {
61
64
  kind: "hybrid-pages";
@@ -1,6 +1,6 @@
1
1
  import { isTrustedPrerenderState } from "./prerender-route-params.js";
2
2
  //#region src/server/app-worker-stages.ts
3
- const APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION = 8;
3
+ const APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION = 10;
4
4
  const APP_METADATA_RESPONSE_STAGE_NO_MATCH_HEADER = "x-vinext-app-metadata-stage-no-match";
5
5
  const STATIC_FILE_SIGNAL_TOKEN_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
6
  /** Normalize a shared App page HEAD request onto its method-invariant GET representation. */
@@ -27,7 +27,7 @@ function isCanonicalHttpOrigin(value) {
27
27
  function isAppWorkerResponseStageProps(value) {
28
28
  if (!value || typeof value !== "object") return false;
29
29
  const props = value;
30
- if (props.protocolVersion !== 8 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || props.draftModeCookie !== null && typeof props.draftModeCookie !== "string" || props.middlewareCookieOverlay !== null && typeof props.middlewareCookieOverlay !== "string" || !isCanonicalHttpOrigin(props.requestOrigin) || props.scriptNonce !== null && typeof props.scriptNonce !== "string") return false;
30
+ if (props.protocolVersion !== 10 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || props.draftModeCookie !== null && typeof props.draftModeCookie !== "string" || props.middlewareCookieOverlay !== null && typeof props.middlewareCookieOverlay !== "string" || !isCanonicalHttpOrigin(props.requestOrigin) || props.scriptNonce !== null && typeof props.scriptNonce !== "string") return false;
31
31
  if (props.kind === "app-full-request") return typeof props.prerenderDiscovery === "boolean" && typeof props.staticFileSignalToken === "string" && STATIC_FILE_SIGNAL_TOKEN_RE.test(props.staticFileSignalToken) && (props.trustedPrerenderState === null || isTrustedPrerenderState(props.trustedPrerenderState));
32
32
  if (props.kind === "hybrid-pages") {
33
33
  const hybrid = props;
@@ -36,9 +36,9 @@ function isAppWorkerResponseStageProps(value) {
36
36
  }
37
37
  if (props.kind === "app-metadata" || props.kind === "app-not-found") {
38
38
  const special = props;
39
- return typeof special.canonicalPathname === "string" && special.canonicalPathname.startsWith("/") && typeof special.cleanPathname === "string" && special.cleanPathname.startsWith("/") && typeof special.isRscRequest === "boolean" && (special.mountedSlotsHeader === null || typeof special.mountedSlotsHeader === "string") && typeof special.resolvedUrl === "string" && special.resolvedUrl.startsWith("/") && (special.renderMode === "navigation" || special.renderMode === "prefetch-empty" || special.renderMode === "prefetch-dynamic-shell" || special.renderMode === "prefetch-loading-shell");
39
+ return typeof special.canonicalPathname === "string" && special.canonicalPathname.startsWith("/") && typeof special.cleanPathname === "string" && special.cleanPathname.startsWith("/") && typeof special.isRscRequest === "boolean" && (special.mountedSlotsHeader === null || typeof special.mountedSlotsHeader === "string") && typeof special.resolvedUrl === "string" && special.resolvedUrl.startsWith("/") && (props.kind !== "app-metadata" || typeof props.routePathname === "string" && props.routePathname.startsWith("/")) && (special.renderMode === "navigation" || special.renderMode === "prefetch-empty" || special.renderMode === "prefetch-dynamic-shell" || special.renderMode === "prefetch-loading-shell");
40
40
  }
41
- return (props.kind === "app-page" || props.kind === "app-route-handler") && typeof props.bypassInterceptionContextCache === "boolean" && typeof props.canUseCanonicalLoadingShell === "boolean" && typeof props.canonicalPathname === "string" && props.canonicalPathname.startsWith("/") && typeof props.cleanPathname === "string" && props.cleanPathname.startsWith("/") && (props.forceDynamic === void 0 || typeof props.forceDynamic === "boolean") && (props.interceptionContext === null || typeof props.interceptionContext === "string") && (props.interceptionId === null || typeof props.interceptionId === "string") && typeof props.isRscRequest === "boolean" && (props.matchKind === "interception" || props.matchKind === "request" || props.matchKind === "resolved") && (props.mountedSlotsHeader === null || typeof props.mountedSlotsHeader === "string") && isAppPageParams(props.params) && typeof props.resolvedUrl === "string" && props.resolvedUrl.startsWith("/") && typeof props.routePattern === "string" && props.routePattern.startsWith("/") && typeof props.routePathname === "string" && props.routePathname.startsWith("/") && (props.renderMode === "navigation" || props.renderMode === "prefetch-empty" || props.renderMode === "prefetch-dynamic-shell" || props.renderMode === "prefetch-loading-shell");
41
+ return (props.kind === "app-page" || props.kind === "app-route-handler") && typeof props.bypassInterceptionContextCache === "boolean" && typeof props.cachePathname === "string" && props.cachePathname.startsWith("/") && typeof props.canUseCanonicalLoadingShell === "boolean" && typeof props.canonicalPathname === "string" && props.canonicalPathname.startsWith("/") && typeof props.cleanPathname === "string" && props.cleanPathname.startsWith("/") && (props.forceDynamic === void 0 || typeof props.forceDynamic === "boolean") && (props.interceptionContext === null || typeof props.interceptionContext === "string") && (props.interceptionId === null || typeof props.interceptionId === "string") && typeof props.isRscRequest === "boolean" && (props.matchKind === "interception" || props.matchKind === "request" || props.matchKind === "resolved") && (props.mountedSlotsHeader === null || typeof props.mountedSlotsHeader === "string") && isAppPageParams(props.params) && typeof props.resolvedUrl === "string" && props.resolvedUrl.startsWith("/") && typeof props.routePattern === "string" && props.routePattern.startsWith("/") && typeof props.routePathname === "string" && props.routePathname.startsWith("/") && (props.renderMode === "navigation" || props.renderMode === "prefetch-empty" || props.renderMode === "prefetch-dynamic-shell" || props.renderMode === "prefetch-loading-shell");
42
42
  }
43
43
  function isSerializedHeaders(value) {
44
44
  return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
@@ -1,5 +1,5 @@
1
- import { fnv1a64 } from "../utils/hash.js";
2
1
  import { isUnknownRecord } from "../utils/record.js";
2
+ import { fnv1a64 } from "../utils/hash.js";
3
3
  //#region src/server/artifact-compatibility.ts
4
4
  const ARTIFACT_COMPATIBILITY_SCHEMA_VERSION = 1;
5
5
  const APP_ELEMENTS_SCHEMA_VERSION = 1;
@@ -1,6 +1,6 @@
1
+ import { isUnknownRecord } from "../utils/record.js";
1
2
  import { fnv1a64 } from "../utils/hash.js";
2
3
  import { parseAppElementsWireElementKey } from "./app-elements-wire-key.js";
3
- import { isUnknownRecord } from "../utils/record.js";
4
4
  import { parseArtifactCompatibilityEnvelope } from "./artifact-compatibility.js";
5
5
  import { isNonNegativeSafeInteger } from "../utils/number.js";
6
6
  //#region src/server/client-reuse-manifest.ts
@@ -36,7 +36,7 @@ export type DevServerInfo = {
36
36
  /** Project directory the server is running in. Used to detect stale entries. */
37
37
  cwd: string;
38
38
  };
39
- export type DevLockfile = {
39
+ type DevLockfile = {
40
40
  /** Update the lock file contents (e.g. once the port is known after listen). */
41
41
  update(info: DevServerInfo): void;
42
42
  /** Release the lock — deletes the file. Safe to call multiple times. */
@@ -99,7 +99,7 @@ function formatAlreadyRunningError(opts) {
99
99
  "Another vinext dev server appears to be running in this directory.",
100
100
  "",
101
101
  `Stale lock file: ${path.relative(cwd, lockfilePath)}`,
102
- "Remove it manually if no server is running, then re-run `vinext dev`."
102
+ "Remove it manually if no server is running, then re-run `vite dev`."
103
103
  ].join("\n");
104
104
  const killCommand = process.platform === "win32" ? `taskkill /PID ${existing.pid} /F` : `kill ${existing.pid}`;
105
105
  return [
@@ -160,7 +160,10 @@ function tryAcquireLockfile(opts) {
160
160
  lockfile: {
161
161
  path: lockfilePath,
162
162
  update(next) {
163
+ if (released) return;
163
164
  try {
165
+ const current = readLockfile(lockfilePath);
166
+ if (!current || current.pid !== ownerPid) return;
164
167
  writeLockfile(lockfilePath, next);
165
168
  } catch {}
166
169
  },
@@ -44,7 +44,7 @@ reactStrictMode?: boolean,
44
44
  /** Next.js `expireTime`, used when formatting terminal GSP responses. */
45
45
  expireTime?: number, crossOrigin?: string,
46
46
  /** Resolved Pages Router build ID shared with the dev data-route parser. */
47
- buildId?: string): (req: IncomingMessage, res: ServerResponse, url: string,
47
+ buildId?: string, clientMiddlewareMatcher?: unknown, classifyPageRoute?: (route: Route) => "static" | "server" | "none"): (req: IncomingMessage, res: ServerResponse, url: string,
48
48
  /** Status code override — propagated from middleware rewrite status. */
49
49
  statusCode?: number,
50
50
  /**
@@ -1,11 +1,12 @@
1
1
  import { closeAfterResponse, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
2
  import { createValidFileMatcher, findFileWithExts } from "../routing/file-matcher.js";
3
+ import { isBotUserAgent } from "../utils/html-limited-bots.js";
4
+ import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
5
+ import { sanitizeDestination } from "../config/config-matchers.js";
3
6
  import { patternToNextFormat } from "../routing/route-validation.js";
4
7
  import { matchRoute } from "../routing/pages-router.js";
5
- import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
6
8
  import { NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
7
9
  import { isDangerousScheme } from "../shims/url-safety.js";
8
- import { sanitizeDestination } from "../config/config-matchers.js";
9
10
  import { extractLocaleFromUrl as extractLocaleFromUrl$1, resolvePagesI18nRequest } from "./pages-i18n.js";
10
11
  import { importModule, reportRequestError } from "./instrumentation.js";
11
12
  import { NEVER_CACHE_CONTROL } from "./cache-control.js";
@@ -26,7 +27,6 @@ import { buildPagesReadinessNextData } from "./pages-readiness.js";
26
27
  import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
27
28
  import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
28
29
  import { callDocumentGetInitialProps } from "./document-initial-head.js";
29
- import { isBotUserAgent } from "../utils/html-limited-bots.js";
30
30
  import { hasPagesGetInitialProps, loadDevAppInitialProps, loadPagesGetInitialProps } from "./pages-get-initial-props.js";
31
31
  import { isSerializableProps } from "./pages-serializable-props.js";
32
32
  import { traceFindPageComponents, tracePagesData, tracePagesDocument, tracePagesDocumentStream } from "./pages-execution-tracing.js";
@@ -315,9 +315,20 @@ function extractLocaleFromUrl(url, i18nConfig) {
315
315
  * 4. Render the component to HTML
316
316
  * 5. Wrap in _document shell and send response
317
317
  */
318
- function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata, htmlLimitedBots, reactStrictMode = false, expireTime = PAGES_CACHE_ONE_YEAR_SECONDS, crossOrigin, buildId = process.env.__VINEXT_BUILD_ID ?? "development") {
318
+ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata, htmlLimitedBots, reactStrictMode = false, expireTime = PAGES_CACHE_ONE_YEAR_SECONDS, crossOrigin, buildId = process.env.__VINEXT_BUILD_ID ?? "development", clientMiddlewareMatcher, classifyPageRoute) {
319
319
  const matcher = fileMatcher ?? createValidFileMatcher();
320
320
  const pagePatterns = routes.map((r) => patternToNextFormat(r.pattern));
321
+ const pageLoaders = routes.map((route) => ({
322
+ pattern: patternToNextFormat(route.pattern),
323
+ moduleSource: createPagesDevModuleUrl(server.config.root, route.filePath, "/"),
324
+ dataKind: classifyPageRoute?.(route) ?? "none"
325
+ }));
326
+ const errorFile = findFileWithExts(pagesDir, "_error", matcher);
327
+ pageLoaders.push({
328
+ pattern: "/_error",
329
+ moduleSource: errorFile ? createPagesDevModuleUrl(server.config.root, errorFile, "/") : "next/error",
330
+ dataKind: "none"
331
+ });
321
332
  const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
322
333
  _alsRegistration.catch(() => {});
323
334
  return async (req, res, url, statusCode, isDataReq = false, originalUrl = url) => {
@@ -357,6 +368,9 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
357
368
  basePath,
358
369
  buildId,
359
370
  clientTraceMetadata,
371
+ pageLoaders,
372
+ hasMiddleware,
373
+ clientMiddlewareMatcher,
360
374
  locale: locale ?? currentDefaultLocale,
361
375
  locales: i18nConfig?.locales,
362
376
  defaultLocale: currentDefaultLocale,
@@ -798,12 +812,14 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
798
812
  pageModuleUrl,
799
813
  appModuleUrl,
800
814
  hasMiddleware,
801
- routeUrl: requestAsPath
815
+ routeUrl: requestAsPath,
816
+ clientMiddlewareMatcher
802
817
  }
803
818
  };
804
819
  const hydrationScript = createPagesDevHydrationScript({
805
820
  appModuleSource,
806
821
  pageModuleSource,
822
+ pageLoaders,
807
823
  reactStrictMode: reactStrictMode === true,
808
824
  replaceFallbackRoute: true,
809
825
  scriptNonce
@@ -1037,16 +1053,22 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1037
1053
  query: parseQueryString(url),
1038
1054
  buildId: context.buildId ?? process.env.__VINEXT_BUILD_ID ?? "development",
1039
1055
  isFallback: false,
1056
+ locale: context.locale,
1057
+ locales: context.locales,
1058
+ defaultLocale: context.defaultLocale,
1040
1059
  notFoundSrcPage: context.notFoundSrcPage,
1041
1060
  __vinext: {
1042
1061
  pageModuleUrl: errorModuleUrl ?? void 0,
1043
- appModuleUrl: appModuleUrl ?? void 0
1062
+ appModuleUrl: appModuleUrl ?? void 0,
1063
+ hasMiddleware: context.hasMiddleware,
1064
+ clientMiddlewareMatcher: context.clientMiddlewareMatcher
1044
1065
  }
1045
1066
  })}<\/script>`}\n${createPagesDevHydrationScript({
1046
1067
  appModuleSource,
1047
1068
  forceRouterReady: true,
1048
1069
  normalizePageProps: false,
1049
1070
  pageModuleSource: errorModuleSource,
1071
+ pageLoaders: context.pageLoaders,
1050
1072
  reactStrictMode: reactStrictMode === true,
1051
1073
  scriptNonce,
1052
1074
  setPagePatternsFromNextData: true
@@ -1,7 +1,7 @@
1
1
  import { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  import { VINEXT_ORIGINAL_STACK_TRACE_ENDPOINT } from "../utils/dev-stack-sourcemap-endpoint.js";
3
- import { readFile } from "node:fs/promises";
4
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { readFile } from "node:fs/promises";
5
5
  //#region src/server/dev-stack-sourcemap.ts
6
6
  const MAX_ORIGINAL_STACK_TRACE_BODY_BYTES = 1048576;
7
7
  const V8_PAREN_STACK_LINE = /^(\s*at\s+.*?\()(.+):(\d+):(\d+)(\)\s*)$/;
@@ -1,24 +1,4 @@
1
1
  //#region src/server/html.d.ts
2
- /**
3
- * HTML-safe JSON serialization for embedding data in <script> tags.
4
- *
5
- * JSON.stringify does NOT escape characters that are meaningful to the
6
- * HTML parser. If a JSON string value contains "</script>", the browser
7
- * closes the script tag early — anything after it executes as HTML.
8
- * This is a well-known stored XSS vector in SSR frameworks.
9
- *
10
- * Next.js mitigates this with htmlEscapeJsonString(). We do the same.
11
- *
12
- * Characters escaped:
13
- * < → \u003c (prevents </script> and <!-- breakout)
14
- * > → \u003e (prevents --> and other HTML close sequences)
15
- * & → \u0026 (prevents &lt; entity interpretation in XHTML)
16
- * \u2028 → \\u2028 (line separator — invalid in JS string literals pre-ES2019)
17
- * \u2029 → \\u2029 (paragraph separator — same)
18
- *
19
- * The result is valid JSON that is also safe to embed in any HTML context
20
- * without additional escaping.
21
- */
22
2
  export declare function safeJsonStringify(data: unknown): string;
23
3
  export declare function escapeHtmlAttr(value: string): string;
24
4
  export declare function htmlTokenListContains(value: string | null, token: string): boolean;
@@ -19,8 +19,23 @@
19
19
  * The result is valid JSON that is also safe to embed in any HTML context
20
20
  * without additional escaping.
21
21
  */
22
+ const HTML_UNSAFE_JSON_CHAR_RE = /[<>&\u2028\u2029]/;
23
+ const HTML_UNSAFE_JSON_CHARS_RE = /[<>&\u2028\u2029]/g;
24
+ const HTML_UNSAFE_JSON_ESCAPES = {
25
+ "<": "\\u003c",
26
+ ">": "\\u003e",
27
+ "&": "\\u0026",
28
+ "\u2028": "\\u2028",
29
+ "\u2029": "\\u2029"
30
+ };
31
+ function escapeHtmlUnsafeJsonChar(char) {
32
+ return HTML_UNSAFE_JSON_ESCAPES[char];
33
+ }
22
34
  function safeJsonStringify(data) {
23
- return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
35
+ const json = JSON.stringify(data);
36
+ if (json === void 0) throw new TypeError("Cannot serialize value as JSON");
37
+ if (!HTML_UNSAFE_JSON_CHAR_RE.test(json)) return json;
38
+ return json.replace(HTML_UNSAFE_JSON_CHARS_RE, escapeHtmlUnsafeJsonChar);
24
39
  }
25
40
  function escapeHtmlAttr(value) {
26
41
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -55,21 +55,16 @@ export declare function getOnRequestErrorHandler(): OnRequestErrorHandler | null
55
55
  /**
56
56
  * Load and execute the instrumentation file via a ModuleRunner.
57
57
  *
58
- * Called once during Pages Router server startup (`configureServer`). It:
58
+ * Called during Node development startup (`configureServer`). It:
59
59
  * 1. Loads the instrumentation module via `runner.import()`.
60
60
  * 2. Calls the `register()` function if exported.
61
61
  * 3. Stores the `onRequestError()` handler on `globalThis` so it is visible
62
62
  * to all Vite environment module graphs (SSR and the host process share
63
63
  * the same Node.js `globalThis`).
64
64
  *
65
- * **App Router** does not use this function. For App Router, `register()` is
66
- * emitted as a top-level `await` inside the generated RSC entry module so it
67
- * runs in the same Worker/environment as request handling.
68
- *
69
- * @param runner - A ModuleRunner created via `createDirectRunner()`. Must be
70
- * the same long-lived runner used for middleware and SSR so the module graph
71
- * is shared. Safe with all Vite plugin combinations, including
72
- * `@cloudflare/vite-plugin`, because it never touches the hot channel.
65
+ * @param runner - The existing RSC runner for App Router, or the long-lived
66
+ * direct runner for Pages-only apps. Reusing the request runner preserves
67
+ * module state initialized by `register()`.
73
68
  * @param instrumentationPath - Absolute path to the instrumentation file
74
69
  */
75
70
  export declare function runInstrumentation(runner: ModuleImporter, instrumentationPath: string): Promise<void>;
@@ -20,12 +20,12 @@ import fs from "node:fs";
20
20
  *
21
21
  * ## App Router
22
22
  *
23
- * For App Router, `register()` is baked directly into the generated RSC entry
24
- * as a top-level `await` at module evaluation time (see `entries/app-rsc-entry.ts`
25
- * `generateRscEntry`). This means it runs inside the Worker process (or RSC
26
- * Vite environment) — the same process that handles requests — before any
27
- * request is served. `runInstrumentation()` is NOT called from `configureServer`
28
- * for App Router.
23
+ * Node development eagerly calls `runInstrumentation()` through the same RSC
24
+ * runner used for requests. The generated RSC entry also awaits the shared
25
+ * registration promise before importing user server modules. This preserves
26
+ * react-server conditions, module state, and initialization order.
27
+ * Workers and production initialize through the generated entry in their own
28
+ * runtime, without an extra registration in the Node development host.
29
29
  *
30
30
  * The `onRequestError` handler is stored on `globalThis` so it is visible across
31
31
  * the RSC and SSR Vite environments (separate module graphs, same Node.js process).
@@ -81,21 +81,16 @@ function getOnRequestErrorHandler() {
81
81
  /**
82
82
  * Load and execute the instrumentation file via a ModuleRunner.
83
83
  *
84
- * Called once during Pages Router server startup (`configureServer`). It:
84
+ * Called during Node development startup (`configureServer`). It:
85
85
  * 1. Loads the instrumentation module via `runner.import()`.
86
86
  * 2. Calls the `register()` function if exported.
87
87
  * 3. Stores the `onRequestError()` handler on `globalThis` so it is visible
88
88
  * to all Vite environment module graphs (SSR and the host process share
89
89
  * the same Node.js `globalThis`).
90
90
  *
91
- * **App Router** does not use this function. For App Router, `register()` is
92
- * emitted as a top-level `await` inside the generated RSC entry module so it
93
- * runs in the same Worker/environment as request handling.
94
- *
95
- * @param runner - A ModuleRunner created via `createDirectRunner()`. Must be
96
- * the same long-lived runner used for middleware and SSR so the module graph
97
- * is shared. Safe with all Vite plugin combinations, including
98
- * `@cloudflare/vite-plugin`, because it never touches the hot channel.
91
+ * @param runner - The existing RSC runner for App Router, or the long-lived
92
+ * direct runner for Pages-only apps. Reusing the request runner preserves
93
+ * module state initialized by `register()`.
99
94
  * @param instrumentationPath - Absolute path to the instrumentation file
100
95
  */
101
96
  async function runInstrumentation(runner, instrumentationPath) {
@@ -1,5 +1,5 @@
1
- import { getRequestExecutionContext } from "../shims/request-context.js";
2
1
  import { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER } from "../utils/protocol-headers.js";
2
+ import { getRequestExecutionContext } from "../shims/request-context.js";
3
3
  import { reportRequestError } from "./instrumentation.js";
4
4
  import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
5
5
  import { fnv1a64 } from "../utils/hash.js";
@@ -163,7 +163,7 @@ function isrCacheKey(router, pathname, buildId) {
163
163
  * Cloudflare KV key under its 512-byte limit for long pathnames.
164
164
  */
165
165
  function appIsrCacheKey(pathname, suffix, buildId = process.env.__VINEXT_BUILD_ID) {
166
- return buildCacheKey(buildId ? `app:${buildId}` : "app", pathname, suffix);
166
+ return buildCacheKey(buildId ? `app:v2:${buildId}` : "app:v2", pathname, suffix);
167
167
  }
168
168
  function appIsrHtmlKey(pathname) {
169
169
  return appIsrCacheKey(pathname, "html");
@@ -194,7 +194,7 @@ function appIsrRscKey(pathname, mountedSlotsHeader, renderMode = APP_RSC_RENDER_
194
194
  return appIsrCacheKey(pathname, variant ? `rsc:${variant}` : "rsc");
195
195
  }
196
196
  function appIsrRouteKey(pathname) {
197
- return appIsrCacheKey(pathname, "route");
197
+ return appIsrCacheKey(pathname, pathname !== "/" && pathname.endsWith("/") ? "route:trailing-slash" : "route");
198
198
  }
199
199
  //#endregion
200
200
  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, triggerBackgroundRegeneration };
@@ -21,6 +21,7 @@ type MetadataRouteRequestOptions = {
21
21
  isrRouteKey?: (pathname: string) => string;
22
22
  isrSet?: MetadataRouteCacheSetter;
23
23
  makeThenableParams: MetadataRouteMakeThenableParams;
24
+ routePathname?: string;
24
25
  scheduleBackgroundRegeneration?: MetadataRouteBackgroundRegenerator;
25
26
  };
26
27
  export type PrerenderableMetadataRoute = {