vinext 1.0.0-beta.2 → 1.0.0-beta.3

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 (126) hide show
  1. package/README.md +6 -9
  2. package/dist/build/client-build-config.js +7 -1
  3. package/dist/check.js +4 -0
  4. package/dist/config/config-matchers.js +4 -1
  5. package/dist/config/next-config.d.ts +12 -3
  6. package/dist/config/next-config.js +4 -0
  7. package/dist/entries/app-browser-entry.d.ts +4 -10
  8. package/dist/entries/app-browser-entry.js +20 -4
  9. package/dist/entries/app-rsc-entry.js +3 -0
  10. package/dist/entries/app-rsc-manifest.js +19 -0
  11. package/dist/entries/pages-client-entry.js +0 -1
  12. package/dist/entries/pages-server-entry.js +10 -3
  13. package/dist/index.js +112 -43
  14. package/dist/init-cloudflare.d.ts +3 -1
  15. package/dist/init-cloudflare.js +59 -5
  16. package/dist/plugins/extensionless-dynamic-import.js +23 -18
  17. package/dist/plugins/ignore-dynamic-requests.js +3 -1
  18. package/dist/plugins/import-meta-url.js +17 -6
  19. package/dist/plugins/require-context.js +20 -15
  20. package/dist/plugins/transform-cache.d.ts +19 -0
  21. package/dist/plugins/transform-cache.js +36 -0
  22. package/dist/routing/app-route-graph.d.ts +14 -6
  23. package/dist/routing/app-route-graph.js +106 -16
  24. package/dist/server/api-handler.d.ts +2 -0
  25. package/dist/server/api-handler.js +3 -3
  26. package/dist/server/app-browser-entry.js +103 -31
  27. package/dist/server/app-browser-navigation-controller.d.ts +23 -21
  28. package/dist/server/app-browser-navigation-controller.js +1 -1
  29. package/dist/server/app-page-cache-finalizer.js +1 -1
  30. package/dist/server/app-page-cache.d.ts +1 -1
  31. package/dist/server/app-page-cache.js +16 -2
  32. package/dist/server/app-page-dispatch.d.ts +8 -0
  33. package/dist/server/app-page-dispatch.js +31 -6
  34. package/dist/server/app-page-element-builder.d.ts +2 -0
  35. package/dist/server/app-page-element-builder.js +33 -7
  36. package/dist/server/app-page-head.d.ts +3 -3
  37. package/dist/server/app-page-head.js +58 -13
  38. package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
  39. package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
  40. package/dist/server/app-page-probe.d.ts +4 -0
  41. package/dist/server/app-page-probe.js +6 -2
  42. package/dist/server/app-page-request.d.ts +3 -0
  43. package/dist/server/app-page-request.js +1 -1
  44. package/dist/server/app-page-response.js +1 -1
  45. package/dist/server/app-page-route-wiring.d.ts +7 -0
  46. package/dist/server/app-page-route-wiring.js +211 -49
  47. package/dist/server/app-pages-bridge.d.ts +1 -1
  48. package/dist/server/app-pages-bridge.js +2 -1
  49. package/dist/server/app-route-handler-cache.js +4 -0
  50. package/dist/server/app-route-handler-dispatch.js +9 -3
  51. package/dist/server/app-route-handler-response.js +1 -1
  52. package/dist/server/app-route-module-loader.d.ts +6 -0
  53. package/dist/server/app-route-module-loader.js +3 -0
  54. package/dist/server/app-router-entry.js +6 -3
  55. package/dist/server/app-rsc-errors.js +10 -0
  56. package/dist/server/app-rsc-handler.js +21 -9
  57. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  58. package/dist/server/app-rsc-route-matching.js +6 -0
  59. package/dist/server/app-ssr-entry.js +4 -4
  60. package/dist/server/cache-control.d.ts +2 -2
  61. package/dist/server/cache-control.js +2 -1
  62. package/dist/server/dev-server.d.ts +3 -1
  63. package/dist/server/dev-server.js +145 -312
  64. package/dist/server/headers.d.ts +2 -2
  65. package/dist/server/headers.js +4 -3
  66. package/dist/server/isr-cache.d.ts +10 -33
  67. package/dist/server/isr-cache.js +33 -43
  68. package/dist/server/isr-decision.d.ts +2 -2
  69. package/dist/server/pages-api-route.d.ts +2 -0
  70. package/dist/server/pages-api-route.js +4 -1
  71. package/dist/server/pages-dev-hydration.js +0 -1
  72. package/dist/server/pages-get-initial-props.d.ts +1 -1
  73. package/dist/server/pages-get-initial-props.js +1 -4
  74. package/dist/server/pages-i18n.js +64 -16
  75. package/dist/server/pages-node-compat.d.ts +2 -0
  76. package/dist/server/pages-node-compat.js +7 -3
  77. package/dist/server/pages-page-data.d.ts +61 -9
  78. package/dist/server/pages-page-data.js +334 -74
  79. package/dist/server/pages-page-handler.d.ts +5 -1
  80. package/dist/server/pages-page-handler.js +115 -19
  81. package/dist/server/pages-page-response.d.ts +5 -3
  82. package/dist/server/pages-page-response.js +23 -26
  83. package/dist/server/pages-request-pipeline.d.ts +7 -0
  84. package/dist/server/pages-request-pipeline.js +5 -1
  85. package/dist/server/pages-revalidate.d.ts +1 -1
  86. package/dist/server/pages-revalidate.js +54 -8
  87. package/dist/server/pages-router-entry.d.ts +1 -0
  88. package/dist/server/pages-router-entry.js +10 -4
  89. package/dist/server/prod-server.d.ts +2 -1
  90. package/dist/server/prod-server.js +37 -13
  91. package/dist/server/revalidation-host.d.ts +11 -0
  92. package/dist/server/revalidation-host.js +19 -0
  93. package/dist/server/worker-revalidation-context.d.ts +13 -0
  94. package/dist/server/worker-revalidation-context.js +29 -0
  95. package/dist/shims/cache-handler.d.ts +1 -1
  96. package/dist/shims/cache-handler.js +10 -7
  97. package/dist/shims/cache-runtime.d.ts +6 -0
  98. package/dist/shims/cache-runtime.js +4 -1
  99. package/dist/shims/cache.js +1 -1
  100. package/dist/shims/error.js +1 -1
  101. package/dist/shims/fetch-cache.js +1 -1
  102. package/dist/shims/font-google-base.js +17 -8
  103. package/dist/shims/font-local.js +41 -10
  104. package/dist/shims/headers.d.ts +10 -13
  105. package/dist/shims/headers.js +85 -36
  106. package/dist/shims/internal/cookie-serialize.d.ts +10 -9
  107. package/dist/shims/internal/cookie-serialize.js +9 -8
  108. package/dist/shims/internal/interpolate-as.d.ts +15 -1
  109. package/dist/shims/internal/interpolate-as.js +27 -2
  110. package/dist/shims/link.js +18 -6
  111. package/dist/shims/metadata.d.ts +7 -17
  112. package/dist/shims/metadata.js +74 -24
  113. package/dist/shims/navigation.js +1 -1
  114. package/dist/shims/request-context.d.ts +4 -1
  115. package/dist/shims/router.js +18 -7
  116. package/dist/shims/script.js +1 -1
  117. package/dist/shims/server.d.ts +7 -6
  118. package/dist/shims/server.js +156 -57
  119. package/dist/shims/unified-request-context.d.ts +21 -2
  120. package/dist/shims/unified-request-context.js +82 -1
  121. package/dist/utils/cache-control-metadata.d.ts +2 -1
  122. package/dist/utils/cache-control-metadata.js +5 -1
  123. package/dist/utils/html-limited-bots.js +1 -1
  124. package/dist/utils/protocol-headers.d.ts +7 -1
  125. package/dist/utils/protocol-headers.js +7 -1
  126. package/package.json +1 -1
@@ -16,6 +16,7 @@ import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isS
16
16
  import { installSocketErrorBackstop } from "./socket-error-backstop.js";
17
17
  import { CONTENT_TYPES, StaticFileCache, etagFromFilenameHash } from "./static-file-cache.js";
18
18
  import { collectInlineCssManifest } from "../build/inline-css.js";
19
+ import { readTrustedRevalidationHostname } from "./revalidation-host.js";
19
20
  import { mergeHeaders } from "./worker-utils.js";
20
21
  import { runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
21
22
  import { computeClientRuntimeMetadata } from "../utils/client-runtime-metadata.js";
@@ -557,13 +558,16 @@ async function statIfFile(filePath) {
557
558
  * When `urlOverride` is provided, it is used as the path + query string
558
559
  * instead of `req.url`.
559
560
  */
560
- function nodeToWebRequest(req, urlOverride, prerenderSecret) {
561
- const origin = `${resolveRequestProtocol(req)}://${resolveRequestHost(req, "localhost")}`;
562
- const url = new URL(urlOverride ?? req.url ?? "/", origin);
561
+ function nodeToWebRequest(req, urlOverride, prerenderSecret, i18nConfig, authorizeOnDemandRevalidate) {
562
+ const proto = resolveRequestProtocol(req);
563
563
  const rawHeaders = nodeHeadersToWebHeaders(req.headers);
564
+ const revalidationHostname = readTrustedRevalidationHostname(rawHeaders, i18nConfig, authorizeOnDemandRevalidate);
565
+ const origin = `${proto}://${revalidationHostname ?? resolveRequestHost(req, "localhost")}`;
566
+ const url = new URL(urlOverride ?? req.url ?? "/", origin);
564
567
  const prerenderRouteParamsPayload = readTrustedPrerenderRouteParamsFromHeaders(rawHeaders, prerenderSecret);
565
568
  const isTrustedSpeculativePrerender = process.env.VINEXT_PRERENDER === "1" && prerenderSecret !== void 0 && rawHeaders.get("x-vinext-prerender-secret") === prerenderSecret && rawHeaders.get("x-vinext-prerender-speculative") === "1";
566
569
  const headers = filterInternalHeaders(rawHeaders);
570
+ if (revalidationHostname) headers.set("host", revalidationHostname);
567
571
  const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParamsPayload);
568
572
  if (prerenderRouteParamsHeader !== null) headers.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
569
573
  if (isTrustedSpeculativePrerender) headers.set(VINEXT_PRERENDER_SPECULATIVE_HEADER, "1");
@@ -660,19 +664,32 @@ async function startProdServer(options = {}) {
660
664
  silent
661
665
  });
662
666
  }
663
- function createNodeExecutionContext() {
667
+ function createNodeExecutionContext(trustedRevalidateOrigin) {
664
668
  return {
665
669
  waitUntil(promise) {
666
670
  Promise.resolve(promise).catch(() => {});
667
671
  },
668
- passThroughOnException() {}
672
+ passThroughOnException() {},
673
+ trustedRevalidateOrigin
669
674
  };
670
675
  }
676
+ function resolveTrustedNodeRevalidateOrigin(req, configuredHost, configuredPort) {
677
+ const port = req.socket.localPort ?? configuredPort;
678
+ return `http://${normalizeInternalFetchHost(configuredHost)}:${port}`;
679
+ }
680
+ function normalizeInternalFetchHost(host) {
681
+ if (!host || host === "0.0.0.0" || host === "::") return "localhost";
682
+ if (host.includes(":") && !host.startsWith("[")) return `[${host}]`;
683
+ return host;
684
+ }
671
685
  function resolveAppRouterHandler(entry) {
672
- if (typeof entry === "function") return (request) => Promise.resolve(entry(request));
686
+ if (typeof entry === "function") {
687
+ const handler = entry;
688
+ return (request, ctx) => Promise.resolve(handler(request, ctx));
689
+ }
673
690
  if (entry && typeof entry === "object" && "fetch" in entry) {
674
691
  const workerEntry = entry;
675
- if (typeof workerEntry.fetch === "function") return (request) => Promise.resolve(workerEntry.fetch(request, void 0, createNodeExecutionContext()));
692
+ if (typeof workerEntry.fetch === "function") return (request, ctx) => Promise.resolve(workerEntry.fetch(request, void 0, ctx));
676
693
  }
677
694
  console.error("[vinext] App Router entry must export either a default handler function or a Worker-style default export with fetch()");
678
695
  process.exit(1);
@@ -793,6 +810,8 @@ async function startAppRouterServer(options) {
793
810
  const appRouterBasePath = typeof rscModule.__basePath === "string" ? rscModule.__basePath : "";
794
811
  const appRouterInlineCss = rscModule.__inlineCss === true;
795
812
  const appRouterHasPagesDir = rscModule.__hasPagesDir === true;
813
+ const appRouterI18nConfig = rscModule.__i18nConfig ?? null;
814
+ const appRouterAuthorizeOnDemandRevalidate = typeof rscModule.authorizeOnDemandRevalidate === "function" ? rscModule.authorizeOnDemandRevalidate : void 0;
796
815
  const appImageAllowedWidths = Array.isArray(rscModule.__imageAllowedWidths) ? rscModule.__imageAllowedWidths : [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
797
816
  let imageConfig = typeof rscModule.__imageConfig === "object" && rscModule.__imageConfig !== null ? rscModule.__imageConfig : void 0;
798
817
  if (imageConfig === void 0) {
@@ -870,8 +889,8 @@ async function startAppRouterServer(options) {
870
889
  return;
871
890
  }
872
891
  try {
873
- const request = nodeToWebRequest(req, rawUrl, prerenderSecret);
874
- const response = await rscHandler(request);
892
+ const request = nodeToWebRequest(req, rawUrl, prerenderSecret, appRouterI18nConfig, appRouterAuthorizeOnDemandRevalidate);
893
+ const response = await rscHandler(request, createNodeExecutionContext(resolveTrustedNodeRevalidateOrigin(req, host, port)));
875
894
  if (missingBuildAsset && response.status === 404) {
876
895
  cancelResponseBody(response);
877
896
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
@@ -935,14 +954,16 @@ function readPagesServerEntryPageRoutes(value) {
935
954
  *
936
955
  * Uses the server entry (dist/server/entry.js) which exports:
937
956
  * - renderPage(request, url, manifest, ctx?, middlewareHeaders?) — SSR rendering (Web Request → Response)
938
- * - handleApiRoute(request, url, ctx?) — API route handling (ctx optional; pass for ctx.waitUntil() on Workers)
957
+ * - handleApiRoute(request, url, ctx?, trustedRevalidateOrigin?) — API route handling
958
+ * (ctx optional; pass for ctx.waitUntil() on Workers)
959
+ * - authorizeOnDemandRevalidate(header) — validates against the bundled build secret
939
960
  * - runMiddleware(request, ctx?) — middleware execution (ctx optional; pass for ctx.waitUntil() on Workers)
940
961
  * - vinextConfig — embedded next.config.js settings
941
962
  */
942
963
  async function startPagesRouterServer(options) {
943
964
  const { port, host, clientDir, serverEntryPath, compress, purpose, silent } = options;
944
965
  const serverEntry = await importServerEntryModule(serverEntryPath);
945
- const { renderPage, handleApiRoute: handleApi, runMiddleware, vinextConfig, buildId: pagesBuildId } = serverEntry;
966
+ const { renderPage, handleApiRoute: handleApi, authorizeOnDemandRevalidate, runMiddleware, vinextConfig, buildId: pagesBuildId } = serverEntry;
946
967
  const matchPageRoute = typeof serverEntry.matchPageRoute === "function" ? serverEntry.matchPageRoute : void 0;
947
968
  const hasMiddleware = serverEntry.hasMiddleware === true;
948
969
  const pageRoutes = readPagesServerEntryPageRoutes(serverEntry.pageRoutes);
@@ -1095,11 +1116,13 @@ async function startPagesRouterServer(options) {
1095
1116
  const protocol = resolveRequestProtocol(req);
1096
1117
  const hostHeader = resolveRequestHost(req, `${host}:${port}`);
1097
1118
  const rawReqHeaders = nodeHeadersToWebHeaders(req.headers);
1119
+ const revalidationHostname = readTrustedRevalidationHostname(rawReqHeaders, i18nConfig, typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0);
1098
1120
  const isDataRequest = isDataReq;
1099
1121
  const reqHeaders = filterInternalHeaders(rawReqHeaders);
1122
+ if (revalidationHostname) reqHeaders.set("host", revalidationHostname);
1100
1123
  const method = req.method ?? "GET";
1101
1124
  const hasBody = method !== "GET" && method !== "HEAD";
1102
- const result = await runPagesRequest(new Request(`${protocol}://${hostHeader}${url}`, {
1125
+ const result = await runPagesRequest(new Request(`${protocol}://${revalidationHostname ?? hostHeader}${url}`, {
1103
1126
  method,
1104
1127
  headers: reqHeaders,
1105
1128
  body: hasBody ? readNodeStream(req) : void 0,
@@ -1117,6 +1140,7 @@ async function startPagesRouterServer(options) {
1117
1140
  hasMiddleware,
1118
1141
  ctx: void 0,
1119
1142
  rawSearch: rawQs,
1143
+ authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
1120
1144
  configMatchPathname,
1121
1145
  matchPageRoute: matchPageRoute ?? null,
1122
1146
  runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
@@ -1124,7 +1148,7 @@ async function startPagesRouterServer(options) {
1124
1148
  ...options,
1125
1149
  originalUrl: originalRenderUrl
1126
1150
  }) : null,
1127
- handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext()) : null,
1151
+ handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port)) : null,
1128
1152
  serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
1129
1153
  if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
1130
1154
  return tryServeStatic(req, res, clientDir, requestPathname, compress, staticCache, stagedHeaders);
@@ -0,0 +1,11 @@
1
+ import { NextI18nConfig } from "../config/next-config.js";
2
+
3
+ //#region src/server/revalidation-host.d.ts
4
+ /**
5
+ * Read the logical request hostname carried by a server-pinned revalidation
6
+ * loopback. The side channel is accepted only as part of the authenticated
7
+ * revalidation protocol and only for an exact configured i18n domain.
8
+ */
9
+ declare function readTrustedRevalidationHostname(headers: Headers, i18nConfig: NextI18nConfig | null | undefined, authorizeRevalidation?: (headerValue: string | null) => boolean): string | null;
10
+ //#endregion
11
+ export { readTrustedRevalidationHostname };
@@ -0,0 +1,19 @@
1
+ import { VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
2
+ import { isOnDemandRevalidateRequest } from "./isr-cache.js";
3
+ import { normalizeDomainHostname } from "../utils/domain-locale.js";
4
+ //#region src/server/revalidation-host.ts
5
+ /**
6
+ * Read the logical request hostname carried by a server-pinned revalidation
7
+ * loopback. The side channel is accepted only as part of the authenticated
8
+ * revalidation protocol and only for an exact configured i18n domain.
9
+ */
10
+ function readTrustedRevalidationHostname(headers, i18nConfig, authorizeRevalidation = isOnDemandRevalidateRequest) {
11
+ if (!i18nConfig?.domains?.length || !authorizeRevalidation(headers.get("x-prerender-revalidate"))) return null;
12
+ const rawHostname = headers.get(VINEXT_REVALIDATE_HOST_HEADER);
13
+ if (!rawHostname) return null;
14
+ const hostname = rawHostname.trim().toLowerCase();
15
+ if (normalizeDomainHostname(hostname) !== hostname) return null;
16
+ return i18nConfig.domains.find((item) => normalizeDomainHostname(item.domain) === hostname)?.domain.toLowerCase() ?? null;
17
+ }
18
+ //#endregion
19
+ export { readTrustedRevalidationHostname };
@@ -0,0 +1,13 @@
1
+ import { ExecutionContextLike } from "../shims/request-context.js";
2
+
3
+ //#region src/server/worker-revalidation-context.d.ts
4
+ type PlatformExecutionContext = Partial<ExecutionContextLike>;
5
+ /**
6
+ * Add a request-local, in-process Pages revalidation dispatcher to a Worker
7
+ * execution context. Re-entering with the derived internal context preserves
8
+ * the authenticated protocol headers while keeping ordinary inbound requests
9
+ * on the normal header-scrubbing path.
10
+ */
11
+ declare function createWorkerRevalidationContext(base: PlatformExecutionContext | undefined, handleInternalRequest: (request: Request, ctx: ExecutionContextLike) => Promise<Response>): ExecutionContextLike;
12
+ //#endregion
13
+ export { createWorkerRevalidationContext };
@@ -0,0 +1,29 @@
1
+ //#region src/server/worker-revalidation-context.ts
2
+ function deriveExecutionContext(base, dispatchPagesRevalidate, isInternalPagesRevalidation) {
3
+ return {
4
+ waitUntil(promise) {
5
+ if (typeof base?.waitUntil === "function") base.waitUntil(promise);
6
+ else Promise.resolve(promise).catch(() => {});
7
+ },
8
+ ...typeof base?.passThroughOnException === "function" ? { passThroughOnException() {
9
+ base.passThroughOnException?.();
10
+ } } : {},
11
+ ...base?.cache === void 0 ? {} : { cache: base.cache },
12
+ ...base?.trustedRevalidateOrigin === void 0 ? {} : { trustedRevalidateOrigin: base.trustedRevalidateOrigin },
13
+ dispatchPagesRevalidate,
14
+ isInternalPagesRevalidation
15
+ };
16
+ }
17
+ /**
18
+ * Add a request-local, in-process Pages revalidation dispatcher to a Worker
19
+ * execution context. Re-entering with the derived internal context preserves
20
+ * the authenticated protocol headers while keeping ordinary inbound requests
21
+ * on the normal header-scrubbing path.
22
+ */
23
+ function createWorkerRevalidationContext(base, handleInternalRequest) {
24
+ if (typeof base?.dispatchPagesRevalidate === "function") return base;
25
+ const dispatchPagesRevalidate = (request) => handleInternalRequest(request, deriveExecutionContext(base, dispatchPagesRevalidate, true));
26
+ return deriveExecutionContext(base, dispatchPagesRevalidate, false);
27
+ }
28
+ //#endregion
29
+ export { createWorkerRevalidationContext };
@@ -9,7 +9,7 @@ type CacheHandlerValue = {
9
9
  value: IncrementalCacheValue | null;
10
10
  };
11
11
  type CacheControlMetadata = {
12
- revalidate: number;
12
+ revalidate: number | false;
13
13
  expire?: number;
14
14
  };
15
15
  type IncrementalCacheValue = CachedFetchValue | CachedAppPageValue | CachedPagesValue | CachedRouteValue | CachedRedirectValue | CachedImageValue;
@@ -1,4 +1,4 @@
1
- import { readCacheControlNumberField } from "../utils/cache-control-metadata.js";
1
+ import { readCacheControlNumberField, readCacheControlRevalidateField } from "../utils/cache-control-metadata.js";
2
2
  //#region src/shims/cache-handler.ts
3
3
  var NoOpCacheHandler = class {
4
4
  async get(_key, _ctx) {
@@ -88,12 +88,14 @@ var MemoryCacheHandler = class {
88
88
  const revalidatedAt = this.tagRevalidatedAt.get(tag);
89
89
  if (revalidatedAt && revalidatedAt >= entry.lastModified) return null;
90
90
  }
91
- if (entry.expireAt !== null && Date.now() > entry.expireAt) {
92
- this.deleteEntry(key);
93
- return null;
94
- }
95
91
  this.touchEntry(key, entry);
96
92
  const now = Date.now();
93
+ if (entry.expireAt !== null && now > entry.expireAt) return {
94
+ lastModified: entry.lastModified,
95
+ value: entry.value,
96
+ cacheState: "expired",
97
+ cacheControl: entry.cacheControl
98
+ };
97
99
  const requestedRevalidate = readPositiveNumberField(ctx, "revalidate");
98
100
  const requestedRevalidateAt = requestedRevalidate === void 0 ? null : entry.lastModified + requestedRevalidate * 1e3;
99
101
  if (entry.revalidateAt !== null && now > entry.revalidateAt || requestedRevalidateAt !== null && now > requestedRevalidateAt) return {
@@ -113,14 +115,15 @@ var MemoryCacheHandler = class {
113
115
  if (data && "tags" in data && Array.isArray(data.tags)) for (const tag of data.tags) tagSet.add(tag);
114
116
  for (const tag of readStringArrayField(ctx, "tags")) tagSet.add(tag);
115
117
  const tags = [...tagSet];
116
- let effectiveRevalidate = readCacheControlNumberField(ctx, "revalidate");
118
+ let effectiveRevalidate = readCacheControlRevalidateField(ctx);
117
119
  const effectiveExpire = readCacheControlNumberField(ctx, "expire");
118
120
  if (data && "revalidate" in data && typeof data.revalidate === "number") effectiveRevalidate = data.revalidate;
121
+ else if (data && "revalidate" in data && data.revalidate === false) effectiveRevalidate ??= false;
119
122
  if (effectiveRevalidate === 0) return;
120
123
  const now = Date.now();
121
124
  const revalidateAt = typeof effectiveRevalidate === "number" && effectiveRevalidate > 0 ? now + effectiveRevalidate * 1e3 : null;
122
125
  const expireAt = typeof effectiveExpire === "number" && effectiveExpire > 0 ? now + effectiveExpire * 1e3 : null;
123
- const cacheControl = typeof effectiveRevalidate === "number" ? effectiveExpire === void 0 ? { revalidate: effectiveRevalidate } : {
126
+ const cacheControl = typeof effectiveRevalidate === "number" || effectiveRevalidate === false ? effectiveExpire === void 0 ? { revalidate: effectiveRevalidate } : {
124
127
  revalidate: effectiveRevalidate,
125
128
  expire: effectiveExpire
126
129
  } : void 0;
@@ -71,6 +71,12 @@ declare function runWithPrivateCache<T>(fn: () => T | Promise<T>): T | Promise<T
71
71
  */
72
72
  declare function clearPrivateCache(): void;
73
73
  type RegisterCachedFunctionOptions = {
74
+ /**
75
+ * Whether the original function declaration accepts a second argument.
76
+ * Function.length cannot represent default or rest parameters, so the
77
+ * transform records this separately for metadata parent resolution.
78
+ */
79
+ acceptsSecondArgument?: boolean;
74
80
  /**
75
81
  * Internal transform metadata for file-level `"use cache"` default exports
76
82
  * in App Router `page.*` files. Page components receive framework-owned
@@ -334,10 +334,13 @@ function registerCachedFunction(fn, id, variant, options = {}) {
334
334
  configurable: true
335
335
  });
336
336
  cachedFn[USE_CACHE_FUNCTION_SYMBOL] = true;
337
+ if (options.acceptsSecondArgument !== void 0) Reflect.set(cachedFn, USE_CACHE_ACCEPTS_SECOND_ARGUMENT_SYMBOL, options.acceptsSecondArgument);
337
338
  return cachedFn;
338
339
  }
339
340
  /** @internal Symbol used to identify "use cache" wrapper functions. */
340
341
  const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
342
+ /** @internal Symbol carrying transform-derived cached function argument metadata. */
343
+ const USE_CACHE_ACCEPTS_SECOND_ARGUMENT_SYMBOL = Symbol.for("vinext.useCacheAcceptsSecondArgument");
341
344
  function throwPrivateUseCacheInsidePublicUseCacheError() {
342
345
  const error = /* @__PURE__ */ new Error("\"use cache: private\" must not be used within \"use cache\". It can only be nested inside of another \"use cache: private\".");
343
346
  const ctx = getRequestContext();
@@ -347,7 +350,7 @@ function throwPrivateUseCacheInsidePublicUseCacheError() {
347
350
  function recordRequestScopedCacheControl(cacheControl) {
348
351
  if (cacheControl === void 0) return;
349
352
  _setRequestScopedCacheLife({
350
- revalidate: cacheControl.revalidate,
353
+ revalidate: cacheControl.revalidate === false ? void 0 : cacheControl.revalidate,
351
354
  expire: cacheControl.expire
352
355
  });
353
356
  }
@@ -4,10 +4,10 @@ import { getRequestExecutionContext, runWithExecutionContext } from "./request-c
4
4
  import { MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, setCacheHandler, setDataCacheHandler } from "./cache-handler.js";
5
5
  import { getCdnCacheAdapter } from "./cdn-cache.js";
6
6
  import { fnv1a64 } from "../utils/hash.js";
7
+ import { encodeCacheTag, encodeCacheTags } from "../utils/encode-cache-tag.js";
7
8
  import { makeHangingPromise } from "./internal/make-hanging-promise.js";
8
9
  import { getHeadersAccessPhase, isDraftModeEnabled, markDynamicUsage } from "./headers.js";
9
10
  import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, recordUnstableCacheObservation, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
10
- import { encodeCacheTag, encodeCacheTags } from "../utils/encode-cache-tag.js";
11
11
  import { addCollectedRequestTags, getCurrentFetchSoftTags } from "./fetch-cache.js";
12
12
  import { workUnitAsyncStorage } from "./internal/work-unit-async-storage.js";
13
13
  //#region src/shims/cache.ts
@@ -1,8 +1,8 @@
1
1
  import { AppRouterContext } from "./internal/app-router-context.js";
2
2
  import { RouterContext } from "./internal/router-context.js";
3
- import Head from "./head.js";
4
3
  import { isNextRouterError } from "./navigation-errors.js";
5
4
  import "./navigation.js";
5
+ import Head from "./head.js";
6
6
  import { useUntrackedPathname } from "./internal/navigation-untracked.js";
7
7
  import React from "react";
8
8
  //#region src/shims/error.tsx
@@ -2,9 +2,9 @@ import { getOrCreateAls } from "./internal/als-registry.js";
2
2
  import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation } from "./unified-request-context.js";
3
3
  import { getRequestExecutionContext } from "./request-context.js";
4
4
  import { getDataCacheHandler } from "./cache-handler.js";
5
+ import { encodeCacheTags } from "../utils/encode-cache-tag.js";
5
6
  import { markDynamicUsage } from "./headers.js";
6
7
  import { _hasPendingRevalidatedTag, _setRequestScopedCacheLife } from "./cache-request-state.js";
7
- import { encodeCacheTags } from "../utils/encode-cache-tag.js";
8
8
  //#region src/shims/fetch-cache.ts
9
9
  /**
10
10
  * Extended fetch() with Next.js caching semantics.
@@ -19,7 +19,16 @@ import { buildGoogleFontsUrl as buildGoogleFontsUrl$1 } from "../build/google-fo
19
19
  * // inter.style -> { fontFamily: "'Inter', 'Inter Fallback'", fontStyle: "normal" }
20
20
  * // inter.variable -> CSS class that sets the font CSS variable when requested
21
21
  */
22
- const injectedFonts = /* @__PURE__ */ new Set();
22
+ const _INJECTED_FONTS_KEY = Symbol.for("vinext.font.injectedFonts");
23
+ const _INJECTED_CLASS_RULES_KEY = Symbol.for("vinext.font.injectedClassRules");
24
+ const _INJECTED_VARIABLE_RULES_KEY = Symbol.for("vinext.font.injectedVariableRules");
25
+ const _INJECTED_SELF_HOSTED_KEY = Symbol.for("vinext.font.injectedSelfHosted");
26
+ const _SSR_FONT_STYLES_KEY = Symbol.for("vinext.font.ssrFontStyles");
27
+ const _SSR_FONT_URLS_KEY = Symbol.for("vinext.font.ssrFontUrls");
28
+ const _SSR_FONT_PRELOADS_KEY = Symbol.for("vinext.font.ssrFontPreloads");
29
+ const _SSR_FONT_PRELOAD_HREFS_KEY = Symbol.for("vinext.font.ssrFontPreloadHrefs");
30
+ const _g = globalThis;
31
+ const injectedFonts = _g[_INJECTED_FONTS_KEY] ??= /* @__PURE__ */ new Set();
23
32
  /**
24
33
  * Convert a font family name to a CSS variable name.
25
34
  * e.g., "Inter" -> "--font-inter", "Roboto Mono" -> "--font-roboto-mono"
@@ -128,7 +137,7 @@ function injectFontStylesheet(url) {
128
137
  }
129
138
  }
130
139
  /** Track which className CSS rules have been injected. */
131
- const injectedClassRules = /* @__PURE__ */ new Set();
140
+ const injectedClassRules = _g[_INJECTED_CLASS_RULES_KEY] ??= /* @__PURE__ */ new Set();
132
141
  /**
133
142
  * Inject a CSS rule that maps a className to the exported font style.
134
143
  *
@@ -152,7 +161,7 @@ function injectClassNameRule(className, fontStyle) {
152
161
  document.head.appendChild(styleElement);
153
162
  }
154
163
  /** Track which variable class CSS rules have been injected. */
155
- const injectedVariableRules = /* @__PURE__ */ new Set();
164
+ const injectedVariableRules = _g[_INJECTED_VARIABLE_RULES_KEY] ??= /* @__PURE__ */ new Set();
156
165
  /**
157
166
  * Inject a CSS rule that sets a CSS variable on an element.
158
167
  * This is what makes `<html className={inter.variable}>` set the CSS variable
@@ -177,7 +186,7 @@ function injectVariableClassRule(variableClassName, cssVarName, fontFamily) {
177
186
  style.setAttribute("data-vinext-font-variable", variableClassName);
178
187
  document.head.appendChild(style);
179
188
  }
180
- const ssrFontStyles = [];
189
+ const ssrFontStyles = _g[_SSR_FONT_STYLES_KEY] ??= [];
181
190
  /**
182
191
  * Get collected SSR font class styles (used by the renderer).
183
192
  * Note: We don't clear the arrays because fonts are loaded at module import
@@ -186,7 +195,7 @@ const ssrFontStyles = [];
186
195
  function getSSRFontStyles() {
187
196
  return [...ssrFontStyles];
188
197
  }
189
- const ssrFontUrls = [];
198
+ const ssrFontUrls = _g[_SSR_FONT_URLS_KEY] ??= [];
190
199
  /**
191
200
  * Get collected SSR font URLs (used by the renderer).
192
201
  * Note: We don't clear the arrays because fonts are loaded at module import
@@ -195,8 +204,8 @@ const ssrFontUrls = [];
195
204
  function getSSRFontLinks() {
196
205
  return [...ssrFontUrls];
197
206
  }
198
- const ssrFontPreloads = [];
199
- const ssrFontPreloadHrefs = /* @__PURE__ */ new Set();
207
+ const ssrFontPreloads = _g[_SSR_FONT_PRELOADS_KEY] ??= [];
208
+ const ssrFontPreloadHrefs = _g[_SSR_FONT_PRELOAD_HREFS_KEY] ??= /* @__PURE__ */ new Set();
200
209
  /**
201
210
  * Get collected SSR font preload data (used by the renderer).
202
211
  * Returns an array of { href, type } objects for emitting
@@ -220,7 +229,7 @@ function collectFontPreloads(urls) {
220
229
  }
221
230
  }
222
231
  /** Track injected self-hosted @font-face blocks (deduplicate) */
223
- const injectedSelfHosted = /* @__PURE__ */ new Set();
232
+ const injectedSelfHosted = _g[_INJECTED_SELF_HOSTED_KEY] ??= /* @__PURE__ */ new Set();
224
233
  /**
225
234
  * Inject self-hosted @font-face CSS (from the build plugin).
226
235
  * This replaces the CDN <link> tag with inline CSS.
@@ -1,3 +1,4 @@
1
+ import { fnv1a64 } from "../utils/hash.js";
1
2
  import { escapeCSSString, formatFontClassRule, getFontMimeType, resolveSingleFaceStyle, sanitizeCSSVarName, sanitizeFallback, sanitizeFontDescriptorValue } from "./font-utils.js";
2
3
  //#region src/shims/font-local.ts
3
4
  /**
@@ -31,8 +32,15 @@ function sanitizeInternalFontFamily(name) {
31
32
  if (typeof name !== "string" || name.length === 0) return void 0;
32
33
  if (/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(name)) return name;
33
34
  }
34
- let classCounter = 0;
35
- const injectedFonts = /* @__PURE__ */ new Set();
35
+ const _INJECTED_FONTS_KEY = Symbol.for("vinext.fontLocal.injectedFonts");
36
+ const _INJECTED_CLASS_RULES_KEY = Symbol.for("vinext.fontLocal.injectedClassRules");
37
+ const _INJECTED_VARIABLE_RULES_KEY = Symbol.for("vinext.fontLocal.injectedVariableRules");
38
+ const _INJECTED_ROOT_VARIABLES_KEY = Symbol.for("vinext.fontLocal.injectedRootVariables");
39
+ const _SSR_FONT_STYLES_KEY = Symbol.for("vinext.fontLocal.ssrFontStyles");
40
+ const _SSR_FONT_PRELOADS_KEY = Symbol.for("vinext.fontLocal.ssrFontPreloads");
41
+ const _SSR_FONT_PRELOAD_HREFS_KEY = Symbol.for("vinext.fontLocal.ssrFontPreloadHrefs");
42
+ const _g = globalThis;
43
+ const injectedFonts = _g[_INJECTED_FONTS_KEY] ??= /* @__PURE__ */ new Set();
36
44
  function generateFontFaceCSS(family, options, sources) {
37
45
  const display = options.display ?? "swap";
38
46
  const rules = [];
@@ -55,9 +63,9 @@ function generateFontFaceCSS(family, options, sources) {
55
63
  }
56
64
  return rules.join("\n");
57
65
  }
58
- const ssrFontStyles = [];
59
- const ssrFontPreloads = [];
60
- const ssrFontPreloadHrefs = /* @__PURE__ */ new Set();
66
+ const ssrFontStyles = _g[_SSR_FONT_STYLES_KEY] ??= [];
67
+ const ssrFontPreloads = _g[_SSR_FONT_PRELOADS_KEY] ??= [];
68
+ const ssrFontPreloadHrefs = _g[_SSR_FONT_PRELOAD_HREFS_KEY] ??= /* @__PURE__ */ new Set();
61
69
  /**
62
70
  * Get collected SSR font styles (used by the renderer).
63
71
  * Note: We don't clear the arrays because fonts are loaded at module import
@@ -87,7 +95,7 @@ function injectFontFaceCSS(css, id) {
87
95
  document.head.appendChild(style);
88
96
  }
89
97
  /** Track which className CSS rules have been injected. */
90
- const injectedClassRules = /* @__PURE__ */ new Set();
98
+ const injectedClassRules = _g[_INJECTED_CLASS_RULES_KEY] ??= /* @__PURE__ */ new Set();
91
99
  /**
92
100
  * Inject a CSS rule that maps a className to the exported font style.
93
101
  *
@@ -110,9 +118,9 @@ function injectClassNameRule(className, fontStyle) {
110
118
  document.head.appendChild(style);
111
119
  }
112
120
  /** Track which variable class CSS rules have been injected. */
113
- const injectedVariableRules = /* @__PURE__ */ new Set();
121
+ const injectedVariableRules = _g[_INJECTED_VARIABLE_RULES_KEY] ??= /* @__PURE__ */ new Set();
114
122
  /** Track which :root CSS variable rules have been injected. */
115
- const injectedRootVariables = /* @__PURE__ */ new Set();
123
+ const injectedRootVariables = _g[_INJECTED_ROOT_VARIABLES_KEY] ??= /* @__PURE__ */ new Set();
116
124
  /**
117
125
  * Inject a CSS rule that sets a CSS variable on an element.
118
126
  * This is what makes `<html className={font.variable}>` set the CSS variable
@@ -151,6 +159,28 @@ function normalizeSources(options) {
151
159
  return [options.src];
152
160
  }
153
161
  /**
162
+ * Derive a stable class identity from the inputs that affect the generated
163
+ * font CSS. Next.js hashes the generated font CSS for the same reason: class
164
+ * names must match across the RSC, SSR, and browser module graphs regardless
165
+ * of which graph evaluates a font call first.
166
+ */
167
+ function createLocalFontIdentity(options, sources, internalFamily) {
168
+ return fnv1a64(JSON.stringify([
169
+ internalFamily ?? "",
170
+ sources.map((source) => [
171
+ source.path,
172
+ source.weight ?? "",
173
+ source.style ?? ""
174
+ ]),
175
+ options.display ?? "swap",
176
+ options.weight ?? "",
177
+ options.style ?? "",
178
+ options.fallback ?? ["sans-serif"],
179
+ options.variable ?? "",
180
+ options.declarations?.map((declaration) => [declaration.prop, declaration.value]) ?? []
181
+ ]));
182
+ }
183
+ /**
154
184
  * Collect font source URLs for preload link generation.
155
185
  * Only collects on the server (SSR). Deduplicates by href using a Set for O(1) lookups.
156
186
  */
@@ -168,10 +198,11 @@ function collectFontPreloads(sources) {
168
198
  }
169
199
  }
170
200
  function localFont(options) {
171
- const id = classCounter++;
172
201
  const sources = normalizeSources(options);
202
+ const internalFamily = sanitizeInternalFontFamily(options._vinext?.font?.family);
203
+ const id = createLocalFontIdentity(options, sources, internalFamily);
173
204
  const singleSource = sources.length === 1 ? sources[0] : void 0;
174
- const family = sanitizeInternalFontFamily(options._vinext?.font?.family) ?? `__local_font_${id}`;
205
+ const family = internalFamily ?? `__local_font_${id}`;
175
206
  const className = `__font_local_${id}`;
176
207
  const fontFamily = `'${family}', ${(options.fallback ?? ["sans-serif"]).map(sanitizeFallback).join(", ")}`;
177
208
  const cssVarName = options.variable ? sanitizeCSSVarName(options.variable) : void 0;
@@ -222,19 +222,16 @@ type DraftModeResult = {
222
222
  * (`getDraftModeProviderForCacheScope` + `trackDynamicDraftMode`).
223
223
  */
224
224
  declare function draftMode(): Promise<DraftModeResult>;
225
+ declare const APPLY_RESPONSE_COOKIE: unique symbol;
226
+ declare const SYNCHRONIZE_REQUEST_COOKIES: unique symbol;
225
227
  declare class RequestCookies {
226
228
  private _cookies;
227
- constructor(cookies: Map<string, string>);
228
- get(name: string): {
229
- name: string;
230
- value: string;
231
- } | undefined;
229
+ private _responseCookies;
230
+ constructor(cookies: Map<string, string>, mutable?: boolean);
231
+ get(name: string): ResponseCookie | undefined;
232
232
  getAll(nameOrOptions?: string | {
233
233
  name: string;
234
- }): Array<{
235
- name: string;
236
- value: string;
237
- }>;
234
+ }): ResponseCookie[];
238
235
  has(name: string): boolean;
239
236
  /**
240
237
  * Set a cookie. In Route Handlers and Server Actions, this produces
@@ -248,11 +245,11 @@ declare class RequestCookies {
248
245
  delete(name: string): this;
249
246
  delete(options: Omit<ResponseCookie, "value" | "expires">): this;
250
247
  get size(): number;
251
- [Symbol.iterator](): MapIterator<[string, {
252
- name: string;
253
- value: string;
254
- }]>;
248
+ [Symbol.iterator](): MapIterator<[string, ResponseCookie]>;
255
249
  toString(): string;
250
+ private _ensureResponseCookies;
251
+ [APPLY_RESPONSE_COOKIE](cookie: ResponseCookie): void;
252
+ [SYNCHRONIZE_REQUEST_COOKIES](): void;
256
253
  }
257
254
  //#endregion
258
255
  export { HeadersAccessPhase, HeadersContext, type RequestCookies, VinextHeadersShimState, applyMiddlewareRequestHeaders, consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage, cookies, draftMode, getActiveDraftModeState, getAndClearPendingCookies, getDraftModeCookieHeader, getHeadersAccessPhase, getHeadersContext, headers, headersContextFromRequest, isDraftModeEnabled, isDraftModeRequest, markDynamicUsage, markRenderRequestApiUsage, peekDynamicUsage, peekRenderRequestApiUsage, runWithConnectionProbe, runWithHeadersContext, runWithIsolatedDynamicUsage, setHeadersAccessPhase, setHeadersContext, suspendConnectionProbe, throwIfInsideCacheScope, throwIfStaticGenerationAccessError };