vinext 1.0.0-beta.2 → 1.0.0-beta.4

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 (174) hide show
  1. package/README.md +6 -9
  2. package/dist/build/client-build-config.js +7 -1
  3. package/dist/build/precompress.js +1 -1
  4. package/dist/build/prerender.d.ts +2 -1
  5. package/dist/build/prerender.js +12 -2
  6. package/dist/check.js +4 -0
  7. package/dist/cli-args.d.ts +2 -1
  8. package/dist/cli-args.js +10 -0
  9. package/dist/cli.js +14 -8
  10. package/dist/client/navigation-runtime.d.ts +6 -0
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/config-matchers.js +4 -1
  13. package/dist/config/dotenv.d.ts +3 -4
  14. package/dist/config/dotenv.js +30 -22
  15. package/dist/config/next-config.d.ts +16 -5
  16. package/dist/config/next-config.js +7 -0
  17. package/dist/entries/app-browser-entry.d.ts +4 -10
  18. package/dist/entries/app-browser-entry.js +20 -4
  19. package/dist/entries/app-rsc-entry.js +21 -3
  20. package/dist/entries/app-rsc-manifest.js +19 -0
  21. package/dist/entries/pages-client-entry.js +0 -1
  22. package/dist/entries/pages-server-entry.js +13 -3
  23. package/dist/index.js +122 -49
  24. package/dist/init-cloudflare.d.ts +3 -1
  25. package/dist/init-cloudflare.js +59 -5
  26. package/dist/plugins/client-reference-dedup.js +7 -5
  27. package/dist/plugins/extensionless-dynamic-import.js +23 -18
  28. package/dist/plugins/ignore-dynamic-requests.js +3 -1
  29. package/dist/plugins/import-meta-url.js +17 -6
  30. package/dist/plugins/require-context.js +20 -15
  31. package/dist/plugins/sass.js +3 -4
  32. package/dist/plugins/transform-cache.d.ts +19 -0
  33. package/dist/plugins/transform-cache.js +36 -0
  34. package/dist/routing/app-route-graph.d.ts +14 -6
  35. package/dist/routing/app-route-graph.js +106 -16
  36. package/dist/server/api-handler.d.ts +2 -0
  37. package/dist/server/api-handler.js +7 -6
  38. package/dist/server/app-bfcache-identity.js +28 -2
  39. package/dist/server/app-browser-entry.js +175 -75
  40. package/dist/server/app-browser-navigation-controller.d.ts +23 -21
  41. package/dist/server/app-browser-navigation-controller.js +30 -5
  42. package/dist/server/app-browser-prefetch-response.d.ts +9 -0
  43. package/dist/server/app-browser-prefetch-response.js +32 -0
  44. package/dist/server/app-browser-state.d.ts +8 -3
  45. package/dist/server/app-browser-state.js +9 -3
  46. package/dist/server/app-page-cache-finalizer.js +1 -1
  47. package/dist/server/app-page-cache.d.ts +1 -1
  48. package/dist/server/app-page-cache.js +16 -2
  49. package/dist/server/app-page-dispatch.d.ts +9 -1
  50. package/dist/server/app-page-dispatch.js +37 -8
  51. package/dist/server/app-page-element-builder.d.ts +4 -1
  52. package/dist/server/app-page-element-builder.js +34 -7
  53. package/dist/server/app-page-head.d.ts +3 -3
  54. package/dist/server/app-page-head.js +58 -13
  55. package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
  56. package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
  57. package/dist/server/app-page-probe.d.ts +4 -0
  58. package/dist/server/app-page-probe.js +6 -2
  59. package/dist/server/app-page-render.d.ts +1 -1
  60. package/dist/server/app-page-render.js +13 -3
  61. package/dist/server/app-page-request.d.ts +3 -1
  62. package/dist/server/app-page-request.js +4 -13
  63. package/dist/server/app-page-response.d.ts +2 -0
  64. package/dist/server/app-page-response.js +7 -2
  65. package/dist/server/app-page-route-wiring.d.ts +10 -2
  66. package/dist/server/app-page-route-wiring.js +269 -59
  67. package/dist/server/app-pages-bridge.d.ts +2 -1
  68. package/dist/server/app-pages-bridge.js +3 -1
  69. package/dist/server/app-route-handler-cache.js +4 -0
  70. package/dist/server/app-route-handler-dispatch.js +9 -3
  71. package/dist/server/app-route-handler-response.js +1 -1
  72. package/dist/server/app-route-module-loader.d.ts +6 -0
  73. package/dist/server/app-route-module-loader.js +3 -0
  74. package/dist/server/app-router-entry.js +6 -3
  75. package/dist/server/app-rsc-cache-busting.d.ts +7 -0
  76. package/dist/server/app-rsc-cache-busting.js +6 -0
  77. package/dist/server/app-rsc-errors.js +10 -0
  78. package/dist/server/app-rsc-handler.d.ts +2 -0
  79. package/dist/server/app-rsc-handler.js +30 -13
  80. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  81. package/dist/server/app-rsc-render-mode.js +4 -1
  82. package/dist/server/app-rsc-request-normalization.js +65 -4
  83. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  84. package/dist/server/app-rsc-route-matching.js +6 -0
  85. package/dist/server/app-server-action-execution.d.ts +1 -1
  86. package/dist/server/app-ssr-entry.js +4 -4
  87. package/dist/server/app-visited-response-cache.d.ts +6 -1
  88. package/dist/server/app-visited-response-cache.js +49 -1
  89. package/dist/server/cache-control.d.ts +2 -2
  90. package/dist/server/cache-control.js +2 -1
  91. package/dist/server/dev-server.d.ts +3 -1
  92. package/dist/server/dev-server.js +204 -348
  93. package/dist/server/edge-api-runtime.d.ts +10 -1
  94. package/dist/server/edge-api-runtime.js +29 -1
  95. package/dist/server/headers.d.ts +4 -2
  96. package/dist/server/headers.js +6 -3
  97. package/dist/server/isr-cache.d.ts +10 -33
  98. package/dist/server/isr-cache.js +33 -43
  99. package/dist/server/isr-decision.d.ts +2 -2
  100. package/dist/server/pages-api-route.d.ts +4 -0
  101. package/dist/server/pages-api-route.js +6 -3
  102. package/dist/server/pages-asset-tags.d.ts +1 -0
  103. package/dist/server/pages-asset-tags.js +6 -4
  104. package/dist/server/pages-dev-hydration.js +0 -1
  105. package/dist/server/pages-document-asset-props.d.ts +26 -0
  106. package/dist/server/pages-document-asset-props.js +69 -0
  107. package/dist/server/pages-get-initial-props.d.ts +1 -1
  108. package/dist/server/pages-get-initial-props.js +1 -4
  109. package/dist/server/pages-i18n.js +64 -16
  110. package/dist/server/pages-node-compat.d.ts +2 -0
  111. package/dist/server/pages-node-compat.js +10 -6
  112. package/dist/server/pages-page-data.d.ts +61 -9
  113. package/dist/server/pages-page-data.js +334 -74
  114. package/dist/server/pages-page-handler.d.ts +6 -1
  115. package/dist/server/pages-page-handler.js +119 -20
  116. package/dist/server/pages-page-response.d.ts +7 -3
  117. package/dist/server/pages-page-response.js +41 -33
  118. package/dist/server/pages-request-pipeline.d.ts +7 -0
  119. package/dist/server/pages-request-pipeline.js +5 -1
  120. package/dist/server/pages-revalidate.d.ts +1 -1
  121. package/dist/server/pages-revalidate.js +54 -8
  122. package/dist/server/pages-router-entry.d.ts +1 -0
  123. package/dist/server/pages-router-entry.js +10 -4
  124. package/dist/server/prerender-manifest.d.ts +1 -0
  125. package/dist/server/prod-server.d.ts +2 -1
  126. package/dist/server/prod-server.js +38 -13
  127. package/dist/server/revalidation-host.d.ts +11 -0
  128. package/dist/server/revalidation-host.js +19 -0
  129. package/dist/server/seed-cache.js +1 -1
  130. package/dist/server/worker-revalidation-context.d.ts +13 -0
  131. package/dist/server/worker-revalidation-context.js +30 -0
  132. package/dist/shims/app-router-scroll-state.d.ts +2 -1
  133. package/dist/shims/app-router-scroll-state.js +7 -2
  134. package/dist/shims/cache-handler.d.ts +1 -1
  135. package/dist/shims/cache-handler.js +10 -7
  136. package/dist/shims/cache-runtime.d.ts +6 -0
  137. package/dist/shims/cache-runtime.js +4 -1
  138. package/dist/shims/cache.js +1 -1
  139. package/dist/shims/document.js +15 -2
  140. package/dist/shims/error.js +1 -1
  141. package/dist/shims/fetch-cache.js +1 -1
  142. package/dist/shims/font-google-base.js +17 -8
  143. package/dist/shims/font-local.js +41 -10
  144. package/dist/shims/hash-scroll.d.ts +1 -1
  145. package/dist/shims/hash-scroll.js +2 -1
  146. package/dist/shims/headers.d.ts +10 -13
  147. package/dist/shims/headers.js +85 -36
  148. package/dist/shims/internal/cookie-serialize.d.ts +10 -9
  149. package/dist/shims/internal/cookie-serialize.js +9 -8
  150. package/dist/shims/internal/interpolate-as.d.ts +15 -1
  151. package/dist/shims/internal/interpolate-as.js +27 -2
  152. package/dist/shims/link.d.ts +2 -0
  153. package/dist/shims/link.js +77 -24
  154. package/dist/shims/metadata.d.ts +18 -34
  155. package/dist/shims/metadata.js +98 -56
  156. package/dist/shims/navigation.d.ts +18 -2
  157. package/dist/shims/navigation.js +70 -21
  158. package/dist/shims/request-context.d.ts +6 -2
  159. package/dist/shims/router.js +18 -7
  160. package/dist/shims/script.js +1 -1
  161. package/dist/shims/server.d.ts +7 -6
  162. package/dist/shims/server.js +156 -57
  163. package/dist/shims/slot.d.ts +2 -1
  164. package/dist/shims/slot.js +6 -2
  165. package/dist/shims/streamed-icons.d.ts +9 -0
  166. package/dist/shims/streamed-icons.js +33 -0
  167. package/dist/shims/unified-request-context.d.ts +21 -2
  168. package/dist/shims/unified-request-context.js +82 -1
  169. package/dist/utils/cache-control-metadata.d.ts +2 -1
  170. package/dist/utils/cache-control-metadata.js +5 -1
  171. package/dist/utils/html-limited-bots.js +1 -1
  172. package/dist/utils/protocol-headers.d.ts +7 -1
  173. package/dist/utils/protocol-headers.js +7 -1
  174. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
2
2
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
3
+ import { VINEXT_REVALIDATE_HOST_HEADER } from "../utils/protocol-headers.js";
3
4
  import { notFoundStaticAssetResponse } from "./http-error-responses.js";
4
5
  import { isOpenRedirectShaped } from "./open-redirect.js";
5
6
  import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders } from "./request-pipeline.js";
@@ -7,6 +8,7 @@ import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js"
7
8
  import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, handleConfiguredImageOptimization, isImageOptimizationPath } from "./image-optimization.js";
8
9
  import { finalizeMissingStaticAssetResponse } from "./worker-utils.js";
9
10
  import { fetchWorkerFilesystemRoute, runPagesRequest, wrapMiddlewareWithBasePath } from "./pages-request-pipeline.js";
11
+ import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
10
12
  import { registerConfiguredCacheAdapters } from "virtual:vinext-cache-adapters";
11
13
  import { registerConfiguredImageOptimizer } from "virtual:vinext-image-adapters";
12
14
  import * as pagesEntry from "virtual:vinext-server-entry";
@@ -25,7 +27,7 @@ import * as pagesEntry from "virtual:vinext-server-entry";
25
27
  * import handler from "vinext/server/pages-router-entry";
26
28
  * return handler.fetch(request, env, ctx);
27
29
  */
28
- const { handleApiRoute, hasMiddleware, matchPageRoute, normalizeDataRequest, renderPage, runMiddleware, vinextConfig } = pagesEntry;
30
+ const { authorizeOnDemandRevalidate, handleApiRoute, hasMiddleware, matchPageRoute, normalizeDataRequest, renderPage, runMiddleware, vinextConfig } = pagesEntry;
29
31
  const basePath = vinextConfig?.basePath ?? "";
30
32
  const assetPathPrefix = assetPrefixPathname(vinextConfig?.assetPrefix ?? "");
31
33
  const trailingSlash = vinextConfig?.trailingSlash ?? false;
@@ -47,7 +49,8 @@ const imageConfig = vinextConfig?.images ? {
47
49
  var pages_router_entry_default = { async fetch(request, env, ctx) {
48
50
  return handleRequest(request, env, ctx);
49
51
  } };
50
- async function handleRequest(request, env, ctx) {
52
+ async function handleRequest(request, env, platformCtx) {
53
+ const ctx = createWorkerRevalidationContext(platformCtx, (internalRequest, internalCtx) => handleRequest(internalRequest, env, internalCtx));
51
54
  registerConfiguredCacheAdapters(env);
52
55
  registerConfiguredImageOptimizer(env);
53
56
  try {
@@ -59,7 +62,9 @@ async function handleRequest(request, env, ctx) {
59
62
  return new Response("Bad Request", { status: 400 });
60
63
  }
61
64
  const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
62
- request = cloneRequestWithHeaders(request, filterInternalHeaders(request.headers));
65
+ const filteredHeaders = ctx.isInternalPagesRevalidation ? new Headers(request.headers) : filterInternalHeaders(request.headers);
66
+ filteredHeaders.delete(VINEXT_REVALIDATE_HOST_HEADER);
67
+ request = cloneRequestWithHeaders(request, filteredHeaders);
63
68
  const hadBasePath = !basePath || hasBasePath(pathname, basePath);
64
69
  {
65
70
  const stripped = stripBasePath(pathname, basePath);
@@ -93,10 +98,11 @@ async function handleRequest(request, env, ctx) {
93
98
  isDataRequest: isDataReq,
94
99
  hasMiddleware,
95
100
  ctx,
101
+ authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
96
102
  matchPageRoute: typeof matchPageRoute === "function" ? matchPageRoute : null,
97
103
  runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
98
104
  renderPage: typeof renderPage === "function" ? (req, resolvedUrl, options, stagedHeaders) => renderPage(req, resolvedUrl, null, ctx, stagedHeaders, options) : null,
99
- handleApi: typeof handleApiRoute === "function" ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx) : null,
105
+ handleApi: typeof handleApiRoute === "function" ? (req, apiUrl) => handleApiRoute(req, apiUrl, ctx, new URL(req.url).origin, "worker") : null,
100
106
  serveFilesystemRoute: async (requestPathname, _stagedHeaders, phase) => {
101
107
  if (!env?.ASSETS) return false;
102
108
  return fetchWorkerFilesystemRoute(request, requestPathname, phase, (assetRequest) => Promise.resolve(env.ASSETS.fetch(assetRequest)));
@@ -8,6 +8,7 @@ type PrerenderManifestRoute = {
8
8
  router?: string;
9
9
  fallback?: boolean;
10
10
  headers?: Record<string, string>;
11
+ tags?: string[];
11
12
  };
12
13
  type PrerenderManifest = {
13
14
  buildId?: string;
@@ -1,3 +1,4 @@
1
+ import { NextI18nConfig } from "../config/next-config.js";
1
2
  import { negotiateEncoding } from "./accept-encoding.js";
2
3
  import { StaticFileCache } from "./static-file-cache.js";
3
4
  import { resolveRequestHost, trustProxy, trustedHosts } from "./proxy-trust.js";
@@ -103,7 +104,7 @@ declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clien
103
104
  * When `urlOverride` is provided, it is used as the path + query string
104
105
  * instead of `req.url`.
105
106
  */
106
- declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string): Request;
107
+ declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string, i18nConfig?: NextI18nConfig | null, authorizeOnDemandRevalidate?: (headerValue: string | null) => boolean): Request;
107
108
  /**
108
109
  * Stream a Web Response back to a Node.js ServerResponse.
109
110
  * Supports streaming compression for SSR responses.
@@ -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,33 @@ async function startProdServer(options = {}) {
660
664
  silent
661
665
  });
662
666
  }
663
- function createNodeExecutionContext() {
667
+ function createNodeExecutionContext(trustedRevalidateOrigin) {
664
668
  return {
669
+ hostRuntime: "node",
665
670
  waitUntil(promise) {
666
671
  Promise.resolve(promise).catch(() => {});
667
672
  },
668
- passThroughOnException() {}
673
+ passThroughOnException() {},
674
+ trustedRevalidateOrigin
669
675
  };
670
676
  }
677
+ function resolveTrustedNodeRevalidateOrigin(req, configuredHost, configuredPort) {
678
+ const port = req.socket.localPort ?? configuredPort;
679
+ return `http://${normalizeInternalFetchHost(configuredHost)}:${port}`;
680
+ }
681
+ function normalizeInternalFetchHost(host) {
682
+ if (!host || host === "0.0.0.0" || host === "::") return "localhost";
683
+ if (host.includes(":") && !host.startsWith("[")) return `[${host}]`;
684
+ return host;
685
+ }
671
686
  function resolveAppRouterHandler(entry) {
672
- if (typeof entry === "function") return (request) => Promise.resolve(entry(request));
687
+ if (typeof entry === "function") {
688
+ const handler = entry;
689
+ return (request, ctx) => Promise.resolve(handler(request, ctx));
690
+ }
673
691
  if (entry && typeof entry === "object" && "fetch" in entry) {
674
692
  const workerEntry = entry;
675
- if (typeof workerEntry.fetch === "function") return (request) => Promise.resolve(workerEntry.fetch(request, void 0, createNodeExecutionContext()));
693
+ if (typeof workerEntry.fetch === "function") return (request, ctx) => Promise.resolve(workerEntry.fetch(request, void 0, ctx));
676
694
  }
677
695
  console.error("[vinext] App Router entry must export either a default handler function or a Worker-style default export with fetch()");
678
696
  process.exit(1);
@@ -793,6 +811,8 @@ async function startAppRouterServer(options) {
793
811
  const appRouterBasePath = typeof rscModule.__basePath === "string" ? rscModule.__basePath : "";
794
812
  const appRouterInlineCss = rscModule.__inlineCss === true;
795
813
  const appRouterHasPagesDir = rscModule.__hasPagesDir === true;
814
+ const appRouterI18nConfig = rscModule.__i18nConfig ?? null;
815
+ const appRouterAuthorizeOnDemandRevalidate = typeof rscModule.authorizeOnDemandRevalidate === "function" ? rscModule.authorizeOnDemandRevalidate : void 0;
796
816
  const appImageAllowedWidths = Array.isArray(rscModule.__imageAllowedWidths) ? rscModule.__imageAllowedWidths : [...DEFAULT_DEVICE_SIZES, ...DEFAULT_IMAGE_SIZES];
797
817
  let imageConfig = typeof rscModule.__imageConfig === "object" && rscModule.__imageConfig !== null ? rscModule.__imageConfig : void 0;
798
818
  if (imageConfig === void 0) {
@@ -870,8 +890,8 @@ async function startAppRouterServer(options) {
870
890
  return;
871
891
  }
872
892
  try {
873
- const request = nodeToWebRequest(req, rawUrl, prerenderSecret);
874
- const response = await rscHandler(request);
893
+ const request = nodeToWebRequest(req, rawUrl, prerenderSecret, appRouterI18nConfig, appRouterAuthorizeOnDemandRevalidate);
894
+ const response = await rscHandler(request, createNodeExecutionContext(resolveTrustedNodeRevalidateOrigin(req, host, port)));
875
895
  if (missingBuildAsset && response.status === 404) {
876
896
  cancelResponseBody(response);
877
897
  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
@@ -935,14 +955,16 @@ function readPagesServerEntryPageRoutes(value) {
935
955
  *
936
956
  * Uses the server entry (dist/server/entry.js) which exports:
937
957
  * - 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)
958
+ * - handleApiRoute(request, url, ctx?, trustedRevalidateOrigin?) — API route handling
959
+ * (ctx optional; pass for ctx.waitUntil() on Workers)
960
+ * - authorizeOnDemandRevalidate(header) — validates against the bundled build secret
939
961
  * - runMiddleware(request, ctx?) — middleware execution (ctx optional; pass for ctx.waitUntil() on Workers)
940
962
  * - vinextConfig — embedded next.config.js settings
941
963
  */
942
964
  async function startPagesRouterServer(options) {
943
965
  const { port, host, clientDir, serverEntryPath, compress, purpose, silent } = options;
944
966
  const serverEntry = await importServerEntryModule(serverEntryPath);
945
- const { renderPage, handleApiRoute: handleApi, runMiddleware, vinextConfig, buildId: pagesBuildId } = serverEntry;
967
+ const { renderPage, handleApiRoute: handleApi, authorizeOnDemandRevalidate, runMiddleware, vinextConfig, buildId: pagesBuildId } = serverEntry;
946
968
  const matchPageRoute = typeof serverEntry.matchPageRoute === "function" ? serverEntry.matchPageRoute : void 0;
947
969
  const hasMiddleware = serverEntry.hasMiddleware === true;
948
970
  const pageRoutes = readPagesServerEntryPageRoutes(serverEntry.pageRoutes);
@@ -1095,11 +1117,13 @@ async function startPagesRouterServer(options) {
1095
1117
  const protocol = resolveRequestProtocol(req);
1096
1118
  const hostHeader = resolveRequestHost(req, `${host}:${port}`);
1097
1119
  const rawReqHeaders = nodeHeadersToWebHeaders(req.headers);
1120
+ const revalidationHostname = readTrustedRevalidationHostname(rawReqHeaders, i18nConfig, typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0);
1098
1121
  const isDataRequest = isDataReq;
1099
1122
  const reqHeaders = filterInternalHeaders(rawReqHeaders);
1123
+ if (revalidationHostname) reqHeaders.set("host", revalidationHostname);
1100
1124
  const method = req.method ?? "GET";
1101
1125
  const hasBody = method !== "GET" && method !== "HEAD";
1102
- const result = await runPagesRequest(new Request(`${protocol}://${hostHeader}${url}`, {
1126
+ const result = await runPagesRequest(new Request(`${protocol}://${revalidationHostname ?? hostHeader}${url}`, {
1103
1127
  method,
1104
1128
  headers: reqHeaders,
1105
1129
  body: hasBody ? readNodeStream(req) : void 0,
@@ -1117,6 +1141,7 @@ async function startPagesRouterServer(options) {
1117
1141
  hasMiddleware,
1118
1142
  ctx: void 0,
1119
1143
  rawSearch: rawQs,
1144
+ authorizeOnDemandRevalidate: typeof authorizeOnDemandRevalidate === "function" ? authorizeOnDemandRevalidate : void 0,
1120
1145
  configMatchPathname,
1121
1146
  matchPageRoute: matchPageRoute ?? null,
1122
1147
  runMiddleware: typeof runMiddleware === "function" ? wrapMiddlewareWithBasePath(runMiddleware, basePath, hadBasePath) : null,
@@ -1124,7 +1149,7 @@ async function startPagesRouterServer(options) {
1124
1149
  ...options,
1125
1150
  originalUrl: originalRenderUrl
1126
1151
  }) : null,
1127
- handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext()) : null,
1152
+ handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node") : null,
1128
1153
  serveFilesystemRoute: async (requestPathname, stagedHeaders, phase) => {
1129
1154
  if (req.method !== "GET" && req.method !== "HEAD" || requestPathname === "/" || requestPathname === "/api" || requestPathname.startsWith("/api/") || phase === "direct" && requestPathname.startsWith(`/_next/static/`)) return false;
1130
1155
  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 };
@@ -66,7 +66,7 @@ async function seedMemoryCacheFromPrerender(serverDir, options) {
66
66
  const rscKey = options?.buildAppPageRscKey?.(cachePathname) ?? baseKey + ":rsc";
67
67
  const revalidateSeconds = typeof route.revalidate === "number" ? route.revalidate : void 0;
68
68
  const expireSeconds = typeof route.expire === "number" ? route.expire : void 0;
69
- const tags = buildAppPageCacheTags(cachePathname, []);
69
+ const tags = buildAppPageCacheTags(cachePathname, route.tags ?? []);
70
70
  if (await seedHtml(writeAppPageEntry, prerenderDir, htmlKey, artifactPathname, trailingSlash, route.headers, revalidateSeconds, expireSeconds, tags)) {
71
71
  await seedRsc(writeAppPageEntry, prerenderDir, rscKey, artifactPathname, revalidateSeconds, expireSeconds, tags);
72
72
  seeded++;
@@ -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,30 @@
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
+ hostRuntime: "worker",
12
+ ...base?.cache === void 0 ? {} : { cache: base.cache },
13
+ ...base?.trustedRevalidateOrigin === void 0 ? {} : { trustedRevalidateOrigin: base.trustedRevalidateOrigin },
14
+ dispatchPagesRevalidate,
15
+ isInternalPagesRevalidation
16
+ };
17
+ }
18
+ /**
19
+ * Add a request-local, in-process Pages revalidation dispatcher to a Worker
20
+ * execution context. Re-entering with the derived internal context preserves
21
+ * the authenticated protocol headers while keeping ordinary inbound requests
22
+ * on the normal header-scrubbing path.
23
+ */
24
+ function createWorkerRevalidationContext(base, handleInternalRequest) {
25
+ if (typeof base?.dispatchPagesRevalidate === "function") return base;
26
+ const dispatchPagesRevalidate = (request) => handleInternalRequest(request, deriveExecutionContext(base, dispatchPagesRevalidate, true));
27
+ return deriveExecutionContext(base, dispatchPagesRevalidate, false);
28
+ }
29
+ //#endregion
30
+ export { createWorkerRevalidationContext };
@@ -9,8 +9,9 @@ type AppRouterScrollIntent = Readonly<{
9
9
  declare function beginAppRouterScrollIntent(hash: string | null): AppRouterScrollIntent;
10
10
  declare function clearAppRouterScrollIntent(): void;
11
11
  declare function getPendingAppRouterScrollIntent(): AppRouterScrollIntent | null;
12
+ declare function isLatestAppRouterScrollIntent(expected: AppRouterScrollIntent | null | undefined): boolean;
12
13
  declare function claimAppRouterScrollIntentForCommit(expected: AppRouterScrollIntent | null | undefined, commitId: number): void;
13
14
  declare function markAppRouterScrollIntentHeadHoisted(expected: AppRouterScrollIntent | null | undefined, commitId: number): void;
14
15
  declare function consumeAppRouterScrollIntent(expected: AppRouterScrollIntent | null | undefined, commitId?: number): AppRouterScrollIntent | null;
15
16
  //#endregion
16
- export { AppRouterScrollIntent, beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
17
+ export { AppRouterScrollIntent, beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, isLatestAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
@@ -22,11 +22,16 @@ function beginAppRouterScrollIntent(hash) {
22
22
  return intent;
23
23
  }
24
24
  function clearAppRouterScrollIntent() {
25
- getScrollIntentStore().pending = null;
25
+ const store = getScrollIntentStore();
26
+ store.nextId += 1;
27
+ store.pending = null;
26
28
  }
27
29
  function getPendingAppRouterScrollIntent() {
28
30
  return getScrollIntentStore().pending;
29
31
  }
32
+ function isLatestAppRouterScrollIntent(expected) {
33
+ return expected !== null && expected !== void 0 && getScrollIntentStore().nextId === expected.id;
34
+ }
30
35
  function claimAppRouterScrollIntentForCommit(expected, commitId) {
31
36
  const store = getScrollIntentStore();
32
37
  const intent = store.pending;
@@ -59,4 +64,4 @@ function consumeAppRouterScrollIntent(expected, commitId) {
59
64
  return intent;
60
65
  }
61
66
  //#endregion
62
- export { beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
67
+ export { beginAppRouterScrollIntent, claimAppRouterScrollIntentForCommit, clearAppRouterScrollIntent, consumeAppRouterScrollIntent, getPendingAppRouterScrollIntent, isLatestAppRouterScrollIntent, markAppRouterScrollIntentHeadHoisted };
@@ -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
@@ -8,6 +8,12 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  * custom Pages Router documents. Vinext's renderer replaces the Main and
9
9
  * NextScript placeholders with the rendered page and hydration scripts.
10
10
  */
11
+ const documentAssetMarkerAttributes = {
12
+ headNonce: "data-vinext-head-nonce",
13
+ headCrossOrigin: "data-vinext-head-cross-origin",
14
+ scriptNonce: "data-vinext-script-nonce",
15
+ scriptCrossOrigin: "data-vinext-script-cross-origin"
16
+ };
11
17
  const HtmlContext = React.createContext(void 0);
12
18
  function Html(props) {
13
19
  return /* @__PURE__ */ jsx("html", { ...props });
@@ -39,9 +45,11 @@ var Head = class extends React.Component {
39
45
  return [];
40
46
  }
41
47
  render() {
42
- const { children, ...props } = this.props;
48
+ const { children, nonce, crossOrigin, ...props } = this.props;
43
49
  return /* @__PURE__ */ jsx("head", {
44
50
  ...props,
51
+ ...nonce ? { [documentAssetMarkerAttributes.headNonce]: nonce } : {},
52
+ ...crossOrigin ? { [documentAssetMarkerAttributes.headCrossOrigin]: crossOrigin } : {},
45
53
  children
46
54
  });
47
55
  }
@@ -70,7 +78,12 @@ var NextScript = class extends React.Component {
70
78
  return JSON.stringify(context.__NEXT_DATA__);
71
79
  }
72
80
  render() {
73
- return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: "<!-- __NEXT_SCRIPTS__ -->" } });
81
+ const { nonce, crossOrigin } = this.props;
82
+ return /* @__PURE__ */ jsx("span", {
83
+ ...nonce ? { [documentAssetMarkerAttributes.scriptNonce]: nonce } : {},
84
+ ...crossOrigin ? { [documentAssetMarkerAttributes.scriptCrossOrigin]: crossOrigin } : {},
85
+ dangerouslySetInnerHTML: { __html: "<!-- __NEXT_SCRIPTS__ -->" }
86
+ });
74
87
  }
75
88
  };
76
89
  var Document = class extends React.Component {
@@ -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.