vinext 0.1.4 → 0.1.6

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 (176) hide show
  1. package/README.md +2 -2
  2. package/dist/build/css-url-assets.d.ts +1 -1
  3. package/dist/build/css-url-assets.js +9 -7
  4. package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
  5. package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
  6. package/dist/build/prerender.js +2 -1
  7. package/dist/build/report.d.ts +1 -2
  8. package/dist/build/report.js +2 -11
  9. package/dist/build/run-prerender.js +1 -1
  10. package/dist/cache/cache-adapters-virtual.js +1 -1
  11. package/dist/check.js +3 -3
  12. package/dist/client/pages-router-link-navigation.d.ts +10 -0
  13. package/dist/client/pages-router-link-navigation.js +3 -3
  14. package/dist/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +1 -1
  15. package/dist/deploy.js +3 -3
  16. package/dist/entries/app-rsc-entry.js +25 -20
  17. package/dist/entries/app-rsc-manifest.js +76 -46
  18. package/dist/entries/pages-server-entry.js +3 -1
  19. package/dist/index.js +254 -198
  20. package/dist/init.d.ts +5 -4
  21. package/dist/init.js +27 -8
  22. package/dist/plugins/css-data-url.js +30 -26
  23. package/dist/plugins/extensionless-dynamic-import.js +27 -24
  24. package/dist/plugins/fonts.js +3 -1
  25. package/dist/plugins/import-meta-url.js +21 -15
  26. package/dist/plugins/instrumentation-client.js +1 -1
  27. package/dist/plugins/middleware-export-validation.d.ts +5 -0
  28. package/dist/plugins/middleware-export-validation.js +36 -0
  29. package/dist/plugins/middleware-server-only.js +7 -6
  30. package/dist/plugins/og-asset-ownership.d.ts +27 -0
  31. package/dist/plugins/og-asset-ownership.js +285 -0
  32. package/dist/plugins/og-assets.js +66 -46
  33. package/dist/plugins/optimize-imports.js +9 -3
  34. package/dist/plugins/remove-console.d.ts +7 -1
  35. package/dist/plugins/remove-console.js +4 -1
  36. package/dist/plugins/require-context.js +21 -20
  37. package/dist/plugins/strip-server-exports.d.ts +7 -1
  38. package/dist/plugins/strip-server-exports.js +4 -1
  39. package/dist/server/accept-encoding.d.ts +29 -0
  40. package/dist/server/accept-encoding.js +95 -0
  41. package/dist/server/app-bfcache-identity.d.ts +26 -0
  42. package/dist/server/app-bfcache-identity.js +127 -0
  43. package/dist/server/app-browser-entry.js +17 -11
  44. package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
  45. package/dist/server/app-browser-rsc-redirect.js +8 -1
  46. package/dist/server/app-browser-state.d.ts +2 -21
  47. package/dist/server/app-browser-state.js +4 -128
  48. package/dist/server/app-browser-stream.js +1 -1
  49. package/dist/server/app-browser-visible-commit.js +3 -2
  50. package/dist/server/app-fallback-renderer.d.ts +1 -1
  51. package/dist/server/app-layout-param-observation.d.ts +1 -1
  52. package/dist/server/app-layout-param-observation.js +1 -1
  53. package/dist/server/app-middleware.d.ts +1 -0
  54. package/dist/server/app-middleware.js +3 -1
  55. package/dist/server/app-page-boundary-render.d.ts +1 -1
  56. package/dist/server/app-page-boundary.js +1 -1
  57. package/dist/server/app-page-cache-finalizer.d.ts +62 -0
  58. package/dist/server/app-page-cache-finalizer.js +122 -0
  59. package/dist/server/app-page-cache-render.d.ts +2 -2
  60. package/dist/server/app-page-cache-render.js +1 -1
  61. package/dist/server/app-page-cache.d.ts +2 -53
  62. package/dist/server/app-page-cache.js +5 -131
  63. package/dist/server/app-page-dispatch.d.ts +4 -4
  64. package/dist/server/app-page-dispatch.js +10 -8
  65. package/dist/server/app-page-head.js +2 -2
  66. package/dist/server/app-page-probe.js +3 -2
  67. package/dist/server/app-page-render-observation.js +2 -2
  68. package/dist/server/app-page-render.d.ts +3 -3
  69. package/dist/server/app-page-render.js +3 -2
  70. package/dist/server/app-page-request.d.ts +7 -0
  71. package/dist/server/app-page-request.js +19 -1
  72. package/dist/server/app-page-stream.d.ts +2 -9
  73. package/dist/server/app-page-stream.js +1 -35
  74. package/dist/server/app-prerender-endpoints.d.ts +3 -3
  75. package/dist/server/app-prerender-endpoints.js +3 -4
  76. package/dist/server/app-request-context.d.ts +1 -2
  77. package/dist/server/app-request-context.js +2 -1
  78. package/dist/server/app-route-handler-dispatch.js +3 -2
  79. package/dist/server/app-route-handler-execution.d.ts +1 -1
  80. package/dist/server/app-route-handler-execution.js +1 -1
  81. package/dist/server/app-route-handler-response.d.ts +1 -1
  82. package/dist/server/app-route-module-loader.d.ts +62 -14
  83. package/dist/server/app-route-module-loader.js +61 -10
  84. package/dist/server/app-router-entry.js +2 -1
  85. package/dist/server/app-rsc-handler.d.ts +7 -6
  86. package/dist/server/app-rsc-handler.js +51 -40
  87. package/dist/server/app-rsc-response-finalizer.js +1 -1
  88. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  89. package/dist/server/app-rsc-route-matching.js +13 -0
  90. package/dist/server/app-server-action-execution.d.ts +1 -1
  91. package/dist/server/app-server-action-execution.js +5 -4
  92. package/dist/server/app-ssr-entry.d.ts +1 -1
  93. package/dist/server/app-ssr-entry.js +9 -7
  94. package/dist/server/app-ssr-router-instance.d.ts +6 -0
  95. package/dist/server/app-ssr-router-instance.js +24 -0
  96. package/dist/server/app-ssr-stream.js +1 -1
  97. package/dist/server/artifact-compatibility.js +1 -1
  98. package/dist/server/client-reuse-manifest.js +1 -1
  99. package/dist/server/defer-until-stream-consumed.d.ts +7 -0
  100. package/dist/server/defer-until-stream-consumed.js +34 -0
  101. package/dist/server/dev-server.js +1 -1
  102. package/dist/server/headers.d.ts +5 -1
  103. package/dist/server/headers.js +5 -1
  104. package/dist/server/instrumentation.js +1 -1
  105. package/dist/server/isr-cache.d.ts +1 -1
  106. package/dist/server/isr-cache.js +1 -1
  107. package/dist/server/isr-decision.d.ts +1 -1
  108. package/dist/server/metadata-route-response.d.ts +3 -3
  109. package/dist/server/middleware-matcher.js +8 -6
  110. package/dist/server/middleware-runtime.d.ts +2 -1
  111. package/dist/server/middleware-runtime.js +21 -10
  112. package/dist/server/middleware.d.ts +2 -2
  113. package/dist/server/middleware.js +26 -11
  114. package/dist/server/open-redirect.d.ts +12 -0
  115. package/dist/server/open-redirect.js +21 -0
  116. package/dist/server/pages-page-data.d.ts +1 -1
  117. package/dist/server/pages-page-response.d.ts +1 -1
  118. package/dist/server/pages-page-response.js +2 -2
  119. package/dist/server/pages-request-pipeline.d.ts +3 -1
  120. package/dist/server/pages-request-pipeline.js +4 -2
  121. package/dist/server/prod-server.d.ts +1 -1
  122. package/dist/server/prod-server.js +87 -73
  123. package/dist/server/request-pipeline.d.ts +1 -24
  124. package/dist/server/request-pipeline.js +1 -33
  125. package/dist/server/seed-cache.d.ts +1 -1
  126. package/dist/shims/cache-handler.d.ts +106 -0
  127. package/dist/shims/cache-handler.js +176 -0
  128. package/dist/shims/cache-request-state.d.ts +47 -0
  129. package/dist/shims/cache-request-state.js +126 -0
  130. package/dist/shims/cache-runtime.d.ts +2 -2
  131. package/dist/shims/cache-runtime.js +3 -14
  132. package/dist/shims/cache.d.ts +3 -231
  133. package/dist/shims/cache.js +17 -383
  134. package/dist/shims/cdn-cache.d.ts +1 -1
  135. package/dist/shims/cdn-cache.js +1 -1
  136. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  137. package/dist/shims/error-boundary-navigation.js +44 -0
  138. package/dist/shims/error-boundary.js +10 -8
  139. package/dist/shims/error.js +2 -1
  140. package/dist/shims/fetch-cache.js +1 -1
  141. package/dist/shims/font-google-base.d.ts +1 -0
  142. package/dist/shims/font-google-base.js +6 -21
  143. package/dist/shims/form.js +1 -1
  144. package/dist/shims/image.js +67 -9
  145. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -0
  146. package/dist/shims/internal/app-page-props-cache-key.js +16 -0
  147. package/dist/shims/internal/interpolate-as.d.ts +25 -0
  148. package/dist/shims/internal/interpolate-as.js +196 -0
  149. package/dist/shims/internal/navigation-untracked.js +2 -1
  150. package/dist/shims/layout-segment-context.d.ts +1 -1
  151. package/dist/shims/layout-segment-context.js +2 -1
  152. package/dist/shims/link.js +26 -3
  153. package/dist/shims/navigation-context-state.d.ts +40 -0
  154. package/dist/shims/navigation-context-state.js +116 -0
  155. package/dist/shims/navigation-errors.d.ts +55 -0
  156. package/dist/shims/navigation-errors.js +110 -0
  157. package/dist/shims/navigation-server.d.ts +3 -0
  158. package/dist/shims/navigation-server.js +3 -0
  159. package/dist/shims/navigation-state.d.ts +1 -2
  160. package/dist/shims/navigation-state.js +2 -1
  161. package/dist/shims/navigation.d.ts +3 -291
  162. package/dist/shims/navigation.js +13 -444
  163. package/dist/shims/navigation.react-server.d.ts +2 -2
  164. package/dist/shims/navigation.react-server.js +4 -2
  165. package/dist/shims/request-state-types.d.ts +3 -3
  166. package/dist/shims/router.d.ts +1 -3
  167. package/dist/shims/router.js +62 -18
  168. package/dist/shims/script.js +1 -1
  169. package/dist/shims/slot.js +3 -1
  170. package/dist/shims/unified-request-context.d.ts +2 -2
  171. package/dist/typegen.js +2 -10
  172. package/dist/utils/project.d.ts +7 -1
  173. package/dist/utils/project.js +16 -2
  174. package/dist/utils/virtual-module.d.ts +5 -0
  175. package/dist/utils/virtual-module.js +0 -0
  176. package/package.json +7 -3
@@ -206,27 +206,12 @@ function getSSRFontPreloads() {
206
206
  return [...ssrFontPreloads];
207
207
  }
208
208
  /**
209
- * Extract font file URLs from @font-face CSS rules.
210
- * Parses url('...') references from the CSS text.
211
- */
212
- function extractFontUrlsFromCSS(css) {
213
- const urls = [];
214
- const urlRegex = /url\(['"]?([^'")]+)['"]?\)/g;
215
- let match;
216
- while ((match = urlRegex.exec(css)) !== null) {
217
- const url = match[1];
218
- if (url && url.startsWith("/")) urls.push(url);
219
- }
220
- return urls;
221
- }
222
- /**
223
- * Collect font file URLs from self-hosted CSS for preload link generation.
209
+ * Collect build-selected font file URLs for preload link generation.
224
210
  * Only collects on the server (SSR). Deduplicates by href using a Set for O(1) lookups.
225
211
  */
226
- function collectFontPreloadsFromCSS(css) {
212
+ function collectFontPreloads(urls) {
227
213
  if (typeof document !== "undefined") return;
228
- const urls = extractFontUrlsFromCSS(css);
229
- for (const href of urls) if (!ssrFontPreloadHrefs.has(href)) {
214
+ for (const href of urls) if (href.startsWith("/") && !ssrFontPreloadHrefs.has(href)) {
230
215
  ssrFontPreloadHrefs.add(href);
231
216
  ssrFontPreloads.push({
232
217
  href,
@@ -240,10 +225,10 @@ const injectedSelfHosted = /* @__PURE__ */ new Set();
240
225
  * Inject self-hosted @font-face CSS (from the build plugin).
241
226
  * This replaces the CDN <link> tag with inline CSS.
242
227
  */
243
- function injectSelfHostedCSS(css) {
228
+ function injectSelfHostedCSS(css, preloadUrls = []) {
229
+ collectFontPreloads(preloadUrls);
244
230
  if (injectedSelfHosted.has(css)) return;
245
231
  injectedSelfHosted.add(css);
246
- collectFontPreloadsFromCSS(css);
247
232
  if (typeof document === "undefined") {
248
233
  ssrFontStyles.push(css);
249
234
  return;
@@ -277,7 +262,7 @@ function createFontLoader(family) {
277
262
  internalStyle: internal?.fontStyle,
278
263
  google: true
279
264
  });
280
- if (internal?.selfHostedCSS) injectSelfHostedCSS(internal.selfHostedCSS);
265
+ if (internal?.selfHostedCSS) injectSelfHostedCSS(internal.selfHostedCSS, internal.preloadUrls);
281
266
  else {
282
267
  const url = buildGoogleFontsUrl(family, options);
283
268
  injectFontStylesheet(url);
@@ -4,8 +4,8 @@ import { isDangerousScheme } from "./url-safety.js";
4
4
  import { AppElementsWire } from "../server/app-elements-wire.js";
5
5
  import "../server/app-elements.js";
6
6
  import { toSameOriginPath, withBasePath } from "./url-utils.js";
7
- import { hasAppNavigationRuntime } from "../client/navigation-runtime.js";
8
7
  import { createRscRequestHeaders, createRscRequestUrl } from "../server/app-rsc-cache-busting.js";
8
+ import { hasAppNavigationRuntime } from "../client/navigation-runtime.js";
9
9
  import { getMountedSlotsHeader, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, navigateClientSide, prefetchRscResponse } from "./navigation.js";
10
10
  import { useMergedRef } from "./use-merged-ref.js";
11
11
  import { forwardRef, useActionState, useCallback, useEffect, useRef } from "react";
@@ -243,7 +243,7 @@ function generateSrcSet(src, originalWidth, quality = 75) {
243
243
  if (widths.length === 0) return `${imageOptimizationUrl(src, originalWidth, quality)} ${originalWidth}w`;
244
244
  return widths.map((w) => `${imageOptimizationUrl(src, w, quality)} ${w}w`).join(", ");
245
245
  }
246
- const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill, preload, priority, quality, placeholder, blurDataURL, loader, sizes, className, style, onLoad, onLoadingComplete, onError, unoptimized: _unoptimized, overrideSrc: _overrideSrc, loading, ...rest }, ref) {
246
+ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill, preload, priority, quality, placeholder, blurDataURL, loader, sizes, className, style, onLoad, onLoadingComplete, onError, unoptimized: _unoptimized, overrideSrc, loading, ...rest }, ref) {
247
247
  const lastLoadedSrcRef = useRef(void 0);
248
248
  const lastErrorSrcRef = useRef(void 0);
249
249
  const didInsertRef = useRef(false);
@@ -326,6 +326,40 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
326
326
  lastErrorSrcRef.current = src;
327
327
  markBlurComplete();
328
328
  } : void 0;
329
+ if (_unoptimized === true) {
330
+ const renderedSrc = overrideSrc || src;
331
+ const sanitizedBlur = imgBlurDataURL ? sanitizeBlurDataURL(imgBlurDataURL) : void 0;
332
+ const blurStyle = !blurComplete && placeholder === "blur" && sanitizedBlur ? {
333
+ backgroundImage: `url(${sanitizedBlur})`,
334
+ backgroundSize: "cover",
335
+ backgroundRepeat: "no-repeat",
336
+ backgroundPosition: "center"
337
+ } : void 0;
338
+ preloadImageResource({
339
+ shouldPreload,
340
+ src: renderedSrc,
341
+ fetchPriority: priorityFetchPriority
342
+ });
343
+ return /* @__PURE__ */ jsx("img", {
344
+ ref: mergedRef,
345
+ src: renderedSrc,
346
+ alt,
347
+ width: fill ? void 0 : imgWidth,
348
+ height: fill ? void 0 : imgHeight,
349
+ loading: imageLoading,
350
+ fetchPriority: priorityFetchPriority,
351
+ decoding: "async",
352
+ className,
353
+ "data-nimg": fill ? "fill" : "1",
354
+ onLoad: handleLoad,
355
+ onError: handleError,
356
+ style: fill ? getFillStyle(style, blurStyle) : {
357
+ ...blurStyle,
358
+ ...style
359
+ },
360
+ ...rest
361
+ });
362
+ }
329
363
  if (loader) {
330
364
  const resolvedSrc = loader({
331
365
  src,
@@ -371,11 +405,11 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
371
405
  } : void 0;
372
406
  const bg = showBlur ? `url(${sanitizedBlur})` : void 0;
373
407
  if (fill) {
374
- const fillSizes = sizes ?? "100vw";
408
+ const imageSizes = sizes ?? "100vw";
375
409
  preloadImageResource({
376
410
  shouldPreload,
377
411
  src,
378
- sizes: fillSizes,
412
+ sizes: imageSizes,
379
413
  fetchPriority: priorityFetchPriority
380
414
  });
381
415
  return /* @__PURE__ */ jsx("img", {
@@ -385,7 +419,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
385
419
  loading: imageLoading,
386
420
  fetchPriority: priorityFetchPriority,
387
421
  decoding: "async",
388
- sizes: fillSizes,
422
+ sizes: imageSizes,
389
423
  className,
390
424
  "data-nimg": "fill",
391
425
  onLoad: handleLoad,
@@ -421,8 +455,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
421
455
  }
422
456
  }
423
457
  const imgQuality = quality ?? 75;
424
- const isSvg = isSvgUrl(src);
425
- const skipOptimization = _unoptimized === true || isSvg && !__dangerouslyAllowSVG;
458
+ const skipOptimization = isSvgUrl(src) && !__dangerouslyAllowSVG;
426
459
  const srcSet = imgWidth && !fill && !skipOptimization ? generateSrcSet(src, imgWidth, imgQuality) : imgWidth && !fill ? RESPONSIVE_WIDTHS.filter((w) => w <= imgWidth * 2).map((w) => `${src} ${w}w`).join(", ") || `${src} ${imgWidth}w` : void 0;
427
460
  const optimizedSrc = skipOptimization ? src : imgWidth ? imageOptimizationUrl(src, imgWidth, imgQuality) : imageOptimizationUrl(src, RESPONSIVE_WIDTHS[0], imgQuality);
428
461
  const sanitizedLocalBlur = imgBlurDataURL ? sanitizeBlurDataURL(imgBlurDataURL) : void 0;
@@ -467,7 +500,7 @@ const Image = forwardRef(function Image({ src: srcProp, alt, width, height, fill
467
500
  * Returns the props that would be passed to the underlying <img> element.
468
501
  */
469
502
  function getImageProps(props) {
470
- const { src: srcProp, alt, width, height, fill, preload: _preload, priority, quality: _quality, placeholder, blurDataURL: blurDataURLProp, loader, sizes, className, style, onLoad: _onLoad, onLoadingComplete: _onLoadingComplete, unoptimized: _unoptimized, overrideSrc: _overrideSrc, loading, ...rest } = props;
503
+ const { src: srcProp, alt, width, height, fill, preload: _preload, priority, quality: _quality, placeholder, blurDataURL: blurDataURLProp, loader, sizes, className, style, onLoad: _onLoad, onLoadingComplete: _onLoadingComplete, unoptimized: _unoptimized, overrideSrc, loading, ...rest } = props;
471
504
  const { src, width: imgWidth, height: imgHeight, blurDataURL: imgBlurDataURL } = resolveImageSource({
472
505
  src: srcProp,
473
506
  width,
@@ -475,6 +508,32 @@ function getImageProps(props) {
475
508
  blurDataURL: blurDataURLProp
476
509
  });
477
510
  const shouldPreload = _preload === true || priority === true;
511
+ if (_unoptimized === true) {
512
+ const renderedSrc = overrideSrc || src;
513
+ const sanitizedBlurURL = imgBlurDataURL ? sanitizeBlurDataURL(imgBlurDataURL) : void 0;
514
+ const blurStyle = placeholder === "blur" && sanitizedBlurURL ? {
515
+ backgroundImage: `url(${sanitizedBlurURL})`,
516
+ backgroundSize: "cover",
517
+ backgroundRepeat: "no-repeat",
518
+ backgroundPosition: "center"
519
+ } : void 0;
520
+ return { props: {
521
+ src: renderedSrc,
522
+ alt,
523
+ width: fill ? void 0 : imgWidth,
524
+ height: fill ? void 0 : imgHeight,
525
+ loading: priority ? "eager" : shouldPreload ? loading : loading ?? "lazy",
526
+ fetchPriority: priority ? "high" : void 0,
527
+ decoding: "async",
528
+ className,
529
+ "data-nimg": fill ? "fill" : "1",
530
+ style: fill ? getFillStyle(style, blurStyle) : {
531
+ ...blurStyle,
532
+ ...style
533
+ },
534
+ ...rest
535
+ } };
536
+ }
478
537
  let blockedInProd = false;
479
538
  if (isRemoteUrl(src)) {
480
539
  const validation = validateRemoteUrl(src);
@@ -490,8 +549,7 @@ function getImageProps(props) {
490
549
  width: imgWidth ?? 0,
491
550
  quality: imgQuality
492
551
  }) : src;
493
- const isSvg = isSvgUrl(resolvedSrc);
494
- const skipOpt = _unoptimized === true || isSvg && !__dangerouslyAllowSVG || blockedInProd || !!loader || isRemoteUrl(resolvedSrc);
552
+ const skipOpt = isSvgUrl(resolvedSrc) && !__dangerouslyAllowSVG || blockedInProd || !!loader || isRemoteUrl(resolvedSrc);
495
553
  const optimizedSrc = skipOpt ? resolvedSrc : imgWidth ? imageOptimizationUrl(resolvedSrc, imgWidth, imgQuality) : imageOptimizationUrl(resolvedSrc, RESPONSIVE_WIDTHS[0], imgQuality);
496
554
  const srcSet = imgWidth && !fill && !isRemoteUrl(resolvedSrc) && !loader && !skipOpt ? generateSrcSet(resolvedSrc, imgWidth, imgQuality) : void 0;
497
555
  const sanitizedBlurURL = imgBlurDataURL ? sanitizeBlurDataURL(imgBlurDataURL) : void 0;
@@ -0,0 +1,5 @@
1
+ //#region src/shims/internal/app-page-props-cache-key.d.ts
2
+ declare function markAppPagePropsForUseCache<T extends object>(props: T): T;
3
+ declare function isMarkedAppPagePropsObject(value: object): boolean;
4
+ //#endregion
5
+ export { isMarkedAppPagePropsObject, markAppPagePropsForUseCache };
@@ -0,0 +1,16 @@
1
+ //#region src/shims/internal/app-page-props-cache-key.ts
2
+ const APP_PAGE_PROPS_CACHE_KEY_MARKER = Symbol.for("vinext.appPagePropsCacheKeyMarker");
3
+ function markAppPagePropsForUseCache(props) {
4
+ Object.defineProperty(props, APP_PAGE_PROPS_CACHE_KEY_MARKER, {
5
+ configurable: false,
6
+ enumerable: false,
7
+ value: true,
8
+ writable: false
9
+ });
10
+ return props;
11
+ }
12
+ function isMarkedAppPagePropsObject(value) {
13
+ return Reflect.get(value, APP_PAGE_PROPS_CACHE_KEY_MARKER) === true;
14
+ }
15
+ //#endregion
16
+ export { isMarkedAppPagePropsObject, markAppPagePropsForUseCache };
@@ -0,0 +1,25 @@
1
+ import { UrlQuery } from "../../utils/query.js";
2
+
3
+ //#region src/shims/internal/interpolate-as.d.ts
4
+ /**
5
+ * Wire-compatible alias for Node's `querystring.ParsedUrlQuery`. Inlined here
6
+ * so this module has no dependency on the `querystring` types package.
7
+ */
8
+ type ParsedUrlQuery = {
9
+ [key: string]: string | string[] | undefined;
10
+ };
11
+ type DynamicRouteHrefProjection = {
12
+ href: string;
13
+ params: string[];
14
+ query: ParsedUrlQuery;
15
+ routePathname: string;
16
+ };
17
+ /**
18
+ * Resolve a bracket-pattern route href against its displayed href. Query
19
+ * values can be supplied directly (object-form hrefs) or parsed from the route
20
+ * href (string-form hrefs). A `?` after `#` is part of the fragment, not a
21
+ * query delimiter.
22
+ */
23
+ declare function interpolateDynamicRouteHref(routeHref: string, asHref: string, queryInput?: UrlQuery): DynamicRouteHrefProjection | null;
24
+ //#endregion
25
+ export { DynamicRouteHrefProjection, interpolateDynamicRouteHref };
@@ -0,0 +1,196 @@
1
+ import { removeTrailingSlash } from "../../utils/base-path.js";
2
+ //#region src/shims/internal/interpolate-as.ts
3
+ /**
4
+ * Port of Next.js' Pages Router `interpolateAs` helper, plus the minimal
5
+ * subset of its transitive dependencies (`getRouteRegex` + `getRouteMatcher`
6
+ * and the parameter-pattern parsers they rely on).
7
+ *
8
+ * Upstream sources:
9
+ * - packages/next/src/shared/lib/router/utils/interpolate-as.ts
10
+ * - packages/next/src/shared/lib/router/utils/route-regex.ts
11
+ * - packages/next/src/shared/lib/router/utils/route-matcher.ts
12
+ * - packages/next/src/shared/lib/router/utils/get-dynamic-param.ts
13
+ * - packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts
14
+ * - packages/next/src/shared/lib/escape-regexp.ts
15
+ *
16
+ * Used by the Pages Router shim to project a route pattern + `as` pathname +
17
+ * query back into a fully-interpolated browser URL (the `<Link href as>`
18
+ * masking path). Interception-route markers (`(.)`, `(..)`, `(...)`,
19
+ * `(..)(..)`) are intentionally omitted — they only apply to App Router and
20
+ * never appear in Pages Router patterns.
21
+ */
22
+ function normalizeQuery(query) {
23
+ const normalized = {};
24
+ if (!query) return normalized;
25
+ for (const [key, value] of Object.entries(query)) {
26
+ if (value === void 0) continue;
27
+ normalized[key] = Array.isArray(value) ? value.map(String) : String(value);
28
+ }
29
+ return normalized;
30
+ }
31
+ function parseRouteHrefQuery(routeHref, queryIndex, hashIndex) {
32
+ const query = {};
33
+ if (queryIndex === -1 || hashIndex !== -1 && queryIndex > hashIndex) return query;
34
+ const searchEnd = hashIndex === -1 ? routeHref.length : hashIndex;
35
+ for (const [key, value] of new URLSearchParams(routeHref.slice(queryIndex + 1, searchEnd))) {
36
+ const existing = query[key];
37
+ if (existing === void 0) query[key] = value;
38
+ else if (Array.isArray(existing)) existing.push(value);
39
+ else query[key] = [existing, value];
40
+ }
41
+ return query;
42
+ }
43
+ const reHasRegExp = /[|\\{}()[\]^$+*?.-]/;
44
+ const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g;
45
+ function escapeStringRegexp(str) {
46
+ if (reHasRegExp.test(str)) return str.replace(reReplaceRegExp, "\\$&");
47
+ return str;
48
+ }
49
+ /**
50
+ * Regular expression pattern used to match route parameters.
51
+ * Matches both single parameters and parameter groups.
52
+ * Examples:
53
+ * - `[[...slug]]` matches parameter group with key 'slug', repeat: true, optional: true
54
+ * - `[...slug]` matches parameter group with key 'slug', repeat: true, optional: false
55
+ * - `[[foo]]` matches parameter with key 'foo', repeat: false, optional: true
56
+ * - `[bar]` matches parameter with key 'bar', repeat: false, optional: false
57
+ */
58
+ const PARAMETER_PATTERN = /^([^[]*)\[((?:\[[^\]]*\])|[^\]]+)\](.*)$/;
59
+ /**
60
+ * Parses a matched parameter from the PARAMETER_PATTERN regex.
61
+ * Examples:
62
+ * - `[...slug]` -> `{ key: 'slug', repeat: true, optional: true }`
63
+ * - `...slug` -> `{ key: 'slug', repeat: true, optional: false }`
64
+ * - `[foo]` -> `{ key: 'foo', repeat: false, optional: true }`
65
+ * - `bar` -> `{ key: 'bar', repeat: false, optional: false }`
66
+ */
67
+ function parseMatchedParameter(param) {
68
+ const optional = param.startsWith("[") && param.endsWith("]");
69
+ if (optional) param = param.slice(1, -1);
70
+ const repeat = param.startsWith("...");
71
+ if (repeat) param = param.slice(3);
72
+ return {
73
+ key: param,
74
+ repeat,
75
+ optional
76
+ };
77
+ }
78
+ function getParametrizedRoute(route) {
79
+ const groups = {};
80
+ let groupIndex = 1;
81
+ const segments = [];
82
+ for (const segment of removeTrailingSlash(route).slice(1).split("/")) {
83
+ const paramMatches = segment.match(PARAMETER_PATTERN);
84
+ if (paramMatches && paramMatches[2]) {
85
+ const { key, repeat, optional } = parseMatchedParameter(paramMatches[2]);
86
+ groups[key] = {
87
+ pos: groupIndex++,
88
+ repeat,
89
+ optional
90
+ };
91
+ const s = repeat ? optional ? "(?:/(.+?))?" : "/(.+?)" : "/([^/]+?)";
92
+ segments.push(s);
93
+ } else segments.push(`/${escapeStringRegexp(segment)}`);
94
+ }
95
+ return {
96
+ parameterizedRoute: segments.join(""),
97
+ groups
98
+ };
99
+ }
100
+ /**
101
+ * From a normalized route this function generates a regular expression and a
102
+ * corresponding groups object intended to be used to store matching groups
103
+ * from the regular expression.
104
+ */
105
+ function getRouteRegex(normalizedRoute) {
106
+ const { parameterizedRoute, groups } = getParametrizedRoute(normalizedRoute);
107
+ return {
108
+ re: new RegExp(`^${parameterizedRoute}(?:/)?$`),
109
+ groups
110
+ };
111
+ }
112
+ /**
113
+ * Compile a route regex into a function that extracts decoded params from a
114
+ * pathname. Returns `false` if the pathname does not match the route.
115
+ *
116
+ * The `safeRouteMatcher`/`stripParameterSeparators` wrapper that Next.js
117
+ * applies (for adjacent-parameter normalization) is omitted — Pages Router
118
+ * patterns never contain adjacent parameters, so the wrapper is a no-op for
119
+ * this caller.
120
+ */
121
+ function getRouteMatcher({ re, groups }) {
122
+ return (pathname) => {
123
+ const routeMatch = re.exec(pathname);
124
+ if (!routeMatch) return false;
125
+ const decode = (param) => {
126
+ try {
127
+ return decodeURIComponent(param);
128
+ } catch {
129
+ throw new Error("failed to decode param");
130
+ }
131
+ };
132
+ const params = {};
133
+ for (const [key, group] of Object.entries(groups)) {
134
+ const match = routeMatch[group.pos];
135
+ if (match !== void 0) if (group.repeat) params[key] = match.split("/").map((entry) => decode(entry));
136
+ else params[key] = decode(match);
137
+ }
138
+ return params;
139
+ };
140
+ }
141
+ /**
142
+ * Project a `(route, asPathname, query)` triple back into a fully-interpolated
143
+ * browser URL. Used by the `<Link href as>` masking path in the Pages Router
144
+ * shim: extract param values from the rendered `as` path when it differs from
145
+ * the route pattern, otherwise fall back to reading them from the href query.
146
+ *
147
+ * Returns `{ params, result }`. `result` is the interpolated URL, or the empty
148
+ * string when one or more required params could not be resolved. Callers warn
149
+ * (in dev) on the empty-string case; this helper deliberately stays silent —
150
+ * matching Next.js' behavior, where `<Link>` itself owns the dev warning.
151
+ */
152
+ function interpolateAs(route, asPathname, query) {
153
+ let interpolatedRoute = "";
154
+ const dynamicRegex = getRouteRegex(route);
155
+ const dynamicGroups = dynamicRegex.groups;
156
+ const dynamicMatches = (asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : "") || query;
157
+ interpolatedRoute = route;
158
+ const params = Object.keys(dynamicGroups);
159
+ if (!params.every((param) => {
160
+ let value = dynamicMatches[param] || "";
161
+ const { repeat, optional } = dynamicGroups[param];
162
+ let replaced = `[${repeat ? "..." : ""}${param}]`;
163
+ if (optional) replaced = `${!value ? "/" : ""}[${replaced}]`;
164
+ if (repeat && !Array.isArray(value)) value = [value];
165
+ return (optional || param in dynamicMatches) && (interpolatedRoute = interpolatedRoute.replace(replaced, repeat ? value.map((segment) => encodeURIComponent(segment)).join("/") : encodeURIComponent(value)) || "/");
166
+ })) interpolatedRoute = "";
167
+ return {
168
+ params,
169
+ result: interpolatedRoute
170
+ };
171
+ }
172
+ /**
173
+ * Resolve a bracket-pattern route href against its displayed href. Query
174
+ * values can be supplied directly (object-form hrefs) or parsed from the route
175
+ * href (string-form hrefs). A `?` after `#` is part of the fragment, not a
176
+ * query delimiter.
177
+ */
178
+ function interpolateDynamicRouteHref(routeHref, asHref, queryInput) {
179
+ if (!routeHref.includes("[")) return null;
180
+ const hashIndex = routeHref.indexOf("#");
181
+ const queryIndex = routeHref.indexOf("?");
182
+ const pathEnd = [hashIndex, queryIndex].filter((index) => index !== -1).reduce((earliest, index) => Math.min(earliest, index), routeHref.length);
183
+ const routePathname = routeHref.slice(0, pathEnd);
184
+ const trailing = routeHref.slice(pathEnd);
185
+ const asPathname = asHref.split(/[?#]/, 1)[0];
186
+ const query = queryInput ? normalizeQuery(queryInput) : parseRouteHrefQuery(routeHref, queryIndex, hashIndex);
187
+ const { result, params } = interpolateAs(routePathname, asPathname, query);
188
+ return {
189
+ href: result ? `${result}${trailing}` : "",
190
+ params,
191
+ query,
192
+ routePathname
193
+ };
194
+ }
195
+ //#endregion
196
+ export { interpolateDynamicRouteHref };
@@ -1,5 +1,6 @@
1
+ import { getNavigationContext } from "../navigation-context-state.js";
1
2
  import { getPagesNavigationContext } from "./pages-router-accessor.js";
2
- import { getClientNavigationState, getNavigationContext, useClientNavigationRenderSnapshot } from "../navigation.js";
3
+ import { getClientNavigationState, useClientNavigationRenderSnapshot } from "../navigation.js";
3
4
  //#region src/shims/internal/navigation-untracked.ts
4
5
  /**
5
6
  * Internal navigation-untracked pathname hook.
@@ -1,4 +1,4 @@
1
- import { SegmentMap } from "./navigation.js";
1
+ import { SegmentMap } from "./navigation-context-state.js";
2
2
  import { ReactNode } from "react";
3
3
 
4
4
  //#region src/shims/layout-segment-context.d.ts
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { getLayoutSegmentContext } from "./navigation.js";
2
+ import { getLayoutSegmentContext } from "./navigation-context-state.js";
3
+ import "./navigation-server.js";
3
4
  import { createElement } from "react";
4
5
  //#region src/shims/layout-segment-context.tsx
5
6
  /**
@@ -12,12 +12,13 @@ import { appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.
12
12
  import { resolveHybridClientRouteOwner } from "./internal/hybrid-client-route-owner.js";
13
13
  import { markAppRouteDetectedOnPrefetch } from "./internal/app-route-detection.js";
14
14
  import { isAbsoluteOrProtocolRelativeUrl, normalizePathTrailingSlash, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
15
+ import { interpolateDynamicRouteHref } from "./internal/interpolate-as.js";
15
16
  import { getCurrentBrowserLocale } from "./client-locale.js";
16
17
  import { getCurrentRoutePathnameForWarning } from "./internal/route-pattern-for-warning.js";
17
- import { getNavigationRuntime, hasAppNavigationRuntime, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
18
18
  import { createRscRequestHeaders, createRscRequestUrl, stripRscCacheBustingSearchParam, stripRscSuffix } from "../server/app-rsc-cache-busting.js";
19
- import { getMountedSlotsHeader, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, navigateClientSide, prefetchRscResponse } from "./navigation.js";
19
+ import { getNavigationRuntime, hasAppNavigationRuntime, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
20
20
  import { navigatePagesRouterLinkWithFallback, resolvePagesRouterQueryOnlyHref } from "../client/pages-router-link-navigation.js";
21
+ import { getMountedSlotsHeader, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, navigateClientSide, prefetchRscResponse } from "./navigation.js";
21
22
  import { getI18nContext } from "./i18n-context.js";
22
23
  import { canLinkIntentPrefetch, canLinkPrefetch, getLinkPrefetchHref } from "./link-prefetch.js";
23
24
  import { clearLinkForCurrentNavigation, notifyLinkNavigationStart, setLinkForCurrentNavigation } from "./internal/link-status-registry.js";
@@ -391,11 +392,30 @@ function applyLocaleToHref(href, locale) {
391
392
  }
392
393
  return addLocalePrefix(href, resolvedLocale, defaultLocale);
393
394
  }
395
+ /**
396
+ * For the `<Link href="/blog/[slug]" as="/blog/test-post">` case, project the
397
+ * bracket-pattern href + the resolved `as` back into a concrete route URL the
398
+ * Pages Router can fetch (`/blog/test-post`). Returns null when:
399
+ * - `href` has no bracket params (already concrete; the existing forwarding
400
+ * path works as-is)
401
+ * - interpolation fails because a required param could not be resolved
402
+ * (caller falls back to `as`, matching pre-PR behavior)
403
+ *
404
+ * The query for interpolation is the href's own query — `as` is the matcher
405
+ * input rather than the source of param values. For string hrefs the search
406
+ * portion is parsed into the query record; for object hrefs we take
407
+ * `href.query` directly.
408
+ */
409
+ function resolveConcreteRouteHref(href, as) {
410
+ if (typeof as !== "string") return null;
411
+ return interpolateDynamicRouteHref(typeof href === "string" ? href : resolveHref(href), as, typeof href === "string" ? void 0 : href.query)?.href || null;
412
+ }
394
413
  const Link = forwardRef(function Link({ href, as, replace = false, prefetch: prefetchProp, scroll = true, shallow = false, children: childrenProp, onClick, onMouseEnter, onTouchStart, onNavigate, unstable_dynamicOnHover = false, legacyBehavior = false, passHref = false, ...rest }, forwardedRef) {
395
414
  const { locale, ...restWithoutLocale } = rest;
396
415
  let children = childrenProp;
397
416
  if (legacyBehavior && (typeof childrenProp === "string" || typeof childrenProp === "number")) children = React.createElement("a", null, childrenProp);
398
417
  const rawResolvedHref = as ?? resolveHref(href);
418
+ const routeHrefRaw = resolveConcreteRouteHref(href, as) ?? (typeof href === "string" ? href : resolveHref(href));
399
419
  const resolvedHref = typeof rawResolvedHref === "string" ? warnAndNormalizeRepeatedSlashesInHref(rawResolvedHref) : rawResolvedHref;
400
420
  const isDangerous = typeof resolvedHref === "string" && isDangerousScheme(resolvedHref);
401
421
  const normalizedHref = normalizePathTrailingSlash(applyLocaleToHref(isDangerous ? "/" : resolvedHref, locale), __trailingSlash);
@@ -508,6 +528,8 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
508
528
  e.preventDefault();
509
529
  const hasAppNavigationRuntime = Boolean(getNavigationRuntime()?.functions.navigate);
510
530
  const pagesNavigateHref = resolvedHref.startsWith("?") ? resolvePagesLinkNavigationHref(resolvedHref, locale) : navigateHref;
531
+ const pagesAsForLink = typeof as === "string" && typeof routeHrefRaw === "string" && as !== routeHrefRaw ? pagesNavigateHref : void 0;
532
+ const pagesHrefForLink = pagesAsForLink === void 0 ? pagesNavigateHref : routeHrefRaw;
511
533
  const absoluteFullHref = toBrowserNavigationHref(hasAppNavigationRuntime ? navigateHref : pagesNavigateHref, window.location.href, __basePath);
512
534
  if (onNavigate) try {
513
535
  const navUrl = new URL(absoluteFullHref, window.location.origin);
@@ -546,7 +568,8 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
546
568
  router: Router && "reload" in Router ? Router : void 0,
547
569
  loadRouter: async () => (await import("next/router.js")).default,
548
570
  navigation: {
549
- href: pagesNavigateHref,
571
+ href: pagesHrefForLink,
572
+ as: pagesAsForLink,
550
573
  replace,
551
574
  scroll,
552
575
  shallow,
@@ -0,0 +1,40 @@
1
+ import * as React$1 from "react";
2
+
3
+ //#region src/shims/navigation-context-state.d.ts
4
+ /**
5
+ * Map of parallel route key to child segments below the current layout.
6
+ * The "children" key is always present.
7
+ */
8
+ type SegmentMap = Readonly<Record<string, string[]>> & {
9
+ readonly children: string[];
10
+ };
11
+ type NavigationContext = {
12
+ pathname: string;
13
+ searchParams: URLSearchParams;
14
+ params: Record<string, string | string[]>;
15
+ };
16
+ declare const ServerInsertedHTMLContext: React$1.Context<((callback: () => unknown) => void) | null> | null;
17
+ declare function getLayoutSegmentContext(): React$1.Context<SegmentMap> | null;
18
+ declare function getBfcacheIdMapContext(): React$1.Context<Readonly<Record<string, string>> | null> | null;
19
+ declare function getBfcacheSegmentIdContext(): React$1.Context<string | null> | null;
20
+ type NavigationStateAccessors = {
21
+ getServerContext: () => NavigationContext | null;
22
+ setServerContext: (context: NavigationContext | null) => void;
23
+ getInsertedHTMLCallbacks: () => Array<() => unknown>;
24
+ clearInsertedHTMLCallbacks: () => void;
25
+ };
26
+ declare const GLOBAL_ACCESSORS_KEY: unique symbol;
27
+ declare function clearClientHydrationContext(): void;
28
+ /**
29
+ * Register request-scoped accessors supplied by navigation-state.ts.
30
+ * The global accessor key also bridges separate Vite module instances.
31
+ */
32
+ declare function _registerStateAccessors(accessors: NavigationStateAccessors): void;
33
+ declare function getNavigationContext(): NavigationContext | null;
34
+ declare function setNavigationContext(context: NavigationContext | null): void;
35
+ declare function registerServerInsertedHTMLCallback(callback: () => unknown): void;
36
+ declare function flushServerInsertedHTML(): unknown[];
37
+ declare function renderServerInsertedHTML(): unknown[];
38
+ declare function clearServerInsertedHTML(): void;
39
+ //#endregion
40
+ export { GLOBAL_ACCESSORS_KEY, NavigationContext, NavigationStateAccessors, SegmentMap, ServerInsertedHTMLContext, _registerStateAccessors, clearClientHydrationContext, clearServerInsertedHTML, flushServerInsertedHTML, getBfcacheIdMapContext, getBfcacheSegmentIdContext, getLayoutSegmentContext, getNavigationContext, registerServerInsertedHTMLCallback, renderServerInsertedHTML, setNavigationContext };