vinext 0.1.0 → 0.1.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 (119) hide show
  1. package/dist/build/assets-ignore.d.ts +32 -0
  2. package/dist/build/assets-ignore.js +48 -0
  3. package/dist/build/client-build-config.d.ts +27 -1
  4. package/dist/build/client-build-config.js +58 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/client/navigation-runtime.d.ts +8 -0
  7. package/dist/client/navigation-runtime.js +1 -1
  8. package/dist/client/vinext-next-data.d.ts +2 -1
  9. package/dist/config/config-matchers.d.ts +20 -1
  10. package/dist/config/config-matchers.js +35 -1
  11. package/dist/config/next-config.d.ts +16 -3
  12. package/dist/config/next-config.js +30 -2
  13. package/dist/deploy.js +40 -304
  14. package/dist/entries/app-rsc-entry.d.ts +8 -2
  15. package/dist/entries/app-rsc-entry.js +54 -4
  16. package/dist/entries/app-rsc-manifest.js +20 -2
  17. package/dist/entries/pages-server-entry.js +9 -1
  18. package/dist/index.js +162 -217
  19. package/dist/plugins/postcss.js +18 -14
  20. package/dist/plugins/require-context.d.ts +6 -0
  21. package/dist/plugins/require-context.js +184 -0
  22. package/dist/routing/app-route-graph.d.ts +12 -1
  23. package/dist/routing/app-route-graph.js +137 -5
  24. package/dist/routing/route-pattern.d.ts +2 -1
  25. package/dist/routing/route-pattern.js +16 -1
  26. package/dist/server/api-handler.js +4 -0
  27. package/dist/server/app-browser-entry.js +84 -39
  28. package/dist/server/app-browser-interception-context.d.ts +2 -1
  29. package/dist/server/app-browser-interception-context.js +15 -2
  30. package/dist/server/app-browser-navigation-controller.d.ts +11 -1
  31. package/dist/server/app-browser-navigation-controller.js +77 -1
  32. package/dist/server/app-browser-popstate.d.ts +12 -3
  33. package/dist/server/app-browser-popstate.js +19 -4
  34. package/dist/server/app-browser-state.d.ts +3 -0
  35. package/dist/server/app-browser-state.js +6 -3
  36. package/dist/server/app-browser-visible-commit.js +9 -7
  37. package/dist/server/app-history-state.d.ts +45 -1
  38. package/dist/server/app-history-state.js +109 -1
  39. package/dist/server/app-page-boundary-render.js +41 -19
  40. package/dist/server/app-page-dispatch.d.ts +6 -0
  41. package/dist/server/app-page-dispatch.js +3 -1
  42. package/dist/server/app-page-element-builder.d.ts +1 -0
  43. package/dist/server/app-page-element-builder.js +22 -10
  44. package/dist/server/app-page-render.d.ts +6 -0
  45. package/dist/server/app-page-render.js +5 -3
  46. package/dist/server/app-page-request.d.ts +8 -6
  47. package/dist/server/app-page-request.js +12 -9
  48. package/dist/server/app-page-response.d.ts +2 -2
  49. package/dist/server/app-page-response.js +1 -1
  50. package/dist/server/app-page-route-wiring.js +2 -1
  51. package/dist/server/app-page-stream.d.ts +37 -2
  52. package/dist/server/app-page-stream.js +36 -3
  53. package/dist/server/app-pages-bridge.d.ts +16 -0
  54. package/dist/server/app-pages-bridge.js +23 -3
  55. package/dist/server/app-route-handler-cache.d.ts +1 -0
  56. package/dist/server/app-route-handler-cache.js +1 -0
  57. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  58. package/dist/server/app-route-handler-dispatch.js +2 -0
  59. package/dist/server/app-route-handler-execution.d.ts +1 -0
  60. package/dist/server/app-route-handler-execution.js +1 -0
  61. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  62. package/dist/server/app-route-handler-runtime.js +3 -2
  63. package/dist/server/app-rsc-handler.d.ts +1 -0
  64. package/dist/server/app-rsc-handler.js +4 -3
  65. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  66. package/dist/server/app-rsc-route-matching.js +29 -4
  67. package/dist/server/app-server-action-execution.d.ts +11 -1
  68. package/dist/server/app-server-action-execution.js +68 -10
  69. package/dist/server/app-ssr-entry.d.ts +6 -0
  70. package/dist/server/app-ssr-entry.js +17 -1
  71. package/dist/server/dev-server.d.ts +1 -1
  72. package/dist/server/dev-server.js +54 -31
  73. package/dist/server/isr-cache.d.ts +37 -1
  74. package/dist/server/isr-cache.js +85 -1
  75. package/dist/server/navigation-planner.js +5 -3
  76. package/dist/server/navigation-trace.d.ts +1 -1
  77. package/dist/server/pages-node-compat.d.ts +2 -0
  78. package/dist/server/pages-node-compat.js +4 -0
  79. package/dist/server/pages-page-data.d.ts +10 -7
  80. package/dist/server/pages-page-data.js +4 -2
  81. package/dist/server/pages-page-handler.d.ts +9 -2
  82. package/dist/server/pages-page-handler.js +29 -16
  83. package/dist/server/pages-page-response.d.ts +11 -2
  84. package/dist/server/pages-page-response.js +8 -1
  85. package/dist/server/pages-readiness.d.ts +36 -0
  86. package/dist/server/pages-readiness.js +21 -0
  87. package/dist/server/pages-request-pipeline.d.ts +99 -0
  88. package/dist/server/pages-request-pipeline.js +209 -0
  89. package/dist/server/pages-revalidate.d.ts +15 -0
  90. package/dist/server/pages-revalidate.js +19 -0
  91. package/dist/server/prod-server.d.ts +6 -2
  92. package/dist/server/prod-server.js +101 -217
  93. package/dist/server/socket-error-backstop.d.ts +19 -1
  94. package/dist/server/socket-error-backstop.js +77 -4
  95. package/dist/shims/app-router-scroll.js +22 -4
  96. package/dist/shims/cache-runtime.js +31 -1
  97. package/dist/shims/error-boundary.d.ts +21 -11
  98. package/dist/shims/error-boundary.js +8 -1
  99. package/dist/shims/fetch-cache.d.ts +14 -1
  100. package/dist/shims/fetch-cache.js +18 -1
  101. package/dist/shims/hash-scroll.d.ts +1 -0
  102. package/dist/shims/hash-scroll.js +3 -1
  103. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  104. package/dist/shims/internal/link-status-registry.js +42 -0
  105. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  106. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  107. package/dist/shims/internal/utils.d.ts +1 -0
  108. package/dist/shims/link.js +20 -6
  109. package/dist/shims/navigation.d.ts +2 -2
  110. package/dist/shims/navigation.js +63 -7
  111. package/dist/shims/router-state.d.ts +1 -0
  112. package/dist/shims/router-state.js +2 -0
  113. package/dist/shims/router.d.ts +6 -3
  114. package/dist/shims/router.js +128 -21
  115. package/dist/utils/client-build-manifest.d.ts +8 -1
  116. package/dist/utils/client-build-manifest.js +30 -5
  117. package/dist/utils/client-entry-manifest.d.ts +11 -0
  118. package/dist/utils/client-entry-manifest.js +29 -0
  119. package/package.json +5 -1
@@ -74,6 +74,7 @@ function useChildSegments(parallelRoutesKey = "children") {
74
74
  }
75
75
  const _READONLY_SEARCH_PARAMS = Symbol("vinext.navigation.readonlySearchParams");
76
76
  const _READONLY_SEARCH_PARAMS_SOURCE = Symbol("vinext.navigation.readonlySearchParamsSource");
77
+ const _READONLY_SEARCH_PARAMS_SOURCE_KEY = Symbol("vinext.navigation.readonlySearchParamsSourceKey");
77
78
  const GLOBAL_ACCESSORS_KEY = Symbol.for("vinext.navigation.globalAccessors");
78
79
  const _GLOBAL_ACCESSORS_KEY = GLOBAL_ACCESSORS_KEY;
79
80
  const _GLOBAL_HYDRATION_CONTEXT_KEY = Symbol.for("vinext.navigation.clientHydrationContext");
@@ -87,6 +88,9 @@ function _getClientHydrationContext() {
87
88
  function _setClientHydrationContext(ctx) {
88
89
  globalThis[_GLOBAL_HYDRATION_CONTEXT_KEY] = ctx;
89
90
  }
91
+ function clearClientHydrationContext() {
92
+ if (typeof window !== "undefined") _setClientHydrationContext(null);
93
+ }
90
94
  let _serverContext = null;
91
95
  let _serverInsertedHTMLCallbacks = [];
92
96
  let _getServerContext = () => {
@@ -127,6 +131,7 @@ function _registerStateAccessors(accessors) {
127
131
  _clearInsertedHTMLCallbacks = accessors.clearInsertedHTMLCallbacks;
128
132
  }
129
133
  const PAGES_NAVIGATION_ACCESSOR_KEY = Symbol.for("vinext.navigation.pagesNavigationContextAccessor");
134
+ const PAGES_NAVIGATION_NOTIFY_KEY = Symbol.for("vinext.navigation.pagesNavigationNotify");
130
135
  function _getPagesNavigationContext() {
131
136
  const accessor = globalThis[PAGES_NAVIGATION_ACCESSOR_KEY];
132
137
  if (!accessor) return null;
@@ -583,7 +588,25 @@ function notifyNavigationListeners() {
583
588
  if (!state) return;
584
589
  for (const fn of state.listeners) fn();
585
590
  }
591
+ if (!isServer) globalThis[PAGES_NAVIGATION_NOTIFY_KEY] = notifyNavigationListeners;
586
592
  let _cachedEmptyServerSearchParams = null;
593
+ const _readonlyPagesSearchParamsCache = /* @__PURE__ */ new WeakMap();
594
+ let _cachedReadonlyPagesSearchParamsKey = null;
595
+ let _cachedReadonlyPagesSearchParams = null;
596
+ function getReadonlyPagesSearchParams(searchParams) {
597
+ const cached = _readonlyPagesSearchParamsCache.get(searchParams);
598
+ if (cached) return cached;
599
+ const key = searchParams.toString();
600
+ if (_cachedReadonlyPagesSearchParamsKey === key && _cachedReadonlyPagesSearchParams) {
601
+ _readonlyPagesSearchParamsCache.set(searchParams, _cachedReadonlyPagesSearchParams);
602
+ return _cachedReadonlyPagesSearchParams;
603
+ }
604
+ const readonly = new ReadonlyURLSearchParams(searchParams);
605
+ _readonlyPagesSearchParamsCache.set(searchParams, readonly);
606
+ _cachedReadonlyPagesSearchParamsKey = key;
607
+ _cachedReadonlyPagesSearchParams = readonly;
608
+ return readonly;
609
+ }
587
610
  /**
588
611
  * Get cached pathname snapshot for useSyncExternalStore.
589
612
  * Note: Returns cached value from ClientNavigationState, not live window.location.
@@ -593,6 +616,8 @@ let _cachedEmptyServerSearchParams = null;
593
616
  * be visible until the next commit.
594
617
  */
595
618
  function getPathnameSnapshot() {
619
+ const pagesCtx = _getPagesNavigationContext();
620
+ if (pagesCtx) return pagesCtx.pathname;
596
621
  return getClientNavigationState()?.cachedPathname ?? "/";
597
622
  }
598
623
  let _cachedEmptyClientSearchParams = null;
@@ -605,6 +630,9 @@ let _cachedEmptyClientSearchParams = null;
605
630
  * be visible until the next commit.
606
631
  */
607
632
  function getSearchParamsSnapshot() {
633
+ if (_getServerContext()) return getServerSearchParamsSnapshot();
634
+ const pagesCtx = _getPagesNavigationContext();
635
+ if (pagesCtx) return getReadonlyPagesSearchParams(pagesCtx.searchParams);
608
636
  const cached = getClientNavigationState()?.cachedReadonlySearchParams;
609
637
  if (cached) return cached;
610
638
  if (_cachedEmptyClientSearchParams === null) _cachedEmptyClientSearchParams = new ReadonlyURLSearchParams();
@@ -631,7 +659,7 @@ function getServerSearchParamsSnapshot() {
631
659
  const ctx = _getServerContext();
632
660
  if (!ctx) {
633
661
  const pagesCtx = _getPagesNavigationContext();
634
- if (pagesCtx) return new ReadonlyURLSearchParams(pagesCtx.searchParams);
662
+ if (pagesCtx) return getReadonlyPagesSearchParams(pagesCtx.searchParams);
635
663
  if (_cachedEmptyServerSearchParams === null) _cachedEmptyServerSearchParams = new ReadonlyURLSearchParams();
636
664
  return _cachedEmptyServerSearchParams;
637
665
  }
@@ -639,9 +667,15 @@ function getServerSearchParamsSnapshot() {
639
667
  const cached = ctx[_READONLY_SEARCH_PARAMS];
640
668
  const cachedSource = ctx[_READONLY_SEARCH_PARAMS_SOURCE];
641
669
  if (cached && cachedSource === source) return cached;
670
+ const sourceKey = source.toString();
671
+ if (cached && ctx[_READONLY_SEARCH_PARAMS_SOURCE_KEY] === sourceKey) {
672
+ ctx[_READONLY_SEARCH_PARAMS_SOURCE] = source;
673
+ return cached;
674
+ }
642
675
  const readonly = new ReadonlyURLSearchParams(source);
643
676
  ctx[_READONLY_SEARCH_PARAMS] = readonly;
644
677
  ctx[_READONLY_SEARCH_PARAMS_SOURCE] = source;
678
+ ctx[_READONLY_SEARCH_PARAMS_SOURCE_KEY] = sourceKey;
645
679
  return readonly;
646
680
  }
647
681
  /**
@@ -742,15 +776,18 @@ function clearPendingPathname(navId) {
742
776
  }
743
777
  function getClientParamsSnapshot() {
744
778
  const state = getClientNavigationState();
779
+ const ctx = _getServerContext();
780
+ if (ctx) return ctx.params;
745
781
  const pagesCtx = _getPagesNavigationContext();
746
782
  if (pagesCtx) return pagesCtx.params;
747
- if (state && Object.keys(state.clientParams).length > 0) return state.clientParams;
748
783
  return state?.clientParams ?? _EMPTY_PARAMS;
749
784
  }
750
785
  function getServerParamsSnapshot() {
751
786
  const ctx = _getServerContext();
752
787
  if (ctx) return ctx.params;
753
- return _getPagesNavigationContext()?.params ?? _EMPTY_PARAMS;
788
+ const pagesCtx = _getPagesNavigationContext();
789
+ if (pagesCtx) return pagesCtx.params;
790
+ return _EMPTY_PARAMS;
754
791
  }
755
792
  function subscribeToNavigation(cb) {
756
793
  const state = getClientNavigationState();
@@ -768,10 +805,16 @@ function usePathname() {
768
805
  if (isServer) {
769
806
  const ctx = _getServerContext();
770
807
  if (ctx) return ctx.pathname;
771
- return _getPagesNavigationContext()?.pathname ?? "/";
808
+ const pagesCtx = _getPagesNavigationContext();
809
+ return pagesCtx ? pagesCtx.pathname : "/";
772
810
  }
773
811
  const renderSnapshot = useClientNavigationRenderSnapshot();
774
- const pathname = React$1.useSyncExternalStore(subscribeToNavigation, getPathnameSnapshot, () => _getServerContext()?.pathname ?? _getPagesNavigationContext()?.pathname ?? "/");
812
+ const pathname = React$1.useSyncExternalStore(subscribeToNavigation, getPathnameSnapshot, () => {
813
+ const ctx = _getServerContext();
814
+ if (ctx) return ctx.pathname;
815
+ const pagesCtx = _getPagesNavigationContext();
816
+ return pagesCtx ? pagesCtx.pathname : "/";
817
+ });
775
818
  if (renderSnapshot && (getClientNavigationState()?.navigationSnapshotActiveCount ?? 0) > 0) return renderSnapshot.pathname;
776
819
  return pathname;
777
820
  }
@@ -833,11 +876,13 @@ function commitClientNavigationState(navId, options) {
833
876
  if (!state) return;
834
877
  if ((navId !== void 0 || options?.releaseSnapshot === true) && state.navigationSnapshotActiveCount > 0) state.navigationSnapshotActiveCount -= 1;
835
878
  const urlChanged = syncCommittedUrlStateFromLocation();
879
+ let paramsChanged = false;
836
880
  if (state.pendingClientParams !== null && state.pendingClientParamsJson !== null) {
837
881
  state.clientParams = state.pendingClientParams;
838
882
  state.clientParamsJson = state.pendingClientParamsJson;
839
883
  state.pendingClientParams = null;
840
884
  state.pendingClientParamsJson = null;
885
+ paramsChanged = true;
841
886
  }
842
887
  if (state.pendingPathnameNavId === null || navId !== void 0 && state.pendingPathnameNavId === navId) {
843
888
  state.pendingPathname = null;
@@ -845,6 +890,7 @@ function commitClientNavigationState(navId, options) {
845
890
  }
846
891
  const shouldNotify = urlChanged || state.hasPendingNavigationUpdate;
847
892
  state.hasPendingNavigationUpdate = false;
893
+ if (urlChanged || paramsChanged) clearClientHydrationContext();
848
894
  if (shouldNotify) notifyNavigationListeners();
849
895
  }
850
896
  function pushHistoryStateWithoutNotify(data, unused, url) {
@@ -918,6 +964,7 @@ function restoreScrollPosition(state) {
918
964
  * Navigate to a URL, handling external URLs, hash-only changes, and RSC navigation.
919
965
  */
920
966
  async function navigateClientSide(href, mode, scroll, programmaticTransition = false) {
967
+ getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
921
968
  let normalizedHref = href;
922
969
  if (isExternalUrl(href)) {
923
970
  const localPath = toSameOriginAppPath(href, __basePath);
@@ -1512,6 +1559,9 @@ if (!isServer) {
1512
1559
  const state = getClientNavigationState();
1513
1560
  if (state && !state.patchInstalled) {
1514
1561
  state.patchInstalled = true;
1562
+ window.addEventListener("popstate", () => {
1563
+ getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
1564
+ });
1515
1565
  window.addEventListener("popstate", (event) => {
1516
1566
  if (!hasAppNavigationRuntime()) {
1517
1567
  commitClientNavigationState();
@@ -1520,11 +1570,17 @@ if (!isServer) {
1520
1570
  });
1521
1571
  window.history.pushState = function patchedPushState(data, unused, url) {
1522
1572
  state.originalPushState.call(window.history, createExternalHistoryStatePreservingMetadata(data, window.history.state), unused, url);
1523
- if (state.suppressUrlNotifyCount === 0) commitClientNavigationState();
1573
+ if (state.suppressUrlNotifyCount === 0) {
1574
+ getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
1575
+ commitClientNavigationState();
1576
+ }
1524
1577
  };
1525
1578
  window.history.replaceState = function patchedReplaceState(data, unused, url) {
1526
1579
  state.originalReplaceState.call(window.history, createExternalHistoryStatePreservingMetadata(data, window.history.state), unused, url);
1527
- if (state.suppressUrlNotifyCount === 0) commitClientNavigationState();
1580
+ if (state.suppressUrlNotifyCount === 0) {
1581
+ getNavigationRuntime()?.functions.notifyLinkNavigationStart?.();
1582
+ commitClientNavigationState();
1583
+ }
1528
1584
  };
1529
1585
  }
1530
1586
  }
@@ -12,6 +12,7 @@ type SSRContext = {
12
12
  pathname: string;
13
13
  query: Record<string, string | string[]>;
14
14
  asPath: string;
15
+ navigationIsReady?: boolean;
15
16
  locale?: string;
16
17
  locales?: string[];
17
18
  defaultLocale?: string;
@@ -1,6 +1,7 @@
1
1
  import { getOrCreateAls } from "./internal/als-registry.js";
2
2
  import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation } from "./unified-request-context.js";
3
3
  import { _registerRouterStateAccessors } from "./router.js";
4
+ import { registerRoutePatternForWarningAccessor } from "./internal/route-pattern-for-warning.js";
4
5
  //#region src/shims/router-state.ts
5
6
  /**
6
7
  * Server-only Pages Router state backed by AsyncLocalStorage.
@@ -33,5 +34,6 @@ _registerRouterStateAccessors({
33
34
  _getState().ssrContext = ctx;
34
35
  }
35
36
  });
37
+ registerRoutePatternForWarningAccessor(() => _getState().ssrContext?.pathname ?? null);
36
38
  //#endregion
37
39
  export { runWithRouterState };
@@ -61,6 +61,8 @@ type SSRContext = {
61
61
  pathname: string;
62
62
  query: Record<string, string | string[]>;
63
63
  asPath: string;
64
+ navigationIsReady?: boolean;
65
+ nextData?: VinextNextData;
64
66
  locale?: string;
65
67
  locales?: string[];
66
68
  defaultLocale?: string;
@@ -84,9 +86,9 @@ declare function _registerRouterStateAccessors(accessors: {
84
86
  }): void;
85
87
  declare function setSSRContext(ctx: SSRContext | null): void;
86
88
  type PagesNavigationContextShape = {
87
- pathname: string;
89
+ pathname: string | null;
88
90
  searchParams: URLSearchParams;
89
- params: Record<string, string | string[]>;
91
+ params: Record<string, string | string[]> | null;
90
92
  };
91
93
  /**
92
94
  * Cross-router compat shim source for `next/navigation` hooks.
@@ -105,6 +107,7 @@ type PagesNavigationContextShape = {
105
107
  * treat null as "App Router context, use normal app-router state".
106
108
  */
107
109
  declare function getPagesNavigationContext(): PagesNavigationContextShape | null;
110
+ declare function getPagesNavigationIsReadyFromSerializedState(routePattern: string | undefined, searchString: string, nextData?: VinextNextData): boolean;
108
111
  /**
109
112
  * useRouter hook - Pages Router compatible.
110
113
  *
@@ -173,4 +176,4 @@ declare const RouterMethods: {
173
176
  };
174
177
  declare const Router: typeof RouterMethods & Omit<NextRouter, keyof typeof RouterMethods>;
175
178
  //#endregion
176
- export { ExcludeRouterProps, NextRouter, WithRouterProps, _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
179
+ export { ExcludeRouterProps, NextRouter, WithRouterProps, _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, getPagesNavigationIsReadyFromSerializedState, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
@@ -247,15 +247,19 @@ function _registerRouterStateAccessors(accessors) {
247
247
  function setSSRContext(ctx) {
248
248
  _setSSRContextImpl(ctx);
249
249
  }
250
+ const PAGES_NAVIGATION_NOTIFY_KEY = Symbol.for("vinext.navigation.pagesNavigationNotify");
250
251
  let _cachedClientPagesNavCtx = null;
251
252
  let _cachedClientPagesNavCtxKey = null;
252
- function _buildClientPagesNavigationContext(routePattern, resolvedPath, searchString) {
253
- const cacheKey = `${routePattern}|${resolvedPath}|${searchString}`;
253
+ function _buildClientPagesNavigationContext(routePattern, resolvedPath, searchString, isReady, nextData) {
254
+ const cacheKey = `${isReady ? "1" : "0"}|${routePattern}|${resolvedPath}|${searchString}`;
254
255
  if (_cachedClientPagesNavCtxKey === cacheKey && _cachedClientPagesNavCtx) return _cachedClientPagesNavCtx;
256
+ const searchParams = isReady ? new URLSearchParams(searchString) : new URLSearchParams();
257
+ const params = isReady ? extractRouteParamsFromPath(routePattern, resolvedPath) ?? getRouteParamsFromQuery(routePattern, nextData?.query ?? {}) ?? {} : null;
258
+ const isAutoExportDynamic = nextData?.autoExport === true && extractRouteParamNames(routePattern).length > 0;
255
259
  const ctx = {
256
- pathname: resolvedPath,
257
- searchParams: new URLSearchParams(searchString),
258
- params: routePattern ? extractRouteParamsFromPath(routePattern, resolvedPath) ?? {} : {}
260
+ pathname: resolvePagesNavigationPathname(resolvedPath, nextData?.isFallback === true, isAutoExportDynamic, isReady),
261
+ searchParams,
262
+ params
259
263
  };
260
264
  _cachedClientPagesNavCtx = ctx;
261
265
  _cachedClientPagesNavCtxKey = cacheKey;
@@ -284,27 +288,33 @@ function getPagesNavigationContext() {
284
288
  if (!ssrCtx) return null;
285
289
  const cached = _ssrPagesNavCtxCache.get(ssrCtx);
286
290
  if (cached) return cached;
287
- let searchParams;
291
+ let searchString = "";
288
292
  let resolvedPath;
289
293
  try {
290
294
  const url = new URL(ssrCtx.asPath, "http://_");
291
- searchParams = url.searchParams;
295
+ searchString = url.search;
292
296
  resolvedPath = url.pathname;
293
297
  } catch {
294
- searchParams = new URLSearchParams();
295
298
  resolvedPath = ssrCtx.pathname;
296
299
  }
297
- const params = extractRouteParamsFromPath(ssrCtx.pathname, resolvedPath) ?? {};
300
+ const isReady = ssrCtx.navigationIsReady ?? true;
301
+ const searchParams = isReady ? new URLSearchParams(searchString) : new URLSearchParams();
302
+ const params = isReady ? extractRouteParamsFromPath(ssrCtx.pathname, resolvedPath) ?? getRouteParamsFromQuery(ssrCtx.pathname, ssrCtx.query) ?? {} : null;
303
+ const isAutoExportDynamic = ssrCtx.nextData?.autoExport === true && extractRouteParamNames(ssrCtx.pathname).length > 0;
298
304
  const ctx = {
299
- pathname: resolvedPath,
305
+ pathname: resolvePagesNavigationPathname(resolvedPath, ssrCtx.isFallback === true, isAutoExportDynamic, isReady),
300
306
  searchParams,
301
307
  params
302
308
  };
303
309
  _ssrPagesNavCtxCache.set(ssrCtx, ctx);
304
310
  return ctx;
305
311
  }
312
+ if (!isPagesRouterDocumentActive()) return null;
306
313
  const resolvedPath = stripBasePath(window.location.pathname, __basePath);
307
- return _buildClientPagesNavigationContext(window.__NEXT_DATA__?.page ?? "", resolvedPath, window.location.search);
314
+ const nextData = window.__NEXT_DATA__;
315
+ const pattern = resolvePagesRoutePatternForPath(nextData?.page, resolvedPath);
316
+ if (!pattern) return null;
317
+ return _buildClientPagesNavigationContext(pattern, resolvedPath, window.location.search, isPagesRouterReady(), nextData);
308
318
  }
309
319
  /**
310
320
  * Extract param names from a Next.js route pattern.
@@ -321,9 +331,52 @@ function extractRouteParamNames(pattern) {
321
331
  for (const m of colonMatches) names.push(m[1]);
322
332
  return names;
323
333
  }
334
+ /**
335
+ * Resolve the `pathname` snapshot for the Pages Router navigation context.
336
+ * Shared by the client and SSR branches of `getPagesNavigationContext` so both
337
+ * runtimes derive identical null-ness — diverging here would reintroduce a
338
+ * hydration mismatch. Returns `null` for a `getStaticPaths` fallback shell or a
339
+ * pre-ready auto-export dynamic route (the live path is published once the
340
+ * client router becomes ready).
341
+ */
342
+ function resolvePagesNavigationPathname(resolvedPath, isFallback, isAutoExportDynamic, isReady) {
343
+ return isFallback || isAutoExportDynamic && !isReady ? null : resolvedPath;
344
+ }
345
+ let _cachedPagesRoutePatternKey = null;
346
+ let _cachedPagesRoutePattern;
347
+ function resolvePagesRoutePatternForPath(nextDataPage, resolvedPath) {
348
+ if (nextDataPage && extractRouteParamNames(nextDataPage).length > 0) return nextDataPage;
349
+ const cacheKey = `${nextDataPage ?? ""}|${resolvedPath}`;
350
+ if (_cachedPagesRoutePatternKey === cacheKey) return _cachedPagesRoutePattern;
351
+ let resolved = nextDataPage;
352
+ for (const pattern of window.__VINEXT_PAGE_PATTERNS__ ?? []) if (matchRoutePattern(splitPathSegments(resolvedPath), routePatternParts(pattern))) {
353
+ resolved = pattern;
354
+ break;
355
+ }
356
+ _cachedPagesRoutePatternKey = cacheKey;
357
+ _cachedPagesRoutePattern = resolved;
358
+ return resolved;
359
+ }
324
360
  function extractRouteParamsFromPath(pattern, pathname) {
325
361
  return matchRoutePattern(splitPathSegments(pathname), routePatternParts(pattern));
326
362
  }
363
+ function getRouteParamsFromQuery(pattern, query) {
364
+ const names = extractRouteParamNames(pattern);
365
+ if (names.length === 0) return null;
366
+ const params = {};
367
+ let hasParam = false;
368
+ for (const name of names) {
369
+ const value = query[name];
370
+ if (typeof value === "string") {
371
+ params[name] = value;
372
+ hasParam = true;
373
+ } else if (Array.isArray(value)) {
374
+ params[name] = [...value];
375
+ hasParam = true;
376
+ }
377
+ }
378
+ return hasParam ? params : null;
379
+ }
327
380
  function getRouteQueryFromNextData(nextData, resolvedPath) {
328
381
  const routeQuery = {};
329
382
  if (!nextData?.query || !nextData.page) return routeQuery;
@@ -372,6 +425,47 @@ function getPathnameAndQuery() {
372
425
  asPath: resolvedPath + window.location.search + window.location.hash
373
426
  };
374
427
  }
428
+ function getPagesNavigationIsReadyFromSerializedState(routePattern, searchString, nextData) {
429
+ if (!routePattern) return true;
430
+ if (nextData?.gssp === true || nextData?.gip === true || nextData?.isExperimentalCompile === true || nextData?.appGip === true && nextData.gsp !== true) return true;
431
+ const autoExportDynamic = nextData?.autoExport === true && extractRouteParamNames(routePattern).length > 0;
432
+ const hasSearch = searchString.length > 0;
433
+ const hasRewrites = nextData?.__vinext?.hasRewrites === true;
434
+ return !autoExportDynamic && !hasSearch && !hasRewrites;
435
+ }
436
+ function shouldDeferInitialPagesRouterReady() {
437
+ if (typeof window === "undefined") return false;
438
+ const nextData = window.__NEXT_DATA__;
439
+ if (!nextData) return false;
440
+ return !getPagesNavigationIsReadyFromSerializedState(nextData.page, window.location.search, nextData);
441
+ }
442
+ let _pagesRouterReady = typeof window === "undefined" ? true : !shouldDeferInitialPagesRouterReady();
443
+ function isPagesRouterReady() {
444
+ return _pagesRouterReady;
445
+ }
446
+ function isPagesRouterDocumentActive() {
447
+ if (typeof window === "undefined") return true;
448
+ if (window.__VINEXT_PAGE_LOADERS__) return true;
449
+ if (window.next?.appDir === true) return false;
450
+ if (window.next?.router) return true;
451
+ return Boolean(window.__VINEXT_APP__ || window.__VINEXT_APP_LOADER__);
452
+ }
453
+ function markPagesRouterReady() {
454
+ if (typeof window === "undefined" || _pagesRouterReady) return false;
455
+ _pagesRouterReady = true;
456
+ return true;
457
+ }
458
+ function getRouterSnapshot() {
459
+ const isReady = typeof window === "undefined" ? _getSSRContext()?.navigationIsReady ?? true : isPagesRouterReady();
460
+ return {
461
+ ...getPathnameAndQuery(),
462
+ isReady
463
+ };
464
+ }
465
+ function notifyNextNavigationPagesContext() {
466
+ const notify = globalThis[PAGES_NAVIGATION_NOTIFY_KEY];
467
+ notify?.();
468
+ }
375
469
  /**
376
470
  * Error thrown when a navigation is superseded by a newer one.
377
471
  * Matches Next.js's convention of an Error with `.cancelled = true`.
@@ -779,7 +873,7 @@ async function runNavigateClient(fullUrl, resolvedUrl, fetchUrl = fullUrl, optio
779
873
  * and a set of navigation methods. Shared by the Pages Router context provider
780
874
  * and tests so the public router shape stays in sync.
781
875
  */
782
- function buildRouterValue(pathname, query, asPath, methods) {
876
+ function buildRouterValue(pathname, query, asPath, isReady, methods) {
783
877
  const _ssrState = _getSSRContext();
784
878
  const nextData = typeof window !== "undefined" ? window.__NEXT_DATA__ : void 0;
785
879
  const locale = typeof window === "undefined" ? _ssrState?.locale : window.__VINEXT_LOCALE__;
@@ -796,7 +890,7 @@ function buildRouterValue(pathname, query, asPath, methods) {
796
890
  locales,
797
891
  defaultLocale,
798
892
  domainLocales,
799
- isReady: true,
893
+ isReady,
800
894
  isPreview: false,
801
895
  isFallback: typeof window !== "undefined" ? nextData?.isFallback === true : _ssrState?.isFallback === true,
802
896
  ...methods,
@@ -815,6 +909,7 @@ function stripHash(url) {
815
909
  }
816
910
  /** Notify in-page listeners (e.g. useRouter hooks) that navigation occurred. */
817
911
  function dispatchNavigateEvent() {
912
+ notifyNextNavigationPagesContext();
818
913
  window.dispatchEvent(new CustomEvent("vinext:navigate"));
819
914
  }
820
915
  /**
@@ -984,15 +1079,25 @@ function useRouter() {
984
1079
  return router;
985
1080
  }
986
1081
  function PagesRouterProvider({ children }) {
987
- const [{ pathname, query, asPath }, setState] = useState(getPathnameAndQuery);
1082
+ const [{ pathname, query, asPath, isReady }, setState] = useState(getRouterSnapshot);
988
1083
  useEffect(() => {
989
1084
  const onNavigate = ((_e) => {
990
- setState(getPathnameAndQuery());
1085
+ setState(getRouterSnapshot());
991
1086
  });
992
1087
  window.addEventListener("vinext:navigate", onNavigate);
993
- return () => window.removeEventListener("vinext:navigate", onNavigate);
1088
+ let cancelled = false;
1089
+ const readyTimer = window.setTimeout(() => {
1090
+ if (cancelled || !markPagesRouterReady()) return;
1091
+ setState(getRouterSnapshot());
1092
+ notifyNextNavigationPagesContext();
1093
+ }, 0);
1094
+ return () => {
1095
+ cancelled = true;
1096
+ window.clearTimeout(readyTimer);
1097
+ window.removeEventListener("vinext:navigate", onNavigate);
1098
+ };
994
1099
  }, []);
995
- const router = useMemo(() => buildRouterValue(pathname, query, asPath, {
1100
+ const router = useMemo(() => buildRouterValue(pathname, query, asPath, isReady, {
996
1101
  push: Router.push,
997
1102
  replace: Router.replace,
998
1103
  back: Router.back,
@@ -1002,7 +1107,8 @@ function PagesRouterProvider({ children }) {
1002
1107
  }), [
1003
1108
  pathname,
1004
1109
  query,
1005
- asPath
1110
+ asPath,
1111
+ isReady
1006
1112
  ]);
1007
1113
  const appRouter = useMemo(() => ({
1008
1114
  bfcacheId: "0",
@@ -1215,8 +1321,9 @@ const Router = Object.defineProperties(RouterMethods, {
1215
1321
  },
1216
1322
  isReady: {
1217
1323
  enumerable: true,
1218
- value: true,
1219
- writable: false
1324
+ get() {
1325
+ return isPagesRouterReady();
1326
+ }
1220
1327
  },
1221
1328
  isPreview: {
1222
1329
  enumerable: true,
@@ -1235,4 +1342,4 @@ if (typeof window !== "undefined") installWindowNext({ router: Router });
1235
1342
  const _PAGES_NAVIGATION_ACCESSOR_KEY = Symbol.for("vinext.navigation.pagesNavigationContextAccessor");
1236
1343
  globalThis[_PAGES_NAVIGATION_ACCESSOR_KEY] = getPagesNavigationContext;
1237
1344
  //#endregion
1238
- export { _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
1345
+ export { _registerRouterStateAccessors, applyNavigationLocale, Router as default, getPagesNavigationContext, getPagesNavigationIsReadyFromSerializedState, isExternalUrl, isHashOnlyChange, setSSRContext, useRouter, withRouter, wrapWithRouterContext };
@@ -4,11 +4,18 @@ import { BuildManifestChunk } from "./lazy-chunks.js";
4
4
  type ClientBuildManifest = Record<string, BuildManifestChunk>;
5
5
  declare function readClientBuildManifest(manifestPath: string): ClientBuildManifest | undefined;
6
6
  declare function findClientEntryFileFromManifest(buildManifest: ClientBuildManifest, assetBase: string): string | undefined;
7
+ declare function findPagesClientEntryFileFromManifest(buildManifest: ClientBuildManifest, assetBase: string): string | undefined;
7
8
  declare function findClientEntryFile(options: {
8
9
  buildManifest?: ClientBuildManifest;
9
10
  clientDir: string;
10
11
  assetsSubdir: string;
11
12
  assetBase: string;
12
13
  }): string | undefined;
14
+ declare function findPagesClientEntryFile(options: {
15
+ buildManifest?: ClientBuildManifest;
16
+ clientDir: string;
17
+ assetsSubdir: string;
18
+ assetBase: string;
19
+ }): string | undefined;
13
20
  //#endregion
14
- export { findClientEntryFile, findClientEntryFileFromManifest, readClientBuildManifest };
21
+ export { findClientEntryFile, findClientEntryFileFromManifest, findPagesClientEntryFile, findPagesClientEntryFileFromManifest, readClientBuildManifest };
@@ -3,7 +3,8 @@ import { manifestFileWithBase } from "./manifest-paths.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  //#region src/utils/client-build-manifest.ts
6
- const CLIENT_ENTRY_MARKERS = ["vinext-client-entry", "vinext-app-browser-entry"];
6
+ const PAGES_CLIENT_ENTRY_MARKERS = ["vinext-client-entry"];
7
+ const CLIENT_ENTRY_MARKERS = [...PAGES_CLIENT_ENTRY_MARKERS, "vinext-app-browser-entry"];
7
8
  function readClientBuildManifest(manifestPath) {
8
9
  if (!fs.existsSync(manifestPath)) return void 0;
9
10
  try {
@@ -32,21 +33,45 @@ function readClientBuildManifest(manifestPath) {
32
33
  }
33
34
  }
34
35
  function findClientEntryFileFromManifest(buildManifest, assetBase) {
36
+ return findEntryFileFromManifest(buildManifest, assetBase, CLIENT_ENTRY_MARKERS, true);
37
+ }
38
+ function findPagesClientEntryFileFromManifest(buildManifest, assetBase) {
39
+ return findEntryFileFromManifest(buildManifest, assetBase, PAGES_CLIENT_ENTRY_MARKERS, false);
40
+ }
41
+ function findEntryFileFromManifest(buildManifest, assetBase, markers, fallbackToFirstEntry) {
35
42
  const entries = Object.values(buildManifest).filter((entry) => entry.isEntry && entry.file);
36
- const chosen = entries.find((entry) => CLIENT_ENTRY_MARKERS.some((marker) => entry.file.includes(marker))) ?? entries[0];
43
+ for (const marker of markers) {
44
+ const markedEntry = entries.find((entry) => entry.file.includes(marker));
45
+ if (markedEntry) return manifestFileWithBase(markedEntry.file, assetBase);
46
+ }
47
+ const chosen = fallbackToFirstEntry ? entries[0] : void 0;
37
48
  return chosen ? manifestFileWithBase(chosen.file, assetBase) : void 0;
38
49
  }
39
50
  function findClientEntryFileInAssetsDir(options) {
40
51
  const assetsDir = path.join(options.clientDir, options.assetsSubdir);
41
52
  if (!fs.existsSync(assetsDir)) return void 0;
42
- const entry = fs.readdirSync(assetsDir).find((file) => CLIENT_ENTRY_MARKERS.some((marker) => file.includes(marker)) && file.endsWith(".js"));
53
+ const files = fs.readdirSync(assetsDir);
54
+ let entry;
55
+ for (const marker of options.markers) {
56
+ entry = files.find((file) => file.includes(marker) && file.endsWith(".js"));
57
+ if (entry) break;
58
+ }
43
59
  return entry ? manifestFileWithBase(`${options.assetsSubdir}/${entry}`, options.assetBase) : void 0;
44
60
  }
45
61
  function findClientEntryFile(options) {
46
- return (options.buildManifest ? findClientEntryFileFromManifest(options.buildManifest, options.assetBase) : void 0) ?? findClientEntryFileInAssetsDir(options);
62
+ return (options.buildManifest ? findClientEntryFileFromManifest(options.buildManifest, options.assetBase) : void 0) ?? findClientEntryFileInAssetsDir({
63
+ ...options,
64
+ markers: CLIENT_ENTRY_MARKERS
65
+ });
66
+ }
67
+ function findPagesClientEntryFile(options) {
68
+ return (options.buildManifest ? findPagesClientEntryFileFromManifest(options.buildManifest, options.assetBase) : void 0) ?? findClientEntryFileInAssetsDir({
69
+ ...options,
70
+ markers: PAGES_CLIENT_ENTRY_MARKERS
71
+ });
47
72
  }
48
73
  function readStringArray(value) {
49
74
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
50
75
  }
51
76
  //#endregion
52
- export { findClientEntryFile, findClientEntryFileFromManifest, readClientBuildManifest };
77
+ export { findClientEntryFile, findClientEntryFileFromManifest, findPagesClientEntryFile, findPagesClientEntryFileFromManifest, readClientBuildManifest };
@@ -0,0 +1,11 @@
1
+ //#region src/utils/client-entry-manifest.d.ts
2
+ declare const VINEXT_CLIENT_ENTRY_MANIFEST = "vinext-client-entry-manifest.json";
3
+ type ClientEntryManifest = {
4
+ pagesClientEntry?: string;
5
+ appBrowserEntry?: string;
6
+ };
7
+ declare function readClientEntryManifest(clientDir: string): ClientEntryManifest | undefined;
8
+ declare function findClientEntryFileFromVinextManifest(manifest: ClientEntryManifest | undefined, assetBase: string): string | undefined;
9
+ declare function findPagesClientEntryFileFromVinextManifest(manifest: ClientEntryManifest | undefined, assetBase: string): string | undefined;
10
+ //#endregion
11
+ export { ClientEntryManifest, VINEXT_CLIENT_ENTRY_MANIFEST, findClientEntryFileFromVinextManifest, findPagesClientEntryFileFromVinextManifest, readClientEntryManifest };
@@ -0,0 +1,29 @@
1
+ import { isUnknownRecord } from "./record.js";
2
+ import { manifestFileWithBase } from "./manifest-paths.js";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ //#region src/utils/client-entry-manifest.ts
6
+ const VINEXT_CLIENT_ENTRY_MANIFEST = "vinext-client-entry-manifest.json";
7
+ function readClientEntryManifest(clientDir) {
8
+ const manifestPath = path.join(clientDir, VINEXT_CLIENT_ENTRY_MANIFEST);
9
+ if (!fs.existsSync(manifestPath)) return void 0;
10
+ try {
11
+ const value = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
12
+ if (!isUnknownRecord(value)) return void 0;
13
+ const manifest = {};
14
+ if (typeof value.pagesClientEntry === "string") manifest.pagesClientEntry = value.pagesClientEntry;
15
+ if (typeof value.appBrowserEntry === "string") manifest.appBrowserEntry = value.appBrowserEntry;
16
+ return manifest.pagesClientEntry || manifest.appBrowserEntry ? manifest : void 0;
17
+ } catch {
18
+ return;
19
+ }
20
+ }
21
+ function findClientEntryFileFromVinextManifest(manifest, assetBase) {
22
+ const entry = manifest?.pagesClientEntry ?? manifest?.appBrowserEntry;
23
+ return entry ? manifestFileWithBase(entry, assetBase) : void 0;
24
+ }
25
+ function findPagesClientEntryFileFromVinextManifest(manifest, assetBase) {
26
+ return manifest?.pagesClientEntry ? manifestFileWithBase(manifest.pagesClientEntry, assetBase) : void 0;
27
+ }
28
+ //#endregion
29
+ export { VINEXT_CLIENT_ENTRY_MANIFEST, findClientEntryFileFromVinextManifest, findPagesClientEntryFileFromVinextManifest, readClientEntryManifest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vinext",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Run Next.js apps on Vite. Drop-in replacement for the next CLI.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -49,6 +49,10 @@
49
49
  "types": "./dist/server/request-pipeline.d.ts",
50
50
  "import": "./dist/server/request-pipeline.js"
51
51
  },
52
+ "./server/pages-request-pipeline": {
53
+ "types": "./dist/server/pages-request-pipeline.d.ts",
54
+ "import": "./dist/server/pages-request-pipeline.js"
55
+ },
52
56
  "./server/app-router-entry": {
53
57
  "types": "./dist/server/app-router-entry.d.ts",
54
58
  "import": "./dist/server/app-router-entry.js"