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
@@ -3,6 +3,7 @@ import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation }
3
3
  import { getRequestExecutionContext } from "./request-context.js";
4
4
  import { getDataCacheHandler } from "./cache-handler.js";
5
5
  import { markDynamicUsage } from "./headers.js";
6
+ import { _setRequestScopedCacheLife } from "./cache-request-state.js";
6
7
  import { encodeCacheTags } from "../utils/encode-cache-tag.js";
7
8
  //#region src/shims/fetch-cache.ts
8
9
  /**
@@ -296,6 +297,7 @@ const _fallbackState = _g[_FALLBACK_KEY] ??= {
296
297
  currentFetchCacheMode: null,
297
298
  currentForceDynamicFetchDefault: false,
298
299
  dynamicFetchUrls: /* @__PURE__ */ new Set(),
300
+ refreshStaleFetchesInForeground: false,
299
301
  isFetchDedupeActive: false,
300
302
  currentFetchDedupeEntries: /* @__PURE__ */ new Map()
301
303
  };
@@ -314,6 +316,7 @@ function _resetFallbackState(isFetchDedupeActive) {
314
316
  _fallbackState.currentFetchCacheMode = null;
315
317
  _fallbackState.currentForceDynamicFetchDefault = false;
316
318
  _fallbackState.dynamicFetchUrls = /* @__PURE__ */ new Set();
319
+ _fallbackState.refreshStaleFetchesInForeground = false;
317
320
  _fallbackState.isFetchDedupeActive = isFetchDedupeActive;
318
321
  _fallbackState.currentFetchDedupeEntries = /* @__PURE__ */ new Map();
319
322
  }
@@ -330,6 +333,56 @@ function markUncachedFetchForPageOutput(input) {
330
333
  function recordCacheableFetchObservation(input) {
331
334
  _getState().cacheableFetchUrls.add(getFetchObservationUrl(input));
332
335
  }
336
+ function recordFiniteFetchRevalidate(revalidateSeconds) {
337
+ if (Number.isFinite(revalidateSeconds) && revalidateSeconds > 0) _setRequestScopedCacheLife({ revalidate: revalidateSeconds });
338
+ }
339
+ function shouldRefreshStaleFetchInForeground() {
340
+ return _getState().refreshStaleFetchesInForeground;
341
+ }
342
+ async function buildFetchCacheValue(response, tags, revalidateSeconds, options) {
343
+ if (response.status !== 200) return null;
344
+ const responseForCache = options?.cloneForReturn === false ? response : response.clone();
345
+ const body = await responseForCache.text();
346
+ const headers = {};
347
+ responseForCache.headers.forEach((v, k) => {
348
+ if (k.toLowerCase() === "set-cookie") return;
349
+ headers[k] = v;
350
+ });
351
+ return {
352
+ kind: "FETCH",
353
+ data: {
354
+ headers,
355
+ body,
356
+ url: response.url,
357
+ status: responseForCache.status
358
+ },
359
+ tags,
360
+ revalidate: revalidateSeconds
361
+ };
362
+ }
363
+ async function writeFetchCacheResponse(handler, cacheKey, response, tags, revalidateSeconds, options) {
364
+ const cacheValue = await buildFetchCacheValue(response, tags, revalidateSeconds, options);
365
+ if (!cacheValue) return;
366
+ await handler.set(cacheKey, cacheValue, {
367
+ fetchCache: true,
368
+ tags,
369
+ revalidate: revalidateSeconds
370
+ });
371
+ }
372
+ async function lowerFetchCacheRevalidateIfNeeded(handler, cacheKey, cachedValue, tags, revalidateSeconds) {
373
+ if (!Number.isFinite(revalidateSeconds) || revalidateSeconds <= 0 || typeof cachedValue.revalidate !== "number" || cachedValue.revalidate <= revalidateSeconds) return;
374
+ const mergedTags = Array.from(new Set([...cachedValue.tags ?? [], ...tags]));
375
+ const updatedValue = {
376
+ ...cachedValue,
377
+ tags: mergedTags,
378
+ revalidate: revalidateSeconds
379
+ };
380
+ await handler.set(cacheKey, updatedValue, {
381
+ fetchCache: true,
382
+ tags: mergedTags,
383
+ revalidate: revalidateSeconds
384
+ });
385
+ }
333
386
  function peekCacheableFetchObservations() {
334
387
  return [..._getState().cacheableFetchUrls].sort();
335
388
  }
@@ -393,6 +446,9 @@ function setCurrentFetchCacheMode(mode) {
393
446
  function setCurrentForceDynamicFetchDefault(enabled) {
394
447
  _getState().currentForceDynamicFetchDefault = enabled;
395
448
  }
449
+ function setRefreshStaleFetchesInForeground(enabled) {
450
+ _getState().refreshStaleFetchesInForeground = enabled;
451
+ }
396
452
  function isNoStoreFetch(cacheDirective, nextOpts) {
397
453
  return cacheDirective === "no-store" || cacheDirective === "no-cache" || nextOpts?.revalidate === 0;
398
454
  }
@@ -562,6 +618,7 @@ function createPatchedFetch() {
562
618
  return dedupeFetch(input, cleanInit);
563
619
  }
564
620
  recordCacheableFetchObservation(input);
621
+ recordFiniteFetchRevalidate(revalidateSeconds);
565
622
  const reqTags = _getState().currentRequestTags;
566
623
  const tags = encodeCacheTags(nextOpts?.tags ?? []);
567
624
  if (tags.length > 0) {
@@ -586,39 +643,24 @@ function createPatchedFetch() {
586
643
  const cached = await handler.get(cacheKey, {
587
644
  kind: "FETCH",
588
645
  tags,
589
- softTags
646
+ softTags,
647
+ revalidate: revalidateSeconds
590
648
  });
591
649
  if (cached?.value && cached.value.kind === "FETCH" && cached.cacheState !== "stale") {
650
+ await lowerFetchCacheRevalidateIfNeeded(handler, cacheKey, cached.value, tags, revalidateSeconds);
592
651
  const cachedData = cached.value.data;
593
652
  return buildCachedFetchResponse(cachedData, input);
594
653
  }
595
654
  if (cached?.value && cached.value.kind === "FETCH" && cached.cacheState === "stale") {
655
+ if (shouldRefreshStaleFetchInForeground()) {
656
+ const freshResponse = await dedupeFetch(input, fetchInit);
657
+ await writeFetchCacheResponse(handler, cacheKey, freshResponse, tags, revalidateSeconds);
658
+ return freshResponse;
659
+ }
596
660
  const staleData = cached.value.data;
597
661
  if (!pendingRefetches.has(cacheKey)) {
598
662
  const refetchPromise = originalFetch(input, fetchInit).then(async (freshResp) => {
599
- if (freshResp.status !== 200) return;
600
- const freshBody = await freshResp.text();
601
- const freshHeaders = {};
602
- freshResp.headers.forEach((v, k) => {
603
- if (k.toLowerCase() === "set-cookie") return;
604
- freshHeaders[k] = v;
605
- });
606
- const freshValue = {
607
- kind: "FETCH",
608
- data: {
609
- headers: freshHeaders,
610
- body: freshBody,
611
- url: freshResp.url,
612
- status: freshResp.status
613
- },
614
- tags,
615
- revalidate: revalidateSeconds
616
- };
617
- await handler.set(cacheKey, freshValue, {
618
- fetchCache: true,
619
- tags,
620
- revalidate: revalidateSeconds
621
- });
663
+ await writeFetchCacheResponse(handler, cacheKey, freshResp, tags, revalidateSeconds, { cloneForReturn: false });
622
664
  }).catch((err) => {
623
665
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
624
666
  console.error(`[vinext] fetch cache background revalidation failed for ${url} (key=${cacheKey.slice(0, 12)}...):`, err);
@@ -638,33 +680,14 @@ function createPatchedFetch() {
638
680
  console.error("[vinext] fetch cache read error:", cacheErr);
639
681
  }
640
682
  const response = await dedupeFetch(input, fetchInit);
641
- if (response.status === 200) {
642
- const cloned = response.clone();
643
- const body = await cloned.text();
644
- const headers = {};
645
- cloned.headers.forEach((v, k) => {
646
- if (k.toLowerCase() === "set-cookie") return;
647
- headers[k] = v;
648
- });
649
- const cacheValue = {
650
- kind: "FETCH",
651
- data: {
652
- headers,
653
- body,
654
- url: response.url,
655
- status: cloned.status
656
- },
657
- tags,
658
- revalidate: revalidateSeconds
659
- };
660
- handler.set(cacheKey, cacheValue, {
661
- fetchCache: true,
662
- tags,
663
- revalidate: revalidateSeconds
664
- }).catch((err) => {
665
- console.error("[vinext] fetch cache write error:", err);
666
- });
667
- }
683
+ const cacheValue = await buildFetchCacheValue(response, tags, revalidateSeconds);
684
+ if (cacheValue) handler.set(cacheKey, cacheValue, {
685
+ fetchCache: true,
686
+ tags,
687
+ revalidate: revalidateSeconds
688
+ }).catch((err) => {
689
+ console.error("[vinext] fetch cache write error:", err);
690
+ });
668
691
  return response;
669
692
  };
670
693
  }
@@ -716,6 +739,7 @@ async function runWithFetchCache(fn) {
716
739
  uCtx.currentRequestTags = [];
717
740
  uCtx.currentFetchSoftTags = [];
718
741
  uCtx.dynamicFetchUrls = /* @__PURE__ */ new Set();
742
+ uCtx.refreshStaleFetchesInForeground = false;
719
743
  uCtx.isFetchDedupeActive = true;
720
744
  uCtx.currentFetchDedupeEntries = /* @__PURE__ */ new Map();
721
745
  }, fn);
@@ -726,6 +750,7 @@ async function runWithFetchCache(fn) {
726
750
  currentFetchCacheMode: null,
727
751
  currentForceDynamicFetchDefault: false,
728
752
  dynamicFetchUrls: /* @__PURE__ */ new Set(),
753
+ refreshStaleFetchesInForeground: false,
729
754
  isFetchDedupeActive: true,
730
755
  currentFetchDedupeEntries: /* @__PURE__ */ new Map()
731
756
  }, fn);
@@ -763,4 +788,4 @@ function getOriginalFetch() {
763
788
  return originalFetch;
764
789
  }
765
790
  //#endregion
766
- export { _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, setCurrentForceDynamicFetchDefault, withFetchCache };
791
+ export { _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, setCurrentForceDynamicFetchDefault, setRefreshStaleFetchesInForeground, withFetchCache };
@@ -16,6 +16,6 @@ declare const Form: import("react").ForwardRefExoticComponent<{
16
16
  * In pages dir, prefetch is not supported and the prop has no effect.
17
17
  */
18
18
  prefetch?: false | null;
19
- } & Omit<FormHTMLAttributes<HTMLFormElement>, "method" | "encType" | "target"> & import("react").RefAttributes<HTMLFormElement>>;
19
+ } & Omit<FormHTMLAttributes<HTMLFormElement>, "target" | "method" | "encType"> & import("react").RefAttributes<HTMLFormElement>>;
20
20
  //#endregion
21
21
  export { createFormSubmitDestinationUrl, Form as default, useActionState };
@@ -0,0 +1,10 @@
1
+ //#region src/shims/internal/app-prefetch-fetch-queue.d.ts
2
+ declare function releaseAppPrefetchFetchSlot(response: Response): void;
3
+ /**
4
+ * Low-priority App Router prefetches share a small request queue. The consumer
5
+ * must either snapshot the returned Response with snapshotRscResponse() or call
6
+ * releaseAppPrefetchFetchSlot() when it drops the response without consuming it.
7
+ */
8
+ declare function scheduleAppPrefetchFetch(fetcher: () => Promise<Response>, priority: "low" | "high"): Promise<Response>;
9
+ //#endregion
10
+ export { releaseAppPrefetchFetchSlot, scheduleAppPrefetchFetch };
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ //#region src/shims/internal/app-prefetch-fetch-queue.ts
3
+ const APP_PREFETCH_FETCH_SLOT_RELEASE_KEY = Symbol.for("vinext.appPrefetchFetchSlotRelease");
4
+ const MAX_DEFAULT_APP_PREFETCH_REQUESTS = 4;
5
+ const defaultAppPrefetchQueue = [];
6
+ let activeDefaultAppPrefetchRequests = 0;
7
+ let defaultAppPrefetchDrainScheduled = false;
8
+ function drainDefaultAppPrefetchQueue() {
9
+ defaultAppPrefetchDrainScheduled = false;
10
+ while (activeDefaultAppPrefetchRequests < MAX_DEFAULT_APP_PREFETCH_REQUESTS) {
11
+ const run = defaultAppPrefetchQueue.shift();
12
+ if (!run) return;
13
+ activeDefaultAppPrefetchRequests += 1;
14
+ run();
15
+ }
16
+ }
17
+ function scheduleDefaultAppPrefetchDrain() {
18
+ if (defaultAppPrefetchDrainScheduled) return;
19
+ defaultAppPrefetchDrainScheduled = true;
20
+ queueMicrotask(drainDefaultAppPrefetchQueue);
21
+ }
22
+ function releaseAppPrefetchFetchSlot(response) {
23
+ const release = response[APP_PREFETCH_FETCH_SLOT_RELEASE_KEY];
24
+ if (release === void 0) return;
25
+ response[APP_PREFETCH_FETCH_SLOT_RELEASE_KEY] = void 0;
26
+ release();
27
+ }
28
+ /**
29
+ * Low-priority App Router prefetches share a small request queue. The consumer
30
+ * must either snapshot the returned Response with snapshotRscResponse() or call
31
+ * releaseAppPrefetchFetchSlot() when it drops the response without consuming it.
32
+ */
33
+ function scheduleAppPrefetchFetch(fetcher, priority) {
34
+ if (priority === "high") return fetcher();
35
+ return new Promise((resolve, reject) => {
36
+ defaultAppPrefetchQueue.push(() => {
37
+ let didRelease = false;
38
+ const release = () => {
39
+ if (didRelease) return;
40
+ didRelease = true;
41
+ activeDefaultAppPrefetchRequests -= 1;
42
+ drainDefaultAppPrefetchQueue();
43
+ };
44
+ try {
45
+ fetcher().then((response) => {
46
+ response[APP_PREFETCH_FETCH_SLOT_RELEASE_KEY] = release;
47
+ resolve(response);
48
+ }, (error) => {
49
+ release();
50
+ reject(error);
51
+ });
52
+ } catch (error) {
53
+ release();
54
+ reject(error);
55
+ }
56
+ });
57
+ scheduleDefaultAppPrefetchDrain();
58
+ });
59
+ }
60
+ //#endregion
61
+ export { releaseAppPrefetchFetchSlot, scheduleAppPrefetchFetch };
@@ -1,6 +1,7 @@
1
1
  import { HybridClientOwner } from "./hybrid-client-route-owner-direct.js";
2
2
 
3
3
  //#region src/shims/internal/hybrid-client-route-owner.d.ts
4
+ declare function resolveHybridClientRewriteHref(href: string, basePath: string): string | null;
4
5
  /**
5
6
  * Decide which router should own a soft-navigated URL. Returns:
6
7
  * - "app" → the App Router runtime handles the navigation (RSC fetch).
@@ -15,4 +16,4 @@ import { HybridClientOwner } from "./hybrid-client-route-owner-direct.js";
15
16
  */
16
17
  declare function resolveHybridClientRouteOwner(href: string, basePath: string): HybridClientOwner | null;
17
18
  //#endregion
18
- export { type HybridClientOwner, resolveHybridClientRouteOwner };
19
+ export { type HybridClientOwner, resolveHybridClientRewriteHref, resolveHybridClientRouteOwner };
@@ -49,6 +49,39 @@ function resolveClientRewrite(href, basePath, rewrites, continueAfterMatch = fal
49
49
  kind: "rewrite"
50
50
  } : null;
51
51
  }
52
+ function resolveHybridClientRewriteHref(href, basePath) {
53
+ if (typeof window === "undefined") return null;
54
+ const rewrites = window.__VINEXT_CLIENT_REWRITES__;
55
+ if (!rewrites) return null;
56
+ let currentHref = href;
57
+ let didRewrite = false;
58
+ const beforeFilesRewrite = resolveClientRewrite(currentHref, basePath, rewrites.beforeFiles, true);
59
+ if (beforeFilesRewrite?.kind === "document") return null;
60
+ if (beforeFilesRewrite?.kind === "rewrite") {
61
+ currentHref = beforeFilesRewrite.href;
62
+ didRewrite = true;
63
+ }
64
+ let matches = matchDirectHybridClientRoutes(currentHref, basePath);
65
+ if ((matches.appMatch === null || matches.appMatch.isDynamic) && (matches.pagesMatch === null || matches.pagesMatch.isDynamic)) for (const rewrite of rewrites.afterFiles) {
66
+ const afterFilesRewrite = resolveClientRewrite(currentHref, basePath, [rewrite]);
67
+ if (afterFilesRewrite?.kind === "document") return didRewrite ? currentHref : null;
68
+ if (afterFilesRewrite?.kind !== "rewrite") continue;
69
+ currentHref = afterFilesRewrite.href;
70
+ didRewrite = true;
71
+ matches = matchDirectHybridClientRoutes(currentHref, basePath);
72
+ if (matches.appMatch || matches.pagesMatch) break;
73
+ }
74
+ if (matches.appMatch === null && matches.pagesMatch === null) for (const rewrite of rewrites.fallback) {
75
+ const fallbackRewrite = resolveClientRewrite(currentHref, basePath, [rewrite]);
76
+ if (fallbackRewrite?.kind === "document") return didRewrite ? currentHref : null;
77
+ if (fallbackRewrite?.kind !== "rewrite") continue;
78
+ currentHref = fallbackRewrite.href;
79
+ didRewrite = true;
80
+ matches = matchDirectHybridClientRoutes(currentHref, basePath);
81
+ if (matches.appMatch || matches.pagesMatch) break;
82
+ }
83
+ return didRewrite ? currentHref : null;
84
+ }
52
85
  /**
53
86
  * Decide which router should own a soft-navigated URL. Returns:
54
87
  * - "app" → the App Router runtime handles the navigation (RSC fetch).
@@ -89,4 +122,4 @@ function resolveHybridClientRouteOwner(href, basePath) {
89
122
  return resolveMatchedHybridClientRouteOwner(matches);
90
123
  }
91
124
  //#endregion
92
- export { resolveHybridClientRouteOwner };
125
+ export { resolveHybridClientRewriteHref, resolveHybridClientRouteOwner };
@@ -35,6 +35,7 @@
35
35
  declare function getPagesStaticDataCache(): Record<string, Promise<Response>>;
36
36
  declare function fetchCachedPagesData(dataHref: string, init?: RequestInit): Promise<Response>;
37
37
  declare function fetchStaticPagesData(dataHref: string, init?: RequestInit): Promise<Response>;
38
+ declare function fetchUncachedPagesData(dataHref: string, init?: RequestInit): Promise<Response>;
38
39
  declare function evictPagesDataCache(dataHref: string): void;
39
40
  /**
40
41
  * Dedupe a `fetch()` against the `_next/data` endpoint. Multiple concurrent
@@ -55,4 +56,4 @@ declare function dedupedPagesDataFetch(dataHref: string, init?: RequestInit): Pr
55
56
  */
56
57
  declare function clearPagesDataInflight(): void;
57
58
  //#endregion
58
- export { clearPagesDataInflight, dedupedPagesDataFetch, evictPagesDataCache, fetchCachedPagesData, fetchStaticPagesData, getPagesStaticDataCache };
59
+ export { clearPagesDataInflight, dedupedPagesDataFetch, evictPagesDataCache, fetchCachedPagesData, fetchStaticPagesData, fetchUncachedPagesData, getPagesStaticDataCache };
@@ -1,3 +1,4 @@
1
+ import "../../utils/protocol-headers.js";
1
2
  import { getDeploymentId } from "../../utils/deployment-id.js";
2
3
  //#region src/shims/internal/pages-data-fetch-dedup.ts
3
4
  /**
@@ -71,7 +72,7 @@ function fetchCachedPagesData(dataHref, init) {
71
72
  cached = dedupedPagesDataFetch(dataHref, sharedInit).then((response) => {
72
73
  const expectedDeploymentId = getDeploymentId() ?? null;
73
74
  const responseDeploymentId = response.headers.get("x-nextjs-deployment-id");
74
- if (!response.ok || response.headers.get("x-middleware-cache") === "no-cache" || responseDeploymentId !== null && responseDeploymentId !== expectedDeploymentId) {
75
+ if (!response.ok || response.headers.get("x-middleware-cache") === "no-cache" || response.headers.get("x-middleware-skip") !== null || responseDeploymentId !== null && responseDeploymentId !== expectedDeploymentId) {
75
76
  delete staticDataCache[key];
76
77
  staticDataSources.delete(key);
77
78
  }
@@ -91,6 +92,16 @@ function fetchCachedPagesData(dataHref, init) {
91
92
  function fetchStaticPagesData(dataHref, init) {
92
93
  return fetchCachedPagesData(dataHref, init);
93
94
  }
95
+ function fetchUncachedPagesData(dataHref, init) {
96
+ return fetch(dataHref, init).then(async (response) => {
97
+ const body = await response.arrayBuffer();
98
+ return new Response(body, {
99
+ headers: response.headers,
100
+ status: response.status,
101
+ statusText: response.statusText
102
+ });
103
+ });
104
+ }
94
105
  function evictPagesDataCache(dataHref) {
95
106
  const key = getStaticDataKey(dataHref);
96
107
  delete staticDataCache[key];
@@ -177,4 +188,4 @@ function clearPagesDataInflight() {
177
188
  for (const key of Object.keys(staticDataCache)) delete staticDataCache[key];
178
189
  }
179
190
  //#endregion
180
- export { clearPagesDataInflight, dedupedPagesDataFetch, evictPagesDataCache, fetchCachedPagesData, fetchStaticPagesData, getPagesStaticDataCache };
191
+ export { clearPagesDataInflight, dedupedPagesDataFetch, evictPagesDataCache, fetchCachedPagesData, fetchStaticPagesData, fetchUncachedPagesData, getPagesStaticDataCache };
@@ -1,6 +1,7 @@
1
1
  //#region src/shims/internal/pages-data-target.d.ts
2
2
  type PagesDataTarget = {
3
- /** Final fetch URL for the data endpoint, including basePath and search. */dataHref: string; /** Matched route pattern (e.g. `/blog/[slug]`). */
3
+ /** Final fetch URL for the data endpoint, including basePath and search. */dataHref: string; /** Locale-qualified data endpoint used by Pages Router prefetch. */
4
+ prefetchDataHref?: string; /** Matched route pattern (e.g. `/blog/[slug]`). */
4
5
  pattern: string; /** Dynamic params extracted from the URL by the pattern matcher. */
5
6
  params: Record<string, string | string[]>; /** Code-split loader thunk for the matched route's page module. */
6
7
  loader: () => Promise<{
@@ -11,7 +12,8 @@ type PagesDataTarget = {
11
12
  middlewareDataHref?: string; /** Current buildId snapshot, used by the data URL and consistency checks. */
12
13
  buildId: string; /** Locale-prefixed (server-routable) page path. */
13
14
  pagePath: string; /** URL search string including the leading `?`. */
14
- search: string;
15
+ search: string; /** Locale selected for middleware-prefetch data URLs when the visible URL is unprefixed. */
16
+ prefetchLocale?: string;
15
17
  /**
16
18
  * Locale prefix detected on the URL, or `undefined` when the URL is
17
19
  * unprefixed (default locale, or no i18n configured). Lets the caller refresh
@@ -20,6 +22,9 @@ type PagesDataTarget = {
20
22
  */
21
23
  locale: string | undefined;
22
24
  };
25
+ type PagesDataNavigationTargetOptions = {
26
+ locale?: string | false;
27
+ };
23
28
  declare function getPagesMiddlewareDataHref(browserUrl: string, basePath: string): string | null;
24
29
  /**
25
30
  * Decide whether the JSON data-endpoint navigation path is usable for this
@@ -44,7 +49,7 @@ declare function getPagesMiddlewareDataHref(browserUrl: string, basePath: string
44
49
  * (`getDataHref`). vinext's equivalent uses an in-memory loader map instead
45
50
  * of Next.js' `_buildManifest.js`.
46
51
  */
47
- declare function resolvePagesDataNavigationTarget(browserUrl: string, basePath: string): PagesDataTarget | null;
52
+ declare function resolvePagesDataNavigationTarget(browserUrl: string, basePath: string, options?: PagesDataNavigationTargetOptions): PagesDataTarget | null;
48
53
  /**
49
54
  * Kick off the code-split loader and, for SSG pages, prefetch the data JSON so
50
55
  * the chunk and payload are warm by the time the user clicks.
@@ -3,7 +3,7 @@ import { isUnknownRecord } from "../../utils/record.js";
3
3
  import { getLocalePathPrefix } from "../../utils/domain-locale.js";
4
4
  import { buildPagesDataHref, matchPagesPattern } from "./pages-data-url.js";
5
5
  import { NEXT_DEPLOYMENT_ID_HEADER, getDeploymentId } from "../../utils/deployment-id.js";
6
- import { fetchCachedPagesData, fetchStaticPagesData } from "./pages-data-fetch-dedup.js";
6
+ import { fetchStaticPagesData, fetchUncachedPagesData } from "./pages-data-fetch-dedup.js";
7
7
  //#region src/shims/internal/pages-data-target.ts
8
8
  /**
9
9
  * Shared decision helper for the Pages Router `/_next/data/<id>/<page>.json`
@@ -106,7 +106,7 @@ function getPagesMiddlewareDataHref(browserUrl, basePath) {
106
106
  * (`getDataHref`). vinext's equivalent uses an in-memory loader map instead
107
107
  * of Next.js' `_buildManifest.js`.
108
108
  */
109
- function resolvePagesDataNavigationTarget(browserUrl, basePath) {
109
+ function resolvePagesDataNavigationTarget(browserUrl, basePath, options = {}) {
110
110
  if (typeof window === "undefined") return null;
111
111
  const loaders = window.__VINEXT_PAGE_LOADERS__;
112
112
  const patterns = window.__VINEXT_PAGE_PATTERNS__;
@@ -129,8 +129,12 @@ function resolvePagesDataNavigationTarget(browserUrl, basePath) {
129
129
  const ssgPatterns = window.__VINEXT_PAGES_SSG_PATTERNS__;
130
130
  const sspPatterns = window.__VINEXT_PAGES_SSP_PATTERNS__;
131
131
  const dataKind = ssgPatterns?.includes(match.pattern) ? "static" : sspPatterns?.includes(match.pattern) ? "server" : ssgPatterns === void 0 || sspPatterns === void 0 ? "server" : "none";
132
+ const explicitLocale = options.locale === false ? window.__VINEXT_DEFAULT_LOCALE__ : options.locale;
133
+ const currentLocale = locale ?? explicitLocale ?? window.__VINEXT_LOCALE__;
134
+ const prefetchPagePath = locale || !currentLocale || !window.__VINEXT_LOCALES__?.includes(currentLocale) ? pagePath : `/${currentLocale}${pagePath === "/" ? "" : pagePath}`;
132
135
  return {
133
136
  dataHref: buildPagesDataHref(basePath, buildId, pagePath, parsed.search),
137
+ prefetchDataHref: buildPagesDataHref(basePath, buildId, prefetchPagePath, parsed.search),
134
138
  pattern: match.pattern,
135
139
  params: match.params,
136
140
  loader,
@@ -139,6 +143,7 @@ function resolvePagesDataNavigationTarget(browserUrl, basePath) {
139
143
  buildId,
140
144
  pagePath,
141
145
  search: parsed.search,
146
+ prefetchLocale: currentLocale,
142
147
  locale
143
148
  };
144
149
  }
@@ -168,10 +173,10 @@ function prefetchPagesData(target) {
168
173
  const deploymentId = getDeploymentId();
169
174
  if (deploymentId) headers[NEXT_DEPLOYMENT_ID_HEADER] = deploymentId;
170
175
  if (target.dataKind === "static") {
171
- fetchStaticPagesData(target.middlewareDataHref ?? target.dataHref, { headers }).catch(() => {});
176
+ fetchStaticPagesData(target.middlewareDataHref && target.middlewareDataHref !== target.dataHref ? target.middlewareDataHref : target.prefetchDataHref ?? target.middlewareDataHref ?? target.dataHref, { headers }).then((response) => response.arrayBuffer()).catch(() => {});
172
177
  return;
173
178
  }
174
- if (target.middlewareDataHref) fetchCachedPagesData(target.middlewareDataHref, { headers }).catch(() => {});
179
+ if (target.middlewareDataHref) fetchUncachedPagesData(target.middlewareDataHref, { headers }).catch(() => {});
175
180
  }
176
181
  //#endregion
177
182
  export { getPagesMiddlewareDataHref, prefetchPagesData, resolvePagesDataNavigationTarget };
@@ -42,7 +42,7 @@ type LinkProps = {
42
42
  onNavigate?: (event: NavigateEvent) => void;
43
43
  children?: React.ReactNode;
44
44
  } & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">;
45
- type LinkPrefetchMode = "disabled" | "auto" | "full";
45
+ type LinkPrefetchMode = "disabled" | "auto" | "full" | "full-after-shell";
46
46
  declare global {
47
47
  interface Window {
48
48
  __VINEXT_LINK_PREFETCH_ROUTES__?: VinextLinkPrefetchRoute[];