vinext 0.2.0 → 0.2.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 (128) hide show
  1. package/dist/build/css-url-assets.d.ts +3 -1
  2. package/dist/build/css-url-assets.js +18 -1
  3. package/dist/build/inject-pregenerated-paths.d.ts +3 -0
  4. package/dist/build/inject-pregenerated-paths.js +4 -1
  5. package/dist/build/prerender-server-entry.d.ts +1 -0
  6. package/dist/build/prerender-server-entry.js +49 -0
  7. package/dist/build/prerender-server-pool.d.ts +44 -0
  8. package/dist/build/prerender-server-pool.js +194 -0
  9. package/dist/build/prerender.js +86 -22
  10. package/dist/build/run-prerender.js +2 -1
  11. package/dist/check.d.ts +4 -6
  12. package/dist/check.js +8 -9
  13. package/dist/cli.js +1 -1
  14. package/dist/client/vinext-next-data.d.ts +4 -1
  15. package/dist/config/config-matchers.js +33 -11
  16. package/dist/config/next-config.d.ts +36 -6
  17. package/dist/config/next-config.js +16 -2
  18. package/dist/entries/app-browser-entry.js +5 -1
  19. package/dist/entries/app-rsc-entry.d.ts +3 -2
  20. package/dist/entries/app-rsc-entry.js +4 -0
  21. package/dist/entries/pages-client-entry.js +14 -3
  22. package/dist/entries/pages-server-entry.js +18 -4
  23. package/dist/index.js +320 -25
  24. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +8 -1
  25. package/dist/packages/cloudflare/src/deploy.js +1 -5
  26. package/dist/packages/cloudflare/src/tpr.js +26 -5
  27. package/dist/plugins/fonts.js +1 -1
  28. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  29. package/dist/plugins/og-assets.js +2 -1
  30. package/dist/plugins/optimize-imports.js +1 -1
  31. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  32. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  33. package/dist/plugins/sass.d.ts +17 -1
  34. package/dist/plugins/sass.js +74 -1
  35. package/dist/plugins/styled-jsx.d.ts +16 -0
  36. package/dist/plugins/styled-jsx.js +149 -0
  37. package/dist/routing/app-route-graph.js +22 -16
  38. package/dist/routing/file-matcher.d.ts +8 -1
  39. package/dist/routing/file-matcher.js +15 -3
  40. package/dist/server/app-browser-entry.js +46 -14
  41. package/dist/server/app-browser-navigation-controller.js +1 -1
  42. package/dist/server/app-browser-server-action-client.js +5 -3
  43. package/dist/server/app-browser-state.d.ts +1 -0
  44. package/dist/server/app-browser-state.js +2 -2
  45. package/dist/server/app-browser-visible-commit.js +1 -1
  46. package/dist/server/app-page-boundary.js +2 -1
  47. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  48. package/dist/server/app-page-cache-finalizer.js +3 -3
  49. package/dist/server/app-page-cache.js +23 -6
  50. package/dist/server/app-page-dispatch.d.ts +1 -0
  51. package/dist/server/app-page-dispatch.js +25 -8
  52. package/dist/server/app-page-element-builder.js +1 -0
  53. package/dist/server/app-page-execution.js +2 -1
  54. package/dist/server/app-page-render-identity.d.ts +1 -0
  55. package/dist/server/app-page-render-identity.js +2 -1
  56. package/dist/server/app-page-render.d.ts +2 -0
  57. package/dist/server/app-page-render.js +67 -16
  58. package/dist/server/app-page-response.d.ts +7 -0
  59. package/dist/server/app-page-response.js +16 -4
  60. package/dist/server/app-page-stream.d.ts +2 -1
  61. package/dist/server/app-page-stream.js +1 -1
  62. package/dist/server/app-route-handler-execution.js +1 -1
  63. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  64. package/dist/server/app-route-tree-prefetch.js +187 -0
  65. package/dist/server/app-router-entry.js +1 -1
  66. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  67. package/dist/server/app-rsc-cache-busting.js +9 -4
  68. package/dist/server/app-rsc-handler.d.ts +6 -0
  69. package/dist/server/app-rsc-handler.js +89 -11
  70. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  71. package/dist/server/app-rsc-render-mode.js +4 -1
  72. package/dist/server/app-rsc-request-normalization.d.ts +3 -4
  73. package/dist/server/app-rsc-request-normalization.js +4 -5
  74. package/dist/server/app-server-action-execution.js +4 -3
  75. package/dist/server/app-ssr-entry.js +3 -3
  76. package/dist/server/cache-control.d.ts +3 -1
  77. package/dist/server/cache-control.js +13 -1
  78. package/dist/server/dev-server.d.ts +11 -1
  79. package/dist/server/dev-server.js +78 -20
  80. package/dist/server/headers.d.ts +6 -2
  81. package/dist/server/headers.js +11 -5
  82. package/dist/server/isr-cache.d.ts +2 -1
  83. package/dist/server/isr-cache.js +7 -3
  84. package/dist/server/pages-data-route.d.ts +4 -2
  85. package/dist/server/pages-data-route.js +18 -4
  86. package/dist/server/pages-dev-module-url.d.ts +2 -1
  87. package/dist/server/pages-dev-module-url.js +6 -3
  88. package/dist/server/pages-node-compat.d.ts +12 -1
  89. package/dist/server/pages-node-compat.js +50 -1
  90. package/dist/server/pages-page-data.d.ts +9 -0
  91. package/dist/server/pages-page-data.js +17 -9
  92. package/dist/server/pages-page-handler.js +19 -7
  93. package/dist/server/pages-page-response.d.ts +1 -0
  94. package/dist/server/pages-page-response.js +3 -2
  95. package/dist/server/pages-request-pipeline.d.ts +8 -6
  96. package/dist/server/pages-request-pipeline.js +30 -6
  97. package/dist/server/pages-router-entry.js +1 -1
  98. package/dist/server/prod-server.d.ts +4 -2
  99. package/dist/server/prod-server.js +38 -22
  100. package/dist/server/request-pipeline.js +1 -1
  101. package/dist/shims/cache-handler.js +8 -1
  102. package/dist/shims/cache.js +3 -0
  103. package/dist/shims/fetch-cache.d.ts +3 -1
  104. package/dist/shims/fetch-cache.js +77 -52
  105. package/dist/shims/form.d.ts +1 -1
  106. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  107. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  108. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
  109. package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
  110. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
  111. package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
  112. package/dist/shims/internal/pages-data-target.d.ts +8 -3
  113. package/dist/shims/internal/pages-data-target.js +9 -4
  114. package/dist/shims/link.d.ts +1 -1
  115. package/dist/shims/link.js +179 -42
  116. package/dist/shims/navigation.d.ts +15 -3
  117. package/dist/shims/navigation.js +202 -39
  118. package/dist/shims/navigation.react-server.js +1 -1
  119. package/dist/shims/request-context.js +18 -0
  120. package/dist/shims/router.js +31 -15
  121. package/dist/shims/unified-request-context.js +1 -0
  122. package/dist/typegen.js +1 -1
  123. package/dist/utils/middleware-request-headers.js +1 -1
  124. package/dist/utils/protocol-headers.d.ts +7 -1
  125. package/dist/utils/protocol-headers.js +7 -1
  126. package/dist/utils/vite-version.d.ts +2 -0
  127. package/dist/utils/vite-version.js +11 -1
  128. package/package.json +2 -2
@@ -264,6 +264,12 @@ async function resolvePagesPageData(options) {
264
264
  }
265
265
  let pageProps = {};
266
266
  let gsspRes = null;
267
+ const previewData = options.isOnDemandRevalidate ? false : options.previewData ?? false;
268
+ const previewContext = previewData === false ? {} : {
269
+ draftMode: true,
270
+ preview: true,
271
+ previewData
272
+ };
267
273
  let sharedReqRes = null;
268
274
  function getSharedReqRes() {
269
275
  sharedReqRes ??= options.createGsspReqRes();
@@ -317,7 +323,8 @@ async function resolvePagesPageData(options) {
317
323
  resolvedUrl: options.resolvedUrl ?? options.routeUrl,
318
324
  locale: options.i18n.locale,
319
325
  locales: options.i18n.locales,
320
- defaultLocale: options.i18n.defaultLocale
326
+ defaultLocale: options.i18n.defaultLocale,
327
+ ...previewContext
321
328
  });
322
329
  if (isResponseSent(res)) return {
323
330
  kind: "response",
@@ -338,7 +345,7 @@ async function resolvePagesPageData(options) {
338
345
  response: buildPagesRedirectResponse(result.redirect, options, renderProps)
339
346
  };
340
347
  if (result?.notFound) return buildPagesNotFoundResult(options);
341
- if (result?.props !== void 0) isSerializableProps(options.routePattern, "getServerSideProps", pageProps);
348
+ if (result?.props !== void 0 && options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getServerSideProps", pageProps);
342
349
  gsspRes = res;
343
350
  }
344
351
  let isrRevalidateSeconds = null;
@@ -347,7 +354,7 @@ async function resolvePagesPageData(options) {
347
354
  const cacheKey = options.isrCacheKey("pages", pathname);
348
355
  const cached = await options.isrGet(cacheKey);
349
356
  const cachedValue = cached?.value.value;
350
- if (!options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq) {
357
+ if (!options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
351
358
  const hitResponse = buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status);
352
359
  const hitBotResult = applyBotETagAndCheck(hitResponse, cachedValue.html, options);
353
360
  if (hitBotResult) return hitBotResult;
@@ -356,7 +363,7 @@ async function resolvePagesPageData(options) {
356
363
  response: hitResponse
357
364
  };
358
365
  }
359
- if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && cached.isStale && !options.scriptNonce && !options.isDataReq) {
366
+ if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
360
367
  options.triggerBackgroundRegeneration(cacheKey, async function() {
361
368
  return options.runInFreshUnifiedContext(async () => {
362
369
  options.applyRequestContexts();
@@ -413,7 +420,7 @@ async function resolvePagesPageData(options) {
413
420
  response: staleResponse
414
421
  };
415
422
  }
416
- const generatedPageData = !options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest && isUnknownRecord(cachedValue.pageData) ? cachedValue.pageData : null;
423
+ const generatedPageData = !options.isOnDemandRevalidate && previewData === false && cached?.isStale === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest && isUnknownRecord(cachedValue.pageData) ? cachedValue.pageData : null;
417
424
  if (!generatedPageData) {
418
425
  const shortCircuit = await loadForegroundAppInitialRenderProps();
419
426
  if (shortCircuit) return shortCircuit;
@@ -423,6 +430,7 @@ async function resolvePagesPageData(options) {
423
430
  locale: options.i18n.locale,
424
431
  locales: options.i18n.locales,
425
432
  defaultLocale: options.i18n.defaultLocale,
433
+ ...previewContext,
426
434
  revalidateReason: options.isOnDemandRevalidate ? "on-demand" : options.isBuildTimePrerendering ? "build" : "stale"
427
435
  });
428
436
  if (generatedPageData) {
@@ -444,10 +452,10 @@ async function resolvePagesPageData(options) {
444
452
  response: buildPagesRedirectResponse(result.redirect, options, renderProps)
445
453
  };
446
454
  if (result?.notFound) return buildPagesNotFoundResult(options);
447
- if (result?.props !== void 0) isSerializableProps(options.routePattern, "getStaticProps", pageProps);
448
- if (typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
449
- else if (cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest) isrRevalidateSeconds = cached?.value.cacheControl?.revalidate ?? 31536e3;
450
- if (shouldPersistFallbackData) {
455
+ if (result?.props !== void 0 && options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getStaticProps", pageProps);
456
+ if (previewData === false && typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
457
+ else if (previewData === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest) isrRevalidateSeconds = cached?.value.cacheControl?.revalidate ?? 31536e3;
458
+ if (shouldPersistFallbackData && previewData === false) {
451
459
  const revalidateSeconds = isrRevalidateSeconds ?? 31536e3;
452
460
  await options.isrSet(cacheKey, {
453
461
  kind: "PAGES",
@@ -3,8 +3,8 @@ import { patternToNextFormat } from "../routing/route-validation.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
5
5
  import { reportRequestError } from "./instrumentation.js";
6
- import { NEVER_CACHE_CONTROL } from "./cache-control.js";
7
- import "./isr-decision.js";
6
+ import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, applyCdnResponseHeaders, shouldUseNextDeployCacheControl } from "./cache-control.js";
7
+ import { buildMissIsrCacheControl } from "./isr-decision.js";
8
8
  import { PRERENDER_REVALIDATE_HEADER, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, triggerBackgroundRegeneration } from "./isr-cache.js";
9
9
  import { ensureFetchPatch } from "../shims/fetch-cache.js";
10
10
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
@@ -18,8 +18,8 @@ import { renderPagesPageResponse } from "./pages-page-response.js";
18
18
  import { hasPagesGetInitialProps } from "./pages-get-initial-props.js";
19
19
  import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
20
20
  import { resolvePagesPageData } from "./pages-page-data.js";
21
- import { createPagesReqRes } from "./pages-node-compat.js";
22
21
  import { collectAssetTags, resolveClientModuleUrl } from "./pages-asset-tags.js";
22
+ import { createPagesReqRes, getPagesPreviewData } from "./pages-node-compat.js";
23
23
  //#region src/server/pages-page-handler.ts
24
24
  function shouldEmitPagesClientTraceMetadata(pageModule, appComponent) {
25
25
  if (typeof pageModule.getServerSideProps === "function") return true;
@@ -61,7 +61,7 @@ function createPagesPageHandler(opts) {
61
61
  const originalRequestPathAndSearch = originalRequestUrl.pathname + originalRequestUrl.search;
62
62
  let dataRequestPathname = null;
63
63
  let dataRequestSearch = "";
64
- const initialDataNorm = normalizePagesDataRequest(request, buildId);
64
+ const initialDataNorm = normalizePagesDataRequest(request, buildId, vinextConfig.basePath, hasMiddleware && vinextConfig.trailingSlash);
65
65
  if (!isDataReq) {
66
66
  if (initialDataNorm.notFoundResponse) return initialDataNorm.notFoundResponse;
67
67
  if (initialDataNorm.isDataReq) {
@@ -133,6 +133,7 @@ function createPagesPageHandler(opts) {
133
133
  const renderStatusCode = renderStatusCodeOverride ?? (routePattern === "/404" ? 404 : void 0);
134
134
  const query = mergeRouteParamsIntoQuery(parseQueryString(routeUrl), params);
135
135
  const pageModule = route.module;
136
+ const isStaticPropsRoute = typeof pageModule.getStaticProps === "function";
136
137
  const pagesNextData = buildPagesReadinessNextData({
137
138
  pageModule,
138
139
  appComponent: AppComponent,
@@ -177,7 +178,8 @@ function createPagesPageHandler(opts) {
177
178
  ...pagesNextData.__vinext,
178
179
  pageModuleUrl,
179
180
  appModuleUrl,
180
- hasMiddleware
181
+ hasMiddleware,
182
+ routeUrl
181
183
  }
182
184
  };
183
185
  const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareHeaders);
@@ -194,6 +196,7 @@ function createPagesPageHandler(opts) {
194
196
  request,
195
197
  url: originalRequestPathAndSearch
196
198
  });
199
+ const isOnDemandRevalidate = isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER));
197
200
  const pageDataResult = await resolvePagesPageData({
198
201
  isDataReq,
199
202
  err: err instanceof Error ? err : void 0,
@@ -217,7 +220,9 @@ function createPagesPageHandler(opts) {
217
220
  isrSet,
218
221
  expireSeconds: vinextConfig.expireTime,
219
222
  isBuildTimePrerendering: typeof process !== "undefined" && process.env && process.env.VINEXT_PRERENDER === "1",
220
- isOnDemandRevalidate: isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER)),
223
+ validatePropsSerialization: process.env.NODE_ENV !== "production" || process.env.VINEXT_PRERENDER === "1",
224
+ isOnDemandRevalidate,
225
+ previewData: getPagesPreviewData(request, { isOnDemandRevalidate }),
221
226
  pageModule,
222
227
  AppComponent,
223
228
  params,
@@ -300,6 +305,12 @@ function createPagesPageHandler(opts) {
300
305
  break;
301
306
  }
302
307
  if (!hasUserCacheControl) init.headers["Cache-Control"] = NEVER_CACHE_CONTROL;
308
+ } else if (isStaticPropsRoute) {
309
+ if (isrRevalidateSeconds) {
310
+ const headers = new Headers(init.headers);
311
+ applyCdnResponseHeaders(headers, { cacheControl: buildMissIsrCacheControl(isrRevalidateSeconds, vinextConfig.expireTime) });
312
+ for (const [key, value] of headers) init.headers[key] = value;
313
+ } else if (shouldUseNextDeployCacheControl()) init.headers["Cache-Control"] = BROWSER_REVALIDATE_CACHE_CONTROL;
303
314
  }
304
315
  if (routePattern !== "/_error" && routePattern !== "/500") {
305
316
  const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
@@ -308,8 +319,8 @@ function createPagesPageHandler(opts) {
308
319
  return buildNextDataPropsJsonResponse(renderProps, safeJsonStringify, init);
309
320
  }
310
321
  const pageModuleIds = [];
311
- if (route.filePath) pageModuleIds.push(route.filePath);
312
322
  if (appAssetPath) pageModuleIds.push(appAssetPath);
323
+ if (route.filePath) pageModuleIds.push(route.filePath);
313
324
  return await renderPagesPageResponse({
314
325
  assetTags: collectAssetTags({
315
326
  manifest,
@@ -346,6 +357,7 @@ function createPagesPageHandler(opts) {
346
357
  isrCacheKey: pageIsrCacheKey,
347
358
  expireSeconds: vinextConfig.expireTime,
348
359
  isrRevalidateSeconds,
360
+ isStaticPropsRoute,
349
361
  isrSet,
350
362
  i18n: buildI18nRenderContext(i18nConfig, locale, currentDefaultLocale, domainLocales),
351
363
  isFallback: isFallbackRender,
@@ -91,6 +91,7 @@ type RenderPagesPageResponseOptions = {
91
91
  isrCacheKey: (router: string, pathname: string) => string;
92
92
  expireSeconds?: number;
93
93
  isrRevalidateSeconds: number | null;
94
+ isStaticPropsRoute?: boolean;
94
95
  isrSet: (key: string, data: CachedPagesValue, revalidateSeconds: number, tags?: string[], expireSeconds?: number) => Promise<void>;
95
96
  i18n: PagesI18nRenderContext;
96
97
  /**
@@ -1,7 +1,7 @@
1
1
  import { getRequestExecutionContext } from "../shims/request-context.js";
2
2
  import { reportRequestError } from "./instrumentation.js";
3
3
  import { setCacheStateHeaders } from "./cache-headers.js";
4
- import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
4
+ import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, shouldUseNextDeployCacheControl } from "./cache-control.js";
5
5
  import { buildMissIsrCacheControl } from "./isr-decision.js";
6
6
  import { fnv1a52 } from "../utils/hash.js";
7
7
  import { encodeCacheTag } from "../utils/encode-cache-tag.js";
@@ -264,7 +264,8 @@ async function renderPagesPageResponse(options) {
264
264
  tags: [encodeCacheTag(`_N_T_${stem || "/"}`)]
265
265
  });
266
266
  setCacheStateHeaders(responseHeaders, "MISS");
267
- } else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
267
+ } else if (options.isStaticPropsRoute && shouldUseNextDeployCacheControl()) responseHeaders.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
268
+ else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
268
269
  if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
269
270
  if (options.userAgent && isPagesStreamingBot(options.userAgent)) {
270
271
  const fullHtml = await readStreamAsText(compositeStream);
@@ -8,6 +8,13 @@ type PagesRenderOptions = {
8
8
  originalUrl?: string;
9
9
  };
10
10
  type FilesystemRoutePhase = "direct" | "beforeFiles" | "afterFiles" | "fallback";
11
+ type PageRouteMatch = {
12
+ route: {
13
+ isDynamic: boolean;
14
+ pattern?: string;
15
+ dataKind?: "static" | "server" | "none";
16
+ };
17
+ };
11
18
  declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>): Promise<Response | false>;
12
19
  type MiddlewareResult = {
13
20
  continue: boolean;
@@ -37,12 +44,7 @@ type PagesPipelineDeps = {
37
44
  hasMiddleware: boolean;
38
45
  ctx?: unknown;
39
46
  rawSearch?: string;
40
- matchPageRoute?: ((pathname: string, request: Request) => {
41
- route: {
42
- isDynamic: boolean;
43
- pattern?: string;
44
- };
45
- } | null) | null;
47
+ matchPageRoute?: ((pathname: string, request: Request) => PageRouteMatch | null) | null;
46
48
  runMiddleware?: ((request: Request, ctx: unknown, opts: {
47
49
  isDataRequest: boolean;
48
50
  }) => Promise<MiddlewareResult>) | null;
@@ -4,6 +4,7 @@ import { applyMiddlewareRequestHeaders, isExternalUrl, matchRedirect, matchRewri
4
4
  import { applyConfigHeadersToHeaderRecord, cloneRequestWithUrl, normalizeTrailingSlash } from "./request-pipeline.js";
5
5
  import { mergeRewriteQuery } from "../utils/query.js";
6
6
  import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pages-i18n.js";
7
+ import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
7
8
  import { mergeHeaders } from "./worker-utils.js";
8
9
  //#region src/server/pages-request-pipeline.ts
9
10
  async function fetchWorkerFilesystemRoute(request, requestPathname, phase, fetchAsset) {
@@ -149,6 +150,15 @@ async function runPagesRequest(request, deps) {
149
150
  if (resolvedLocale && i18nConfig.locales.includes(resolvedLocale)) return matchedPathname === "/" ? `/${resolvedLocale}` : `/${resolvedLocale}${matchedPathname}`;
150
151
  return matchResolvedPathname(matchedPathname);
151
152
  };
153
+ const buildMiddlewarePrefetchSkipResult = (match) => {
154
+ if (!match) return null;
155
+ if (match.route.dataKind !== "server" || !isDataRequest || !deps.hasMiddleware || request.headers.get("x-middleware-prefetch") !== "1") return null;
156
+ return {
157
+ type: "response",
158
+ response: mergeHeaders(buildMiddlewarePrefetchSkipResponse(matchedPathnameForRoute(match.route.pattern)), middlewareHeaders, void 0),
159
+ defaultContentType: "application/json"
160
+ };
161
+ };
152
162
  if (configHeaders.length) applyConfigHeadersToHeaderRecord(middlewareHeaders, {
153
163
  configHeaders,
154
164
  pathname: matchPathname,
@@ -178,14 +188,16 @@ async function runPagesRequest(request, deps) {
178
188
  const beforeFilesResult = await serveFilesystemRoute(resolvedPathname, "beforeFiles");
179
189
  if (beforeFilesResult) return beforeFilesResult;
180
190
  }
181
- if (basePath && !hadBasePath && !configRewriteFired) return {
191
+ const isOutsideBasePathUnclaimed = () => basePath && !hadBasePath && !configRewriteFired;
192
+ const outOfBasePathNotFound = () => ({
182
193
  type: "response",
183
194
  response: new Response("This page could not be found", {
184
195
  status: 404,
185
196
  headers: { "Content-Type": "text/html; charset=utf-8" }
186
197
  })
187
- };
198
+ });
188
199
  const handleResolvedApiRoute = async () => {
200
+ if (isOutsideBasePathUnclaimed()) return null;
189
201
  const apiLookupUrl = stripI18nLocaleForApiRoute(resolvedUrl, i18nConfig);
190
202
  const apiLookupPathname = apiLookupUrl.split("?")[0];
191
203
  if (!apiLookupPathname.startsWith("/api/") && apiLookupPathname !== "/api") return null;
@@ -212,7 +224,7 @@ async function runPagesRequest(request, deps) {
212
224
  };
213
225
  const apiResult = await handleResolvedApiRoute();
214
226
  if (apiResult) return apiResult;
215
- let pageMatch = deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
227
+ let pageMatch = !isOutsideBasePathUnclaimed() && deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null;
216
228
  let resolvedPathnameChanged = false;
217
229
  if (!pageMatch || pageMatch.route.isDynamic) for (const rewrite of configRewrites.afterFiles ?? []) {
218
230
  const rewritten = matchRewrite(matchResolvedPathname(resolvedPathname), [rewrite], rewriteRequestContext(), basePathState);
@@ -223,6 +235,7 @@ async function runPagesRequest(request, deps) {
223
235
  };
224
236
  resolvedUrl = mergeRewriteQuery(resolvedUrl, rewritten);
225
237
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
238
+ configRewriteFired = true;
226
239
  resolvedPathnameChanged = true;
227
240
  const afterFilesFilesystemResult = await serveFilesystemRoute(resolvedPathname, "afterFiles");
228
241
  if (afterFilesFilesystemResult) return afterFilesFilesystemResult;
@@ -239,7 +252,7 @@ async function runPagesRequest(request, deps) {
239
252
  refreshDataRewriteHeader();
240
253
  if (typeof deps.renderPage === "function") {
241
254
  let renderPageMatch = pageMatch;
242
- if ((isDataReq || isDataRequest) && !renderPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
255
+ if ((isOutsideBasePathUnclaimed() || isDataReq || isDataRequest) && !renderPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
243
256
  const fallbackRewrite = matchRewrite(matchResolvedPathname(resolvedPathname), [rewrite], rewriteRequestContext(), basePathState);
244
257
  if (!fallbackRewrite) continue;
245
258
  if (isExternalUrl(fallbackRewrite)) return {
@@ -248,6 +261,7 @@ async function runPagesRequest(request, deps) {
248
261
  };
249
262
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
250
263
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
264
+ configRewriteFired = true;
251
265
  const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
252
266
  if (fallbackFilesystemResult) return fallbackFilesystemResult;
253
267
  const fallbackApiResult = await handleResolvedApiRoute();
@@ -256,6 +270,9 @@ async function runPagesRequest(request, deps) {
256
270
  refreshDataRewriteHeader();
257
271
  if (renderPageMatch) break;
258
272
  }
273
+ const prefetchSkipResult = buildMiddlewarePrefetchSkipResult(renderPageMatch);
274
+ if (prefetchSkipResult) return prefetchSkipResult;
275
+ if (isOutsideBasePathUnclaimed()) return outOfBasePathNotFound();
259
276
  const shouldDeferErrorPageOnMiss = !isDataReq && !isDataRequest && !!deps.matchPageRoute && !renderPageMatch;
260
277
  const initialRenderOptions = shouldDeferErrorPageOnMiss ? { renderErrorPageOnMiss: false } : isDataReq ? { isDataReq: true } : void 0;
261
278
  const stagedHeaders = new Headers();
@@ -272,6 +289,7 @@ async function runPagesRequest(request, deps) {
272
289
  };
273
290
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
274
291
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
292
+ configRewriteFired = true;
275
293
  const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
276
294
  if (fallbackFilesystemResult) return fallbackFilesystemResult;
277
295
  const fallbackApiResult = await handleResolvedApiRoute();
@@ -305,7 +323,8 @@ async function runPagesRequest(request, deps) {
305
323
  defaultContentType: "text/html"
306
324
  };
307
325
  }
308
- if (!(resolvedPathnameChanged ? deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null : pageMatch) && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
326
+ let devPageMatch = isOutsideBasePathUnclaimed() ? null : resolvedPathnameChanged ? deps.matchPageRoute ? deps.matchPageRoute(resolvedPathname, request) : null : pageMatch;
327
+ if (!devPageMatch && configRewrites.fallback?.length) for (const rewrite of configRewrites.fallback) {
309
328
  const fallbackRewrite = matchRewrite(matchResolvedPathname(resolvedPathname), [rewrite], rewriteRequestContext(), basePathState);
310
329
  if (!fallbackRewrite) continue;
311
330
  if (isExternalUrl(fallbackRewrite)) return {
@@ -314,12 +333,17 @@ async function runPagesRequest(request, deps) {
314
333
  };
315
334
  resolvedUrl = mergeRewriteQuery(resolvedUrl, fallbackRewrite);
316
335
  resolvedPathname = pathnameForResolvedUrl(resolvedUrl);
336
+ configRewriteFired = true;
317
337
  const fallbackFilesystemResult = await serveFilesystemRoute(resolvedPathname, "fallback");
318
338
  if (fallbackFilesystemResult) return fallbackFilesystemResult;
319
339
  const fallbackApiResult = await handleResolvedApiRoute();
320
340
  if (fallbackApiResult) return fallbackApiResult;
321
- if (deps.matchPageRoute?.(resolvedPathname, request)) break;
341
+ devPageMatch = deps.matchPageRoute?.(resolvedPathname, request) ?? null;
342
+ if (devPageMatch) break;
322
343
  }
344
+ const prefetchSkipResult = buildMiddlewarePrefetchSkipResult(devPageMatch);
345
+ if (prefetchSkipResult) return prefetchSkipResult;
346
+ if (isOutsideBasePathUnclaimed()) return outOfBasePathNotFound();
323
347
  refreshDataRewriteHeader();
324
348
  return {
325
349
  type: "render",
@@ -2,8 +2,8 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
2
2
  import { notFoundStaticAssetResponse } from "./http-error-responses.js";
3
3
  import { isOpenRedirectShaped } from "./open-redirect.js";
4
4
  import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders } from "./request-pipeline.js";
5
- import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
6
5
  import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js";
6
+ import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
7
7
  import { finalizeMissingStaticAssetResponse } from "./worker-utils.js";
8
8
  import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
9
9
  import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
@@ -41,6 +41,7 @@ import { IncomingMessage, ServerResponse } from "node:http";
41
41
  * Exported for direct unit testing of the URL choice.
42
42
  */
43
43
  declare function resolveServerEntryImportUrl(entryPath: string): string;
44
+ declare function rememberCurrentServerEntryImportMtime(entryPath: string): void;
44
45
  declare function importServerEntryModule(entryPath: string): Promise<any>;
45
46
  /** Convert a Node.js IncomingMessage into a ReadableStream for Web Request body. */
46
47
  declare function readNodeStream(req: IncomingMessage): ReadableStream<Uint8Array>;
@@ -54,7 +55,8 @@ type ProdServerOptions = {
54
55
  * Omitted for normal `vinext start` so the existing production-server output
55
56
  * remains stable.
56
57
  */
57
- purpose?: "prerender";
58
+ purpose?: "prerender"; /** Suppress the startup log for internal child-process servers. */
59
+ silent?: boolean;
58
60
  };
59
61
  /** Content types that benefit from compression. */
60
62
  declare const COMPRESSIBLE_TYPES: Set<string>;
@@ -145,4 +147,4 @@ declare function resolveAppRouterPrerenderSeeder(entryModule: unknown): AppRoute
145
147
  */
146
148
  declare function resolveAppRouterAssetPath(pathname: string, assetPathPrefix: string, assetPrefix: string): string | null;
147
149
  //#endregion
148
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
150
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, rememberCurrentServerEntryImportMtime, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
@@ -1,24 +1,24 @@
1
1
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
2
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
- import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../utils/protocol-headers.js";
3
+ import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
4
4
  import { VINEXT_STATIC_FILE_HEADER } from "./headers.js";
5
5
  import { normalizePath } from "./normalize-path.js";
6
6
  import { notFoundResponse } from "./http-error-responses.js";
7
7
  import { isOpenRedirectShaped } from "./open-redirect.js";
8
8
  import { filterInternalHeaders } from "./request-pipeline.js";
9
9
  import { isUnknownRecord } from "../utils/record.js";
10
- import { buildNextDataNotFoundResponse, isNextDataPathname, parseNextDataPathname } from "./pages-data-route.js";
10
+ import { buildNextDataNotFoundResponse, isNextDataPathname, normalizeNextDataPagePathname, parseNextDataPathname } from "./pages-data-route.js";
11
+ import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix } from "../utils/asset-prefix.js";
12
+ import { setPagesClientAssets } from "./pages-client-assets.js";
11
13
  import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
12
14
  import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
13
15
  import { installSocketErrorBackstop } from "./socket-error-backstop.js";
14
- import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix } from "../utils/asset-prefix.js";
15
16
  import { CONTENT_TYPES, StaticFileCache, etagFromFilenameHash } from "./static-file-cache.js";
16
17
  import { collectInlineCssManifest } from "../build/inline-css.js";
17
18
  import { mergeHeaders } from "./worker-utils.js";
18
19
  import { runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
19
20
  import { computeClientRuntimeMetadata } from "../utils/client-runtime-metadata.js";
20
21
  import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
21
- import { setPagesClientAssets } from "./pages-client-assets.js";
22
22
  import { readPrerenderSecret } from "../build/server-manifest.js";
23
23
  import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
24
24
  import { negotiateEncoding, parseAcceptedEncodings, selectContentEncoding } from "./accept-encoding.js";
@@ -56,6 +56,18 @@ import { createServer } from "node:http";
56
56
  * through a cache-busted URL instead.
57
57
  */
58
58
  const bareServerEntryMtimes = /* @__PURE__ */ new Map();
59
+ function resolveCanonicalServerEntry(entryPath) {
60
+ let canonicalEntryPath;
61
+ try {
62
+ canonicalEntryPath = fs.realpathSync.native(entryPath);
63
+ } catch {
64
+ canonicalEntryPath = entryPath;
65
+ }
66
+ return {
67
+ href: pathToFileURL(canonicalEntryPath).href,
68
+ mtime: fs.statSync(canonicalEntryPath).mtimeMs
69
+ };
70
+ }
59
71
  /**
60
72
  * Import a built server entry module (App Router RSC entry or Pages Router
61
73
  * server entry) by absolute file path.
@@ -93,14 +105,7 @@ const bareServerEntryMtimes = /* @__PURE__ */ new Map();
93
105
  * Exported for direct unit testing of the URL choice.
94
106
  */
95
107
  function resolveServerEntryImportUrl(entryPath) {
96
- let canonicalEntryPath;
97
- try {
98
- canonicalEntryPath = fs.realpathSync.native(entryPath);
99
- } catch {
100
- canonicalEntryPath = entryPath;
101
- }
102
- const href = pathToFileURL(canonicalEntryPath).href;
103
- const mtime = fs.statSync(canonicalEntryPath).mtimeMs;
108
+ const { href, mtime } = resolveCanonicalServerEntry(entryPath);
104
109
  const bareMtime = bareServerEntryMtimes.get(href);
105
110
  if (bareMtime === void 0 || bareMtime === mtime) {
106
111
  bareServerEntryMtimes.set(href, mtime);
@@ -108,6 +113,10 @@ function resolveServerEntryImportUrl(entryPath) {
108
113
  }
109
114
  return `${href}?t=${mtime}`;
110
115
  }
116
+ function rememberCurrentServerEntryImportMtime(entryPath) {
117
+ const { href, mtime } = resolveCanonicalServerEntry(entryPath);
118
+ bareServerEntryMtimes.set(href, mtime);
119
+ }
111
120
  async function importServerEntryModule(entryPath) {
112
121
  return import(resolveServerEntryImportUrl(entryPath));
113
122
  }
@@ -557,9 +566,11 @@ function nodeToWebRequest(req, urlOverride, prerenderSecret) {
557
566
  const url = new URL(urlOverride ?? req.url ?? "/", origin);
558
567
  const rawHeaders = nodeHeadersToWebHeaders(req.headers);
559
568
  const prerenderRouteParamsPayload = readTrustedPrerenderRouteParamsFromHeaders(rawHeaders, prerenderSecret);
569
+ const isTrustedSpeculativePrerender = process.env.VINEXT_PRERENDER === "1" && prerenderSecret !== void 0 && rawHeaders.get("x-vinext-prerender-secret") === prerenderSecret && rawHeaders.get("x-vinext-prerender-speculative") === "1";
560
570
  const headers = filterInternalHeaders(rawHeaders);
561
571
  const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParamsPayload);
562
572
  if (prerenderRouteParamsHeader !== null) headers.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
573
+ if (isTrustedSpeculativePrerender) headers.set(VINEXT_PRERENDER_SPECULATIVE_HEADER, "1");
563
574
  const method = req.method ?? "GET";
564
575
  const hasBody = method !== "GET" && method !== "HEAD";
565
576
  const init = {
@@ -622,7 +633,7 @@ async function sendWebResponse(webResponse, req, res, compress) {
622
633
  */
623
634
  async function startProdServer(options = {}) {
624
635
  installSocketErrorBackstop();
625
- const { port = process.env.PORT ? parseInt(process.env.PORT) : 3e3, host = "0.0.0.0", outDir = path.resolve("dist"), noCompression = false, purpose } = options;
636
+ const { port = process.env.PORT ? parseInt(process.env.PORT) : 3e3, host = "0.0.0.0", outDir = path.resolve("dist"), noCompression = false, purpose, silent = false } = options;
626
637
  const compress = !noCompression;
627
638
  const resolvedOutDir = path.resolve(outDir);
628
639
  const clientDir = path.join(resolvedOutDir, "client");
@@ -640,7 +651,8 @@ async function startProdServer(options = {}) {
640
651
  clientDir,
641
652
  rscEntryPath,
642
653
  compress,
643
- purpose
654
+ purpose,
655
+ silent
644
656
  });
645
657
  return startPagesRouterServer({
646
658
  port,
@@ -648,7 +660,8 @@ async function startProdServer(options = {}) {
648
660
  clientDir,
649
661
  serverEntryPath,
650
662
  compress,
651
- purpose
663
+ purpose,
664
+ silent
652
665
  });
653
666
  }
654
667
  function createNodeExecutionContext() {
@@ -776,7 +789,7 @@ function installPagesClientAssets(options) {
776
789
  * 4. Stream the Web Response back (with optional compression)
777
790
  */
778
791
  async function startAppRouterServer(options) {
779
- const { port, host, clientDir, rscEntryPath, compress, purpose } = options;
792
+ const { port, host, clientDir, rscEntryPath, compress, purpose, silent } = options;
780
793
  const prerenderSecret = readPrerenderSecret(path.dirname(rscEntryPath));
781
794
  const rscModule = await importServerEntryModule(rscEntryPath);
782
795
  const rscHandler = resolveAppRouterHandler(rscModule.default);
@@ -899,7 +912,8 @@ async function startAppRouterServer(options) {
899
912
  await new Promise((resolve) => {
900
913
  server.listen(port, host, () => {
901
914
  const addr = server.address();
902
- logProdServerStarted(host, typeof addr === "object" && addr ? addr.port : port, purpose);
915
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
916
+ if (!silent) logProdServerStarted(host, actualPort, purpose);
903
917
  resolve();
904
918
  });
905
919
  });
@@ -930,7 +944,7 @@ function readPagesServerEntryPageRoutes(value) {
930
944
  * - vinextConfig — embedded next.config.js settings
931
945
  */
932
946
  async function startPagesRouterServer(options) {
933
- const { port, host, clientDir, serverEntryPath, compress, purpose } = options;
947
+ const { port, host, clientDir, serverEntryPath, compress, purpose, silent } = options;
934
948
  const serverEntry = await importServerEntryModule(serverEntryPath);
935
949
  const { renderPage, handleApiRoute: handleApi, runMiddleware, vinextConfig, buildId: pagesBuildId } = serverEntry;
936
950
  const matchPageRoute = typeof serverEntry.matchPageRoute === "function" ? serverEntry.matchPageRoute : void 0;
@@ -1062,8 +1076,9 @@ async function startPagesRouterServer(options) {
1062
1076
  }
1063
1077
  isDataReq = true;
1064
1078
  const qs = url.includes("?") ? url.slice(url.indexOf("?")) : "";
1065
- url = dataMatch.pagePathname + qs;
1066
- pathname = dataMatch.pagePathname;
1079
+ const pagePathname = normalizeNextDataPagePathname(dataMatch.pagePathname, hasMiddleware && trailingSlash);
1080
+ url = pagePathname + qs;
1081
+ pathname = pagePathname;
1067
1082
  }
1068
1083
  const protocol = resolveRequestProtocol(req);
1069
1084
  const hostHeader = resolveRequestHost(req, `${host}:${port}`);
@@ -1144,7 +1159,8 @@ async function startPagesRouterServer(options) {
1144
1159
  await new Promise((resolve) => {
1145
1160
  server.listen(port, host, () => {
1146
1161
  const addr = server.address();
1147
- logProdServerStarted(host, typeof addr === "object" && addr ? addr.port : port, purpose);
1162
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
1163
+ if (!silent) logProdServerStarted(host, actualPort, purpose);
1148
1164
  resolve();
1149
1165
  });
1150
1166
  });
@@ -1155,4 +1171,4 @@ async function startPagesRouterServer(options) {
1155
1171
  };
1156
1172
  }
1157
1173
  //#endregion
1158
- export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
1174
+ export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, importServerEntryModule, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, readNodeStream, rememberCurrentServerEntryImportMtime, resolveAppRouterAssetPath, resolveAppRouterPrerenderSeeder, resolveRequestHost as resolveHost, resolveServerEntryImportUrl, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts, tryServeStatic };
@@ -338,7 +338,7 @@ function isOriginAllowed(origin, allowed) {
338
338
  */
339
339
  function processMiddlewareHeaders(headers) {
340
340
  const keysToDelete = [];
341
- for (const key of headers.keys()) if (key.startsWith("x-middleware-")) keysToDelete.push(key);
341
+ for (const key of headers.keys()) if (key.startsWith("x-middleware-") && key !== "x-middleware-cache") keysToDelete.push(key);
342
342
  for (const key of keysToDelete) headers.delete(key);
343
343
  }
344
344
  const STRIPPED_INTERNAL_HEADERS = new Set([...INTERNAL_HEADERS, ...VINEXT_INTERNAL_HEADERS]);
@@ -42,6 +42,10 @@ function readStringArrayField(ctx, field) {
42
42
  if (!Array.isArray(value)) return [];
43
43
  return value.filter((item) => typeof item === "string");
44
44
  }
45
+ function readPositiveNumberField(ctx, field) {
46
+ const value = ctx?.[field];
47
+ return typeof value === "number" && value > 0 ? value : void 0;
48
+ }
45
49
  var MemoryCacheHandler = class {
46
50
  store = /* @__PURE__ */ new Map();
47
51
  tagRevalidatedAt = /* @__PURE__ */ new Map();
@@ -89,7 +93,10 @@ var MemoryCacheHandler = class {
89
93
  return null;
90
94
  }
91
95
  this.touchEntry(key, entry);
92
- if (entry.revalidateAt !== null && Date.now() > entry.revalidateAt) return {
96
+ const now = Date.now();
97
+ const requestedRevalidate = readPositiveNumberField(ctx, "revalidate");
98
+ const requestedRevalidateAt = requestedRevalidate === void 0 ? null : entry.lastModified + requestedRevalidate * 1e3;
99
+ if (entry.revalidateAt !== null && now > entry.revalidateAt || requestedRevalidateAt !== null && now > requestedRevalidateAt) return {
93
100
  lastModified: entry.lastModified,
94
101
  value: entry.value,
95
102
  cacheState: "stale",
@@ -8,6 +8,7 @@ import { makeHangingPromise } from "./internal/make-hanging-promise.js";
8
8
  import { getHeadersAccessPhase, isDraftModeEnabled, markDynamicUsage } from "./headers.js";
9
9
  import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _consumeRequestScopedCacheLife, _initRequestScopedCacheState, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, recordUnstableCacheObservation, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
10
10
  import { encodeCacheTag, encodeCacheTags } from "../utils/encode-cache-tag.js";
11
+ import { addCollectedRequestTags } from "./fetch-cache.js";
11
12
  import { workUnitAsyncStorage } from "./internal/work-unit-async-storage.js";
12
13
  //#region src/shims/cache.ts
13
14
  /**
@@ -124,6 +125,7 @@ function updateTag(tag) {
124
125
  * It's provided for API compatibility so apps importing it don't break.
125
126
  */
126
127
  function unstable_noStore() {
128
+ if (isInsideUnstableCacheScope()) return;
127
129
  markDynamicUsage();
128
130
  }
129
131
  /**
@@ -373,6 +375,7 @@ function unstable_cache(fn, keyParts, options) {
373
375
  const revalidateSeconds = options?.revalidate;
374
376
  const cachedFn = async (...args) => {
375
377
  const cacheKey = `unstable_cache:${baseKey}:${JSON.stringify(args)}`;
378
+ addCollectedRequestTags(tags);
376
379
  recordUnstableCacheObservation({
377
380
  kind: "unstable_cache",
378
381
  keyHash: fnv1a64(cacheKey),
@@ -42,6 +42,7 @@ type FetchCacheState = {
42
42
  currentFetchCacheMode: FetchCacheMode | null;
43
43
  currentForceDynamicFetchDefault: boolean;
44
44
  dynamicFetchUrls: Set<string>;
45
+ refreshStaleFetchesInForeground: boolean;
45
46
  isFetchDedupeActive: boolean;
46
47
  currentFetchDedupeEntries: Map<string, FetchDedupeEntry[]>;
47
48
  };
@@ -86,6 +87,7 @@ declare function setCurrentFetchSoftTags(tags: string[]): void;
86
87
  declare function getCurrentFetchSoftTags(): string[];
87
88
  declare function setCurrentFetchCacheMode(mode: FetchCacheMode | null): void;
88
89
  declare function setCurrentForceDynamicFetchDefault(enabled: boolean): void;
90
+ declare function setRefreshStaleFetchesInForeground(enabled: boolean): void;
89
91
  /**
90
92
  * Install the patched fetch and reset per-request tag state.
91
93
  * Returns a cleanup function that clears tags.
@@ -133,4 +135,4 @@ declare function ensureFetchPatch(): void;
133
135
  */
134
136
  declare function getOriginalFetch(): typeof globalThis.fetch;
135
137
  //#endregion
136
- export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, setCurrentForceDynamicFetchDefault, withFetchCache };
138
+ export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, setCurrentForceDynamicFetchDefault, setRefreshStaleFetchesInForeground, withFetchCache };