vinext 0.1.0 → 0.1.1

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 (119) hide show
  1. package/dist/build/assets-ignore.d.ts +32 -0
  2. package/dist/build/assets-ignore.js +48 -0
  3. package/dist/build/client-build-config.d.ts +27 -1
  4. package/dist/build/client-build-config.js +58 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/client/navigation-runtime.d.ts +8 -0
  7. package/dist/client/navigation-runtime.js +1 -1
  8. package/dist/client/vinext-next-data.d.ts +2 -1
  9. package/dist/config/config-matchers.d.ts +20 -1
  10. package/dist/config/config-matchers.js +35 -1
  11. package/dist/config/next-config.d.ts +16 -3
  12. package/dist/config/next-config.js +30 -2
  13. package/dist/deploy.js +40 -304
  14. package/dist/entries/app-rsc-entry.d.ts +8 -2
  15. package/dist/entries/app-rsc-entry.js +54 -4
  16. package/dist/entries/app-rsc-manifest.js +20 -2
  17. package/dist/entries/pages-server-entry.js +9 -1
  18. package/dist/index.js +162 -217
  19. package/dist/plugins/postcss.js +18 -14
  20. package/dist/plugins/require-context.d.ts +6 -0
  21. package/dist/plugins/require-context.js +184 -0
  22. package/dist/routing/app-route-graph.d.ts +12 -1
  23. package/dist/routing/app-route-graph.js +137 -5
  24. package/dist/routing/route-pattern.d.ts +2 -1
  25. package/dist/routing/route-pattern.js +16 -1
  26. package/dist/server/api-handler.js +4 -0
  27. package/dist/server/app-browser-entry.js +84 -39
  28. package/dist/server/app-browser-interception-context.d.ts +2 -1
  29. package/dist/server/app-browser-interception-context.js +15 -2
  30. package/dist/server/app-browser-navigation-controller.d.ts +11 -1
  31. package/dist/server/app-browser-navigation-controller.js +77 -1
  32. package/dist/server/app-browser-popstate.d.ts +12 -3
  33. package/dist/server/app-browser-popstate.js +19 -4
  34. package/dist/server/app-browser-state.d.ts +3 -0
  35. package/dist/server/app-browser-state.js +6 -3
  36. package/dist/server/app-browser-visible-commit.js +9 -7
  37. package/dist/server/app-history-state.d.ts +45 -1
  38. package/dist/server/app-history-state.js +109 -1
  39. package/dist/server/app-page-boundary-render.js +41 -19
  40. package/dist/server/app-page-dispatch.d.ts +6 -0
  41. package/dist/server/app-page-dispatch.js +3 -1
  42. package/dist/server/app-page-element-builder.d.ts +1 -0
  43. package/dist/server/app-page-element-builder.js +22 -10
  44. package/dist/server/app-page-render.d.ts +6 -0
  45. package/dist/server/app-page-render.js +5 -3
  46. package/dist/server/app-page-request.d.ts +8 -6
  47. package/dist/server/app-page-request.js +12 -9
  48. package/dist/server/app-page-response.d.ts +2 -2
  49. package/dist/server/app-page-response.js +1 -1
  50. package/dist/server/app-page-route-wiring.js +2 -1
  51. package/dist/server/app-page-stream.d.ts +37 -2
  52. package/dist/server/app-page-stream.js +36 -3
  53. package/dist/server/app-pages-bridge.d.ts +16 -0
  54. package/dist/server/app-pages-bridge.js +23 -3
  55. package/dist/server/app-route-handler-cache.d.ts +1 -0
  56. package/dist/server/app-route-handler-cache.js +1 -0
  57. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  58. package/dist/server/app-route-handler-dispatch.js +2 -0
  59. package/dist/server/app-route-handler-execution.d.ts +1 -0
  60. package/dist/server/app-route-handler-execution.js +1 -0
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  62. package/dist/server/app-route-handler-runtime.js +3 -2
  63. package/dist/server/app-rsc-handler.d.ts +1 -0
  64. package/dist/server/app-rsc-handler.js +4 -3
  65. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  66. package/dist/server/app-rsc-route-matching.js +29 -4
  67. package/dist/server/app-server-action-execution.d.ts +11 -1
  68. package/dist/server/app-server-action-execution.js +68 -10
  69. package/dist/server/app-ssr-entry.d.ts +6 -0
  70. package/dist/server/app-ssr-entry.js +17 -1
  71. package/dist/server/dev-server.d.ts +1 -1
  72. package/dist/server/dev-server.js +54 -31
  73. package/dist/server/isr-cache.d.ts +37 -1
  74. package/dist/server/isr-cache.js +85 -1
  75. package/dist/server/navigation-planner.js +5 -3
  76. package/dist/server/navigation-trace.d.ts +1 -1
  77. package/dist/server/pages-node-compat.d.ts +2 -0
  78. package/dist/server/pages-node-compat.js +4 -0
  79. package/dist/server/pages-page-data.d.ts +10 -7
  80. package/dist/server/pages-page-data.js +4 -2
  81. package/dist/server/pages-page-handler.d.ts +9 -2
  82. package/dist/server/pages-page-handler.js +29 -16
  83. package/dist/server/pages-page-response.d.ts +11 -2
  84. package/dist/server/pages-page-response.js +8 -1
  85. package/dist/server/pages-readiness.d.ts +36 -0
  86. package/dist/server/pages-readiness.js +21 -0
  87. package/dist/server/pages-request-pipeline.d.ts +99 -0
  88. package/dist/server/pages-request-pipeline.js +209 -0
  89. package/dist/server/pages-revalidate.d.ts +15 -0
  90. package/dist/server/pages-revalidate.js +19 -0
  91. package/dist/server/prod-server.d.ts +6 -2
  92. package/dist/server/prod-server.js +101 -217
  93. package/dist/server/socket-error-backstop.d.ts +19 -1
  94. package/dist/server/socket-error-backstop.js +77 -4
  95. package/dist/shims/app-router-scroll.js +22 -4
  96. package/dist/shims/cache-runtime.js +31 -1
  97. package/dist/shims/error-boundary.d.ts +21 -11
  98. package/dist/shims/error-boundary.js +8 -1
  99. package/dist/shims/fetch-cache.d.ts +14 -1
  100. package/dist/shims/fetch-cache.js +18 -1
  101. package/dist/shims/hash-scroll.d.ts +1 -0
  102. package/dist/shims/hash-scroll.js +3 -1
  103. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  104. package/dist/shims/internal/link-status-registry.js +42 -0
  105. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  106. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  107. package/dist/shims/internal/utils.d.ts +1 -0
  108. package/dist/shims/link.js +20 -6
  109. package/dist/shims/navigation.d.ts +2 -2
  110. package/dist/shims/navigation.js +63 -7
  111. package/dist/shims/router-state.d.ts +1 -0
  112. package/dist/shims/router-state.js +2 -0
  113. package/dist/shims/router.d.ts +6 -3
  114. package/dist/shims/router.js +128 -21
  115. package/dist/utils/client-build-manifest.d.ts +8 -1
  116. package/dist/utils/client-build-manifest.js +30 -5
  117. package/dist/utils/client-entry-manifest.d.ts +11 -0
  118. package/dist/utils/client-entry-manifest.js +29 -0
  119. package/package.json +5 -1
@@ -110,6 +110,15 @@ function isRequestBodyTooLarge(error) {
110
110
  return error instanceof Error && error.message === "Request body too large";
111
111
  }
112
112
  /**
113
+ * Build the error thrown when a server-action request body exceeds the
114
+ * configured size limit. Matches Next.js' `Body exceeded {limit} limit.`
115
+ * message + docs link (action-handler.ts) verbatim — including the original
116
+ * config string (e.g. "2mb") — so it reads identically in logs.
117
+ */
118
+ function createBodyExceededError(limitLabel) {
119
+ return /* @__PURE__ */ new Error(`Body exceeded ${limitLabel} limit.\nTo configure the body size limit for Server Actions, see: https://nextjs.org/docs/app/api-reference/next-config-js/serverActions#bodysizelimit`);
120
+ }
121
+ /**
113
122
  * Collapse repeated `cookies().set(name, ...)` / `cookies().delete(name)`
114
123
  * calls down to the last value per name, matching Next.js'
115
124
  * `MutableRequestCookiesAdapter` semantics. Next.js stores response cookies in
@@ -325,7 +334,13 @@ async function handleProgressiveServerActionRequest(options) {
325
334
  return payloadResponse;
326
335
  }
327
336
  const action = await options.decodeAction(body);
328
- if (!isAppServerActionFunction(action)) return null;
337
+ if (!isAppServerActionFunction(action)) {
338
+ if (options.hasPageRoute) return createActionNotFoundResponse(null, {
339
+ clearRequestContext: options.clearRequestContext,
340
+ getAndClearPendingCookies: options.getAndClearPendingCookies
341
+ });
342
+ return null;
343
+ }
329
344
  let actionRedirect = null;
330
345
  let actionError = void 0;
331
346
  let actionFailed = false;
@@ -411,23 +426,66 @@ async function handleProgressiveServerActionRequest(options) {
411
426
  return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action parsing failed: " + getServerActionFailureMessage(error));
412
427
  }
413
428
  }
429
+ /**
430
+ * Render the response for a fetch (client-invoked) server action whose request
431
+ * body exceeds the configured `serverActions.bodySizeLimit`.
432
+ *
433
+ * Next.js does not return a bare 413 here: it throws the body-exceeded error
434
+ * before the action runs, then — for fetch actions — emits a Flight response
435
+ * with status 500 carrying the rejected action result, so the nearest client
436
+ * error boundary catches it (see action-handler.ts, the `isFetchAction` branch
437
+ * of the generic error path). vinext mirrors that by rendering a Flight stream
438
+ * with `returnValue: { ok: false }` and no page root (the action never ran, so
439
+ * nothing was revalidated and the page render is skipped). A bare 413 plain
440
+ * response would bypass the boundary and surface the wrong status/content-type.
441
+ */
442
+ async function renderFetchActionBodyExceededResponse(options) {
443
+ const error = createBodyExceededError(options.maxActionBodySizeLabel);
444
+ console.error("[vinext] Server action error:", error);
445
+ options.reportRequestError(normalizeError(error), {
446
+ path: options.cleanPathname,
447
+ method: options.request.method,
448
+ headers: Object.fromEntries(options.request.headers.entries())
449
+ }, {
450
+ routerKind: "App Router",
451
+ routePath: options.cleanPathname,
452
+ routeType: "action"
453
+ });
454
+ getAndClearActionRevalidationKind();
455
+ options.getAndClearPendingCookies();
456
+ const returnValue = {
457
+ ok: false,
458
+ data: options.sanitizeErrorForClient(error)
459
+ };
460
+ const temporaryReferences = options.createTemporaryReferenceSet();
461
+ const onRenderError = options.createRscOnErrorHandler(options.request, options.cleanPathname, options.cleanPathname);
462
+ const rscStream = await options.renderToReadableStream({ returnValue }, {
463
+ temporaryReferences,
464
+ onError: onRenderError
465
+ });
466
+ const headers = new Headers({
467
+ "Content-Type": VINEXT_RSC_CONTENT_TYPE,
468
+ Vary: VINEXT_RSC_VARY_HEADER
469
+ });
470
+ applyEdgeRuntimeHeader(headers, options.isEdgeRuntime);
471
+ mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
472
+ applyRscCompatibilityIdHeader(headers);
473
+ return createServerActionRscResponse(rscStream, {
474
+ status: 500,
475
+ headers
476
+ }, options.clearRequestContext);
477
+ }
414
478
  async function handleServerActionRscRequest(options) {
415
479
  if (options.request.method.toUpperCase() !== "POST" || !options.actionId) return null;
416
480
  const csrfResponse = validateCsrfOrigin(options.request, options.allowedOrigins);
417
481
  if (csrfResponse) return csrfResponse;
418
- if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) {
419
- options.clearRequestContext();
420
- return payloadTooLargeResponse();
421
- }
482
+ if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) return renderFetchActionBodyExceededResponse(options);
422
483
  try {
423
484
  let body;
424
485
  try {
425
486
  body = options.contentType.startsWith("multipart/form-data") ? await options.readFormDataWithLimit(options.request, options.maxActionBodySize) : await options.readBodyWithLimit(options.request, options.maxActionBodySize);
426
487
  } catch (error) {
427
- if (isRequestBodyTooLarge(error)) {
428
- options.clearRequestContext();
429
- return payloadTooLargeResponse();
430
- }
488
+ if (isRequestBodyTooLarge(error)) return renderFetchActionBodyExceededResponse(options);
431
489
  throw error;
432
490
  }
433
491
  const payloadResponse = await validateServerActionPayload(body);
@@ -599,7 +657,7 @@ async function handleServerActionRscRequest(options) {
599
657
  let errorPattern = match ? match.route.pattern : options.cleanPathname;
600
658
  if (match) {
601
659
  const { route: actionRoute, params: actionParams } = match;
602
- const actionRerenderTarget = resolveAppPageActionRerenderTarget({
660
+ const actionRerenderTarget = await resolveAppPageActionRerenderTarget({
603
661
  cleanPathname: options.cleanPathname,
604
662
  currentParams: actionParams,
605
663
  currentRoute: actionRoute,
@@ -30,6 +30,12 @@ declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navContext: Na
30
30
  * SSR head. Undefined or empty disables emission entirely.
31
31
  */
32
32
  clientTraceMetadata?: readonly string[];
33
+ /**
34
+ * Maximum total length (in characters) of the preload `Link` header React
35
+ * emits during SSR. `0` disables emission. From `reactMaxHeadersLength` in
36
+ * `next.config`. Undefined falls back to React's own default.
37
+ */
38
+ reactMaxHeadersLength?: number;
33
39
  rootParams?: RootParams; /** Dev-only: original server error to surface in the browser overlay. */
34
40
  initialDevServerError?: unknown;
35
41
  /** When true, wait for the full React tree (including Suspense boundaries)
@@ -24,6 +24,13 @@ import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.e
24
24
  import { createFromReadableStream } from "@vitejs/plugin-rsc/ssr";
25
25
  import clientReferences from "virtual:vite-rsc/client-references";
26
26
  //#region src/server/app-ssr-entry.ts
27
+ /**
28
+ * Default cap for the preload `Link` header, matching Next.js's
29
+ * `defaultConfig.reactMaxHeadersLength`. Used when no config value threads
30
+ * through (e.g. error-boundary renders) so React's internal cap agrees with
31
+ * the response-layer combine cap.
32
+ */
33
+ const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6e3;
27
34
  const clientReferencePreloader = createClientReferencePreloader({
28
35
  getReferences() {
29
36
  return clientReferences;
@@ -175,7 +182,15 @@ async function handleSsr(rscStream, navContext, fontData, options) {
175
182
  const ssrRoot = withScriptNonce(createElement(BeforeInteractiveContext.Provider, { value: registerBeforeInteractiveInlineScript }, ssrTree), options?.scriptNonce);
176
183
  const bootstrapModuleUrl = extractBootstrapModuleUrl(await import.meta.viteRsc.loadBootstrapScriptContent("index"));
177
184
  const errorMetaRenderer = createSsrErrorMetaRenderer({ basePath: options?.basePath });
185
+ let reactLinkHeader = "";
186
+ const maxHeadersLength = options?.reactMaxHeadersLength ?? DEFAULT_REACT_MAX_HEADERS_LENGTH;
187
+ const captureHeaders = maxHeadersLength > 0;
178
188
  const htmlStream = await renderToReadableStream(ssrRoot, {
189
+ onHeaders: captureHeaders ? (headers) => {
190
+ const link = headers.get("Link");
191
+ if (link) reactLinkHeader = link;
192
+ } : void 0,
193
+ maxHeadersLength: captureHeaders ? maxHeadersLength : void 0,
179
194
  bootstrapModules: bootstrapModuleUrl ? [bootstrapModuleUrl] : void 0,
180
195
  formState: options?.formState ?? null,
181
196
  nonce: options?.scriptNonce,
@@ -210,7 +225,8 @@ async function handleSsr(rscStream, navContext, fontData, options) {
210
225
  return {
211
226
  htmlStream: deferUntilStreamConsumed(htmlStream.pipeThrough(createTickBufferedTransform(rscEmbed, getInsertedHTML, getBeforeInteractiveHeadHTML, inlineCssManifest, inlineCssFontStyles, inlineCssFontStyleFallbackHTML, options?.scriptNonce)), cleanup),
212
227
  metadataReady: Promise.resolve(),
213
- capturedRscData: options?.capturedRscDataRef?.value ?? null
228
+ capturedRscData: options?.capturedRscDataRef?.value ?? null,
229
+ linkHeader: reactLinkHeader
214
230
  };
215
231
  } catch (error) {
216
232
  cleanup();
@@ -36,7 +36,7 @@ declare function parseCookieLocale(req: IncomingMessage, i18nConfig: NextI18nCon
36
36
  * 4. Render the component to HTML
37
37
  * 5. Wrap in _document shell and send response
38
38
  */
39
- declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean,
39
+ declare function createSSRHandler(server: ViteDevServer, runner: ModuleImporter, routes: Route[], pagesDir: string, i18nConfig?: NextI18nConfig | null, fileMatcher?: ValidFileMatcher, basePath?: string, trailingSlash?: boolean, hasMiddleware?: boolean, hasRewrites?: boolean,
40
40
  /**
41
41
  * Allow-list of OpenTelemetry propagation keys to emit as `<meta>` tags
42
42
  * in the SSR head. Sourced from `experimental.clientTraceMetadata` in
@@ -6,9 +6,9 @@ import { normalizeStaticPathname } from "../routing/route-pattern.js";
6
6
  import { importModule, reportRequestError } from "./instrumentation.js";
7
7
  import { buildCacheStateHeaders } from "./cache-headers.js";
8
8
  import { _runWithCacheState } from "../shims/cache.js";
9
- import { buildPagesCacheValue, getRevalidateDuration, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
10
- import { runWithPrivateCache } from "../shims/cache-runtime.js";
9
+ import { PRERENDER_REVALIDATE_HEADER, buildPagesCacheValue, getRevalidateDuration, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, setRevalidateDuration, triggerBackgroundRegeneration } from "./isr-cache.js";
11
10
  import { ensureFetchPatch, runWithFetchCache } from "../shims/fetch-cache.js";
11
+ import { runWithPrivateCache } from "../shims/cache-runtime.js";
12
12
  import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
13
13
  import "../shims/router-state.js";
14
14
  import { runWithHeadState } from "../shims/head-state.js";
@@ -20,6 +20,7 @@ import { getScriptNonceFromNodeHeaderSources } from "./csp.js";
20
20
  import { logRequest, now } from "./request-log.js";
21
21
  import { detectLocaleFromAcceptLanguage, extractLocaleFromUrl as extractLocaleFromUrl$1, parseCookieLocaleFromHeader, resolvePagesI18nRequest } from "./pages-i18n.js";
22
22
  import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
23
+ import { buildPagesReadinessNextData } from "./pages-readiness.js";
23
24
  import { resolvePagesPageMethodResponse } from "./pages-page-method.js";
24
25
  import { isSerializableProps } from "./pages-serializable-props.js";
25
26
  import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
@@ -183,8 +184,9 @@ function parseCookieLocale(req, i18nConfig) {
183
184
  * 4. Render the component to HTML
184
185
  * 5. Wrap in _document shell and send response
185
186
  */
186
- function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, clientTraceMetadata) {
187
+ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata) {
187
188
  const matcher = fileMatcher ?? createValidFileMatcher();
189
+ const pagePatterns = routes.map((r) => patternToNextFormat(r.pattern));
188
190
  const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
189
191
  _alsRegistration.catch(() => {});
190
192
  return async (req, res, url, statusCode, isDataReq = false) => {
@@ -241,15 +243,6 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
241
243
  try {
242
244
  await _alsRegistration;
243
245
  const routerShim = await importModule(runner, "next/router");
244
- if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext({
245
- pathname: patternToNextFormat(route.pattern),
246
- query,
247
- asPath: url,
248
- locale: locale ?? currentDefaultLocale,
249
- locales: i18nConfig?.locales,
250
- defaultLocale: currentDefaultLocale,
251
- domainLocales
252
- });
253
246
  if (i18nConfig) {
254
247
  await runner.import("vinext/i18n-state");
255
248
  const i18nCtx = await importModule(runner, "vinext/i18n-context");
@@ -262,6 +255,28 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
262
255
  });
263
256
  }
264
257
  const pageModule = await importModule(runner, route.filePath);
258
+ let AppComponent = null;
259
+ const appPath = path.join(pagesDir, "_app");
260
+ if (findFileWithExtensions(appPath, matcher)) try {
261
+ AppComponent = (await importModule(runner, appPath)).default ?? null;
262
+ } catch {}
263
+ const pagesNextData = buildPagesReadinessNextData({
264
+ pageModule,
265
+ appComponent: AppComponent,
266
+ hasRewrites
267
+ });
268
+ const navigationIsReady = typeof routerShim.getPagesNavigationIsReadyFromSerializedState === "function" ? routerShim.getPagesNavigationIsReadyFromSerializedState(patternToNextFormat(route.pattern), new URL(url, "http://_").search, pagesNextData) : true;
269
+ if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext({
270
+ pathname: patternToNextFormat(route.pattern),
271
+ query,
272
+ asPath: url,
273
+ navigationIsReady,
274
+ nextData: pagesNextData,
275
+ locale: locale ?? currentDefaultLocale,
276
+ locales: i18nConfig?.locales,
277
+ defaultLocale: currentDefaultLocale,
278
+ domainLocales
279
+ });
265
280
  _compileEnd = now();
266
281
  const PageComponent = pageModule.default;
267
282
  if (!PageComponent) {
@@ -323,6 +338,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
323
338
  pathname: patternToNextFormat(route.pattern),
324
339
  query,
325
340
  asPath: url,
341
+ navigationIsReady: false,
326
342
  locale: locale ?? currentDefaultLocale,
327
343
  locales: i18nConfig?.locales,
328
344
  defaultLocale: currentDefaultLocale,
@@ -385,7 +401,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
385
401
  if (typeof pageModule.getStaticProps === "function" && !isFallbackRender) {
386
402
  const cacheKey = pagesIsrCacheKey(url.split("?")[0]);
387
403
  const cached = await isrGet(cacheKey);
388
- if (cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
404
+ const isOnDemandRevalidate = isOnDemandRevalidateRequest(req.headers[PRERENDER_REVALIDATE_HEADER]);
405
+ if (!isOnDemandRevalidate && cached && !cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
389
406
  const cachedHtml = cached.value.value.html;
390
407
  const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
391
408
  const revalidateSecs = getRevalidateDuration(cacheKey) ?? 60;
@@ -399,7 +416,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
399
416
  res.end(transformedHtml);
400
417
  return;
401
418
  }
402
- if (cached && cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
419
+ if (!isOnDemandRevalidate && cached && cached.isStale && cached.value.value?.kind === "PAGES" && !scriptNonce && !isDataReq) {
403
420
  const cachedHtml = cached.value.value.html;
404
421
  const transformedHtml = await server.transformIndexHtml(url, cachedHtml);
405
422
  triggerBackgroundRegeneration(cacheKey, async () => {
@@ -420,6 +437,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
420
437
  pathname: patternToNextFormat(route.pattern),
421
438
  query,
422
439
  asPath: url,
440
+ navigationIsReady,
423
441
  locale: locale ?? currentDefaultLocale,
424
442
  locales: i18nConfig?.locales,
425
443
  defaultLocale: currentDefaultLocale,
@@ -450,6 +468,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
450
468
  const viteRoot = server.config?.root;
451
469
  const regenPageUrl = viteRoot ? "/" + path.relative(viteRoot, route.filePath) : route.filePath;
452
470
  const regenAppUrl = RegenApp ? viteRoot ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app")) : path.join(pagesDir, "_app") : null;
471
+ const freshPagesNextData = {
472
+ ...pagesNextData,
473
+ __vinext: {
474
+ ...pagesNextData.__vinext,
475
+ pageModuleUrl: regenPageUrl,
476
+ appModuleUrl: regenAppUrl,
477
+ hasMiddleware
478
+ }
479
+ };
453
480
  await isrSet(cacheKey, buildPagesCacheValue(`<!DOCTYPE html><html><head></head><body><div id="__next">${freshBody}</div>${`<script>window.__NEXT_DATA__ = ${safeJsonStringify({
454
481
  props: { pageProps: freshProps },
455
482
  page: patternToNextFormat(route.pattern),
@@ -460,11 +487,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
460
487
  locales: i18nConfig?.locales,
461
488
  defaultLocale: currentDefaultLocale,
462
489
  domainLocales,
463
- __vinext: {
464
- pageModuleUrl: regenPageUrl,
465
- appModuleUrl: regenAppUrl,
466
- hasMiddleware
467
- }
490
+ ...freshPagesNextData
468
491
  })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}<\/script>`}\n ${cachedHtml.match(/<script type="module">[\s\S]*?<\/script>/)?.[0] ?? ""}</body></html>`, freshProps), revalidate);
469
492
  setRevalidateDuration(cacheKey, revalidate);
470
493
  }
@@ -491,7 +514,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
491
514
  locale: locale ?? currentDefaultLocale,
492
515
  locales: i18nConfig?.locales,
493
516
  defaultLocale: currentDefaultLocale,
494
- revalidateReason: "stale"
517
+ revalidateReason: isOnDemandRevalidate ? "on-demand" : "stale"
495
518
  };
496
519
  const result = await pageModule.getStaticProps(context);
497
520
  if (result && "props" in result) pageProps = result.props;
@@ -536,11 +559,6 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
536
559
  _renderEnd = now();
537
560
  return;
538
561
  }
539
- let AppComponent = null;
540
- const appPath = path.join(pagesDir, "_app");
541
- if (findFileWithExtensions(appPath, matcher)) try {
542
- AppComponent = (await importModule(runner, appPath)).default ?? null;
543
- } catch {}
544
562
  const createElement = React.createElement;
545
563
  let element;
546
564
  const wrapWithRouterContext = routerShim.wrapWithRouterContext;
@@ -584,6 +602,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
584
602
  const viteRoot = server.config.root;
585
603
  const pageModuleUrl = "/" + path.relative(viteRoot, route.filePath);
586
604
  const appModuleUrl = AppComponent ? "/" + path.relative(viteRoot, path.join(pagesDir, "_app")) : null;
605
+ const serializedPagesNextData = {
606
+ ...pagesNextData,
607
+ __vinext: {
608
+ ...pagesNextData.__vinext,
609
+ pageModuleUrl,
610
+ appModuleUrl,
611
+ hasMiddleware
612
+ }
613
+ };
587
614
  const hydrationScript = `
588
615
  <script type="module"${nonceAttr}>
589
616
  import "vinext/instrumentation-client";
@@ -641,18 +668,14 @@ hydrate();
641
668
  locales: i18nConfig?.locales,
642
669
  defaultLocale: currentDefaultLocale,
643
670
  domainLocales,
644
- __vinext: {
645
- pageModuleUrl,
646
- appModuleUrl,
647
- hasMiddleware
648
- }
671
+ ...serializedPagesNextData
649
672
  })}${i18nConfig ? `;window.__VINEXT_LOCALE__=${safeJsonStringify(locale ?? currentDefaultLocale)};window.__VINEXT_LOCALES__=${safeJsonStringify(i18nConfig.locales)};window.__VINEXT_DEFAULT_LOCALE__=${safeJsonStringify(currentDefaultLocale)}` : ""}`, scriptNonce);
650
673
  const docPath = path.join(pagesDir, "_document");
651
674
  let DocumentComponent = null;
652
675
  if (findFileWithExtensions(docPath, matcher)) try {
653
676
  DocumentComponent = (await runner.import(docPath)).default ?? null;
654
677
  } catch {}
655
- const allScripts = `${nextDataScript}\n ${hydrationScript}`;
678
+ const allScripts = `${nextDataScript}\n ${createInlineScriptTag(`window.__VINEXT_PAGE_PATTERNS__=${safeJsonStringify(pagePatterns)}`, scriptNonce)}\n ${hydrationScript}`;
656
679
  const extraHeaders = { ...gsspExtraHeaders };
657
680
  if (isrRevalidateSeconds) if (scriptNonce) extraHeaders["Cache-Control"] = "no-store, must-revalidate";
658
681
  else {
@@ -5,6 +5,42 @@ import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
5
5
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
6
6
 
7
7
  //#region src/server/isr-cache.d.ts
8
+ /**
9
+ * Header set on the internal request that `res.revalidate()` issues to
10
+ * trigger on-demand ISR regeneration of a Pages Router route. Mirrors Next.js's
11
+ * `PRERENDER_REVALIDATE_HEADER` (`x-prerender-revalidate`) — see
12
+ * `.nextjs-ref/packages/next/src/lib/constants.ts`.
13
+ *
14
+ * SECURITY: in Next.js this header is NOT a presence flag — it carries the
15
+ * secret `previewModeId`, and `checkIsOnDemandRevalidate`
16
+ * (`.nextjs-ref/packages/next/src/server/api-utils/index.ts`) only treats a
17
+ * request as on-demand revalidation when the value *equals* that secret. If we
18
+ * gated on presence alone, any external client could send
19
+ * `x-prerender-revalidate: <anything>` to force synchronous regeneration of any
20
+ * ISR page, bypassing the fresh/stale cache short-circuits — a
21
+ * cache-stampede/DoS vector. We therefore validate the value against
22
+ * {@link getRevalidateSecret} (a build-time secret shared across all Workers
23
+ * isolates) with a constant-time comparison, and only the matching value (sent
24
+ * by our own `res.revalidate()`) is honored.
25
+ */
26
+ declare const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
27
+ /**
28
+ * Companion header to {@link PRERENDER_REVALIDATE_HEADER}. When set,
29
+ * `res.revalidate(path, { unstable_onlyGenerated: true })` only revalidates the
30
+ * path if it was already generated, and a 404 response counts as a successful
31
+ * no-op. Mirrors Next.js's `PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER`
32
+ * (`x-prerender-revalidate-if-generated`) — see
33
+ * `.nextjs-ref/packages/next/src/lib/constants.ts`.
34
+ */
35
+ declare const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
36
+ declare function getRevalidateSecret(): string;
37
+ /**
38
+ * Authorize an incoming request as an on-demand revalidation trigger. Mirrors
39
+ * Next.js's `checkIsOnDemandRevalidate`: the {@link PRERENDER_REVALIDATE_HEADER}
40
+ * value must *equal* the process revalidate secret. Header presence alone is
41
+ * NOT sufficient — see the security note on {@link PRERENDER_REVALIDATE_HEADER}.
42
+ */
43
+ declare function isOnDemandRevalidateRequest(headerValue: string | string[] | null | undefined): boolean;
8
44
  type ISRCacheEntry = {
9
45
  value: CacheHandlerValue;
10
46
  isStale: boolean;
@@ -88,4 +124,4 @@ declare function setRevalidateDuration(key: string, seconds: number): void;
88
124
  */
89
125
  declare function getRevalidateDuration(key: string): number | undefined;
90
126
  //#endregion
91
- export { ISRCacheEntry, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
127
+ export { ISRCacheEntry, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
@@ -7,6 +7,90 @@ import { APP_RSC_RENDER_MODE_NAVIGATION, getRscRenderModeCacheVariant } from "./
7
7
  import { normalizeAppPageInterceptionProofPathname } from "./app-page-render-identity.js";
8
8
  //#region src/server/isr-cache.ts
9
9
  /**
10
+ * Header set on the internal request that `res.revalidate()` issues to
11
+ * trigger on-demand ISR regeneration of a Pages Router route. Mirrors Next.js's
12
+ * `PRERENDER_REVALIDATE_HEADER` (`x-prerender-revalidate`) — see
13
+ * `.nextjs-ref/packages/next/src/lib/constants.ts`.
14
+ *
15
+ * SECURITY: in Next.js this header is NOT a presence flag — it carries the
16
+ * secret `previewModeId`, and `checkIsOnDemandRevalidate`
17
+ * (`.nextjs-ref/packages/next/src/server/api-utils/index.ts`) only treats a
18
+ * request as on-demand revalidation when the value *equals* that secret. If we
19
+ * gated on presence alone, any external client could send
20
+ * `x-prerender-revalidate: <anything>` to force synchronous regeneration of any
21
+ * ISR page, bypassing the fresh/stale cache short-circuits — a
22
+ * cache-stampede/DoS vector. We therefore validate the value against
23
+ * {@link getRevalidateSecret} (a build-time secret shared across all Workers
24
+ * isolates) with a constant-time comparison, and only the matching value (sent
25
+ * by our own `res.revalidate()`) is honored.
26
+ */
27
+ const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
28
+ /**
29
+ * Companion header to {@link PRERENDER_REVALIDATE_HEADER}. When set,
30
+ * `res.revalidate(path, { unstable_onlyGenerated: true })` only revalidates the
31
+ * path if it was already generated, and a 404 response counts as a successful
32
+ * no-op. Mirrors Next.js's `PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER`
33
+ * (`x-prerender-revalidate-if-generated`) — see
34
+ * `.nextjs-ref/packages/next/src/lib/constants.ts`.
35
+ */
36
+ const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
37
+ /**
38
+ * Build-time secret that authenticates on-demand revalidation requests, the
39
+ * vinext analog of Next.js's prerender-manifest `previewModeId`.
40
+ *
41
+ * `res.revalidate()` loops back into the server via an internal `fetch()`. On
42
+ * Cloudflare Workers that loopback can land on a *different* isolate than the
43
+ * sender, so a per-process random secret would mismatch across isolates and
44
+ * false-reject legitimate revalidations (and, symmetrically, two isolates with
45
+ * independently-rolled secrets could never agree). The fix mirrors Next.js's
46
+ * `previewModeId`: the secret is generated once at BUILD time and baked
47
+ * (server-only — never into the client bundle) into every server bundle via the
48
+ * `__VINEXT_REVALIDATE_SECRET` Vite `define`, so it is byte-for-byte identical in
49
+ * every isolate. See `vinext build` CLI (`__VINEXT_SHARED_REVALIDATE_SECRET`) and
50
+ * the `vinext:compiler-define-server` plugin. The sender attaches it as the
51
+ * {@link PRERENDER_REVALIDATE_HEADER} value; the receiver authorizes a request
52
+ * only when the incoming value equals this secret (see
53
+ * {@link isOnDemandRevalidateRequest}).
54
+ *
55
+ * When the build-time define is absent — dev mode, and any path that doesn't
56
+ * run through `vinext build` — we fall back to a lazily-generated random secret.
57
+ * Those paths are single-process, so a module-scoped value is shared by sender
58
+ * and receiver there — no regression.
59
+ */
60
+ let devRevalidateSecret;
61
+ function getRevalidateSecret() {
62
+ const baked = process.env.__VINEXT_REVALIDATE_SECRET;
63
+ if (baked) return baked;
64
+ if (devRevalidateSecret === void 0) {
65
+ const bytes = new Uint8Array(32);
66
+ crypto.getRandomValues(bytes);
67
+ devRevalidateSecret = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
68
+ }
69
+ return devRevalidateSecret;
70
+ }
71
+ /**
72
+ * Constant-time string equality. Avoids leaking secret length / prefix via
73
+ * early-exit timing on the on-demand revalidation auth check. Returns false
74
+ * for length mismatch (the only safe option without revealing the secret
75
+ * length, and equality is impossible anyway).
76
+ */
77
+ function safeEqual(a, b) {
78
+ if (a.length !== b.length) return false;
79
+ let mismatch = 0;
80
+ for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
81
+ return mismatch === 0;
82
+ }
83
+ /**
84
+ * Authorize an incoming request as an on-demand revalidation trigger. Mirrors
85
+ * Next.js's `checkIsOnDemandRevalidate`: the {@link PRERENDER_REVALIDATE_HEADER}
86
+ * value must *equal* the process revalidate secret. Header presence alone is
87
+ * NOT sufficient — see the security note on {@link PRERENDER_REVALIDATE_HEADER}.
88
+ */
89
+ function isOnDemandRevalidateRequest(headerValue) {
90
+ if (typeof headerValue !== "string" || headerValue.length === 0) return false;
91
+ return safeEqual(headerValue, getRevalidateSecret());
92
+ }
93
+ /**
10
94
  * Get a cache entry with staleness information.
11
95
  *
12
96
  * Returns { value, isStale: false } for fresh entries,
@@ -195,4 +279,4 @@ function getRevalidateDuration(key) {
195
279
  return revalidateDurations.get(key);
196
280
  }
197
281
  //#endregion
198
- export { appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
282
+ export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
@@ -1,5 +1,5 @@
1
1
  import { splitPathnameForRouteMatch } from "../routing/utils.js";
2
- import { matchRoutePattern, matchRoutePatternPrefix } from "../routing/route-pattern.js";
2
+ import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
3
3
  import { compareAppElementsSlotIds } from "./app-elements-wire.js";
4
4
  import "./app-elements.js";
5
5
  import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
@@ -119,8 +119,10 @@ function findRouteManifestInterceptionForProof(routeManifest, proof) {
119
119
  const candidateInterceptions = routeManifest.segmentGraph.interceptionsBySlotId.get(proof.slotId) ?? [];
120
120
  for (const interception of candidateInterceptions) {
121
121
  if (!matchRoutePatternPrefix(sourceParts, interception.sourcePatternParts)) continue;
122
- if (matchRoutePattern(targetParts, interception.targetPatternParts) === null) continue;
123
- if (interception.targetRouteId !== null && targetRoute?.id !== interception.targetRouteId) continue;
122
+ const exactTargetParams = matchRoutePattern(targetParts, interception.targetPatternParts);
123
+ const allowsMiddlewareRewriteTarget = exactTargetParams === null && matchRoutePatternWithOptionalDynamicSegments(targetParts, interception.targetPatternParts);
124
+ if (exactTargetParams === null && !allowsMiddlewareRewriteTarget) continue;
125
+ if (!allowsMiddlewareRewriteTarget && interception.targetRouteId !== null && targetRoute?.id !== interception.targetRouteId) continue;
124
126
  return interception;
125
127
  }
126
128
  return null;
@@ -47,4 +47,4 @@ declare function createNavigationLifecycleTraceFields(options: {
47
47
  declare function createNavigationTrace(code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
48
48
  declare function prependNavigationTraceEntry(trace: NavigationTrace, code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
49
49
  //#endregion
50
- export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace, NavigationTraceCode, NavigationTraceFields, NavigationTraceReasonCode, NavigationTraceReasonCodes, NavigationTraceTransactionCode, NavigationTraceTransactionCodes, createNavigationLifecycleTraceFields, createNavigationTrace, prependNavigationTraceEntry };
50
+ export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace, NavigationTraceFields, NavigationTraceReasonCode, NavigationTraceReasonCodes, NavigationTraceTransactionCode, NavigationTraceTransactionCodes, createNavigationLifecycleTraceFields, createNavigationTrace, prependNavigationTraceEntry };
@@ -1,4 +1,5 @@
1
1
  import { PagesBodyParseError } from "./pages-media-type.js";
2
+ import { RevalidateOptions } from "./pages-revalidate.js";
2
3
 
3
4
  //#region src/server/pages-node-compat.d.ts
4
5
  type PagesRequestQuery = Record<string, string | string[]>;
@@ -33,6 +34,7 @@ type PagesReqResResponse = {
33
34
  send: (data: unknown) => void;
34
35
  redirect: (statusOrUrl: number | string, url?: string) => void;
35
36
  getHeaders: () => PagesReqResHeaders;
37
+ revalidate: (urlPath: string, opts?: RevalidateOptions) => Promise<void>;
36
38
  };
37
39
  type CreatePagesReqResOptions = {
38
40
  body: unknown;
@@ -2,6 +2,7 @@ import { parseCookies } from "../config/config-matchers.js";
2
2
  import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
3
3
  import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
4
4
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT } from "./pages-body-parser-config.js";
5
+ import { performOnDemandRevalidate } from "./pages-revalidate.js";
5
6
  import { decode } from "node:querystring";
6
7
  //#region src/server/pages-node-compat.ts
7
8
  const MAX_PAGES_API_BODY_SIZE = DEFAULT_PAGES_API_BODY_SIZE_LIMIT;
@@ -166,6 +167,9 @@ function createPagesReqRes(options) {
166
167
  const headers = { ...resHeaders };
167
168
  if (setCookieHeaders.length > 0) headers["set-cookie"] = setCookieHeaders;
168
169
  return headers;
170
+ },
171
+ async revalidate(urlPath, opts) {
172
+ await performOnDemandRevalidate(options.request.headers, urlPath, opts);
169
173
  }
170
174
  };
171
175
  return {
@@ -2,7 +2,7 @@ import { Route } from "../routing/pages-router.js";
2
2
  import { VinextNextData } from "../client/vinext-next-data.js";
3
3
  import { CachedPagesValue } from "../shims/cache.js";
4
4
  import { ISRCacheEntry } from "./isr-cache.js";
5
- import { PagesGsspResponse, PagesI18nRenderContext } from "./pages-page-response.js";
5
+ import { PagesGsspResponse, PagesI18nRenderContext, PagesNextDataExtras } from "./pages-page-response.js";
6
6
  import { ReactNode } from "react";
7
7
 
8
8
  //#region src/server/pages-page-data.d.ts
@@ -90,6 +90,7 @@ type RenderPagesIsrHtmlOptions = {
90
90
  routePattern: string;
91
91
  safeJsonStringify: (value: unknown) => string;
92
92
  vinext?: VinextNextData["__vinext"];
93
+ nextData?: PagesNextDataExtras;
93
94
  };
94
95
  type ResolvePagesPageDataOptions = {
95
96
  applyRequestContexts: () => void;
@@ -125,15 +126,16 @@ type ResolvePagesPageDataOptions = {
125
126
  * When true, this dispatch was triggered by an on-demand revalidation
126
127
  * request (e.g. `res.revalidate()` in a Pages Router API route, or an
127
128
  * equivalent webhook). Maps to `revalidateReason: "on-demand"` when
128
- * `getStaticProps` is invoked. Mirrors Next.js's
129
+ * `getStaticProps` is invoked, and bypasses the fresh/stale cache-hit
130
+ * short-circuits so the entry is regenerated synchronously. Mirrors Next.js's
129
131
  * `renderOpts.isOnDemandRevalidate` flag — see
130
132
  * `.nextjs-ref/packages/next/src/server/render.tsx`.
131
133
  *
132
- * Forward-looking plumbing: no caller currently sets this `res.revalidate()`
133
- * is not yet implemented in vinext. The `"on-demand"` branch in the
134
- * `revalidateReason` resolver is intentionally unreachable today; keeping the
135
- * typed contract here means wiring it up will be a one-line change once the
136
- * trigger lands.
134
+ * The page handler sets this only when the incoming request's
135
+ * `x-prerender-revalidate` header (`PRERENDER_REVALIDATE_HEADER`) *equals* the
136
+ * process revalidate secret that `res.revalidate()` attaches to its internal
137
+ * request (`isOnDemandRevalidateRequest`). It is never set on mere header
138
+ * presence — see the security note in `isr-cache.ts`.
137
139
  */
138
140
  isOnDemandRevalidate?: boolean;
139
141
  pageModule: PagesPageModule;
@@ -155,6 +157,7 @@ type ResolvePagesPageDataOptions = {
155
157
  }) => void;
156
158
  renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;
157
159
  vinext?: VinextNextData["__vinext"];
160
+ nextData?: PagesNextDataExtras;
158
161
  };
159
162
  type ResolvePagesPageDataRenderResult = {
160
163
  kind: "render";