vinext 0.1.5 → 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 (69) hide show
  1. package/README.md +2 -2
  2. package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
  3. package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
  4. package/dist/build/report.d.ts +1 -2
  5. package/dist/build/report.js +2 -11
  6. package/dist/build/run-prerender.js +1 -1
  7. package/dist/check.js +3 -3
  8. package/dist/client/pages-router-link-navigation.d.ts +10 -0
  9. package/dist/client/pages-router-link-navigation.js +3 -3
  10. package/dist/deploy.js +3 -3
  11. package/dist/entries/app-rsc-entry.js +1 -0
  12. package/dist/entries/app-rsc-manifest.js +76 -46
  13. package/dist/entries/pages-server-entry.js +1 -0
  14. package/dist/index.js +69 -54
  15. package/dist/init.d.ts +5 -4
  16. package/dist/init.js +27 -8
  17. package/dist/plugins/fonts.js +3 -1
  18. package/dist/plugins/middleware-export-validation.d.ts +5 -0
  19. package/dist/plugins/middleware-export-validation.js +36 -0
  20. package/dist/plugins/og-asset-ownership.d.ts +27 -0
  21. package/dist/plugins/og-asset-ownership.js +285 -0
  22. package/dist/plugins/og-assets.js +22 -4
  23. package/dist/server/accept-encoding.d.ts +29 -0
  24. package/dist/server/accept-encoding.js +95 -0
  25. package/dist/server/app-browser-entry.js +8 -5
  26. package/dist/server/app-browser-navigation-controller.js +1 -1
  27. package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
  28. package/dist/server/app-browser-rsc-redirect.js +8 -1
  29. package/dist/server/app-browser-state.js +1 -1
  30. package/dist/server/app-browser-visible-commit.js +1 -1
  31. package/dist/server/app-middleware.d.ts +1 -0
  32. package/dist/server/app-middleware.js +1 -0
  33. package/dist/server/app-page-dispatch.d.ts +2 -2
  34. package/dist/server/app-page-head.js +2 -2
  35. package/dist/server/app-page-request.d.ts +7 -0
  36. package/dist/server/app-page-request.js +19 -1
  37. package/dist/server/app-prerender-endpoints.d.ts +3 -3
  38. package/dist/server/app-prerender-endpoints.js +3 -4
  39. package/dist/server/app-route-module-loader.d.ts +62 -14
  40. package/dist/server/app-route-module-loader.js +61 -10
  41. package/dist/server/app-rsc-handler.d.ts +7 -6
  42. package/dist/server/app-rsc-handler.js +31 -26
  43. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  44. package/dist/server/app-rsc-route-matching.js +13 -0
  45. package/dist/server/app-ssr-entry.js +2 -2
  46. package/dist/server/headers.d.ts +5 -1
  47. package/dist/server/headers.js +5 -1
  48. package/dist/server/metadata-route-response.d.ts +3 -3
  49. package/dist/server/middleware-runtime.d.ts +2 -1
  50. package/dist/server/middleware-runtime.js +19 -8
  51. package/dist/server/middleware.d.ts +2 -2
  52. package/dist/server/middleware.js +26 -11
  53. package/dist/server/pages-request-pipeline.d.ts +3 -1
  54. package/dist/server/pages-request-pipeline.js +4 -2
  55. package/dist/server/prod-server.d.ts +1 -1
  56. package/dist/server/prod-server.js +85 -72
  57. package/dist/shims/font-google-base.d.ts +1 -0
  58. package/dist/shims/font-google-base.js +6 -21
  59. package/dist/shims/internal/interpolate-as.d.ts +25 -0
  60. package/dist/shims/internal/interpolate-as.js +196 -0
  61. package/dist/shims/link.js +24 -1
  62. package/dist/shims/navigation.js +1 -1
  63. package/dist/shims/navigation.react-server.js +1 -1
  64. package/dist/shims/router.d.ts +1 -3
  65. package/dist/shims/router.js +62 -18
  66. package/dist/typegen.js +2 -10
  67. package/dist/utils/project.d.ts +7 -1
  68. package/dist/utils/project.js +16 -2
  69. package/package.json +3 -3
@@ -18,14 +18,15 @@ import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscR
18
18
  import { getNavigationRuntime, registerNavigationRuntimeBootstrap, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
19
19
  import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
20
20
  import "../client/instrumentation-client.js";
21
- import { readHistoryStatePreviousNextUrl } from "./app-history-state.js";
22
- import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-bfcache-identity.js";
23
- import { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult } from "./app-browser-action-result.js";
24
- import { createClientReuseManifestHeaderFromVisibleAppState } from "./app-browser-client-reuse-manifest.js";
25
21
  import { resolveManifestNavigationInterceptionContext, resolveMiddlewareRewriteNavigationInterceptionContext } from "./app-browser-interception-context.js";
22
+ import { readHistoryStatePreviousNextUrl } from "./app-history-state.js";
26
23
  import { AppBrowserMpaNavigationScheduler } from "./app-browser-mpa-navigation.js";
24
+ import { blockDangerousStreamedRscRedirect } from "./app-browser-rsc-redirect.js";
27
25
  import { navigationPlanner } from "./navigation-planner.js";
28
26
  import { __basePath, appRouterInstance, commitClientNavigationState, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, getClientNavigationRenderContext, getPrefetchCache, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, setClientParams, setMountedSlotsHeader, setPendingPathname, useRouter } from "../shims/navigation.js";
27
+ import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./app-bfcache-identity.js";
28
+ import { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult } from "./app-browser-action-result.js";
29
+ import { createClientReuseManifestHeaderFromVisibleAppState } from "./app-browser-client-reuse-manifest.js";
29
30
  import { chunksToReadableStream, createProgressiveRscStream, getVinextBrowserGlobal } from "./app-browser-stream.js";
30
31
  import { FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, isCacheRestorableAppPayloadMetadata, resolveInterceptionContextFromPreviousNextUrl, resolveServerActionRequestState } from "./app-browser-state.js";
31
32
  import { clearHardNavigationLoopGuard, createAppBrowserNavigationController, createBasePathStrippedPathAndSearch } from "./app-browser-navigation-controller.js";
@@ -1032,6 +1033,8 @@ function bootstrapHydration(rscStream) {
1032
1033
  }
1033
1034
  if (!browserNavigationController.isCurrentNavigation(navId)) return;
1034
1035
  const navContentType = navResponse.headers.get("content-type") ?? "";
1036
+ const streamedRedirectTarget = navResponse.headers.get(VINEXT_RSC_REDIRECT_HEADER);
1037
+ if (blockDangerousStreamedRscRedirect(navResponse, streamedRedirectTarget)) return;
1035
1038
  const liveFetchDecision = navigationPlanner.classifyRscFetchResult({
1036
1039
  clientCompatibilityId: CLIENT_RSC_COMPATIBILITY_ID,
1037
1040
  compatibilityIdHeader: navResponse.headers.get(VINEXT_RSC_COMPATIBILITY_ID_HEADER),
@@ -1045,7 +1048,7 @@ function bootstrapHydration(rscStream) {
1045
1048
  responseOk: navResponse.ok,
1046
1049
  responseUrl: navResponseUrl ?? navResponse.url,
1047
1050
  source: "live",
1048
- streamedRedirectTarget: navResponse.headers.get(VINEXT_RSC_REDIRECT_HEADER)
1051
+ streamedRedirectTarget
1049
1052
  });
1050
1053
  if (liveFetchDecision.kind === "hardNavigate") {
1051
1054
  if (liveFetchDecision.discardBody) navResponse.body?.cancel().catch(() => {});
@@ -1,8 +1,8 @@
1
1
  import { stripBasePath } from "../utils/base-path.js";
2
2
  import { clearAppNavigationFailureTarget, getAppNavigationFailureTarget } from "../client/app-nav-failure-handler.js";
3
3
  import { claimAppRouterScrollIntentForCommit, consumeAppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
4
- import { shouldScheduleRefreshForDiscardedServerAction } from "./app-browser-action-result.js";
5
4
  import { activateNavigationSnapshot, clearPendingPathname, commitClientNavigationState, createSnapshotPathAndSearch } from "../shims/navigation.js";
5
+ import { shouldScheduleRefreshForDiscardedServerAction } from "./app-browser-action-result.js";
6
6
  import { FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, createPendingNavigationCommit } from "./app-browser-state.js";
7
7
  import { applyApprovedVisibleCommit, approveHmrVisibleCommit, approvePendingNavigationCommit, resolveAndClassifyNavigationCommit } from "./app-browser-visible-commit.js";
8
8
  import { startTransition, useInsertionEffect, useLayoutEffect } from "react";
@@ -1,4 +1,5 @@
1
1
  //#region src/server/app-browser-rsc-redirect.d.ts
2
+ declare function blockDangerousStreamedRscRedirect(response: Response, streamedRedirectTarget: string | null): boolean;
2
3
  type RscRedirectHistoryUpdateMode = "push" | "replace" | undefined;
3
4
  type RscRedirectLifecycleDecision = {
4
5
  href: string;
@@ -34,4 +35,4 @@ declare function resolveStreamedRscRedirectLifecycleHop(options: {
34
35
  streamedRedirectTarget: string;
35
36
  }): RscRedirectLifecycleDecision;
36
37
  //#endregion
37
- export { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop };
38
+ export { blockDangerousStreamedRscRedirect, resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop };
@@ -1,6 +1,13 @@
1
+ import { isDangerousScheme, reportBlockedDangerousNavigation } from "../shims/url-safety.js";
1
2
  import { resolveHardNavigationTargetFromRscResponse, stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
2
3
  //#region src/server/app-browser-rsc-redirect.ts
3
4
  const MAX_RSC_REDIRECT_DEPTH = 10;
5
+ function blockDangerousStreamedRscRedirect(response, streamedRedirectTarget) {
6
+ if (streamedRedirectTarget === null || !isDangerousScheme(streamedRedirectTarget)) return false;
7
+ response.body?.cancel().catch(() => {});
8
+ reportBlockedDangerousNavigation();
9
+ return true;
10
+ }
4
11
  function toVisibleAppHref(href, origin) {
5
12
  const url = new URL(href, origin);
6
13
  stripRscCacheBustingSearchParam(url);
@@ -54,4 +61,4 @@ function resolveStreamedRscRedirectLifecycleHop(options) {
54
61
  });
55
62
  }
56
63
  //#endregion
57
- export { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop };
64
+ export { blockDangerousStreamedRscRedirect, resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop };
@@ -7,11 +7,11 @@ import { getMountedSlotIds, getMountedSlotIdsHeader } from "./app-elements.js";
7
7
  import { createRscRequestHeaders } from "./app-rsc-cache-busting.js";
8
8
  import { createCacheEntryReuseProof } from "./cache-proof.js";
9
9
  import { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
10
- import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap, createNextBfcacheIdMap, preserveBfcacheIdsForMergedElements } from "./app-bfcache-identity.js";
11
10
  import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
12
11
  import { verifyOperationTokenForCommit } from "./operation-token.js";
13
12
  import { navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts } from "./navigation-planner.js";
14
13
  import { createSnapshotPathAndSearch } from "../shims/navigation.js";
14
+ import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap, createNextBfcacheIdMap, preserveBfcacheIdsForMergedElements } from "./app-bfcache-identity.js";
15
15
  //#region src/server/app-browser-state.ts
16
16
  const FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN = { origin: "fresh" };
17
17
  const VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN = { origin: "visited-cache" };
@@ -1,8 +1,8 @@
1
1
  import { normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
2
2
  import "./app-elements.js";
3
3
  import { mergeElements } from "../shims/slot.js";
4
- import { preserveBfcacheIdsForMergedElements } from "./app-bfcache-identity.js";
5
4
  import { NavigationTraceReasonCodes, NavigationTraceTransactionCodes, createNavigationTrace, prependNavigationTraceEntry } from "./navigation-trace.js";
5
+ import { preserveBfcacheIdsForMergedElements } from "./app-bfcache-identity.js";
6
6
  import { createPendingNavigationCommit, resolvePendingNavigationCommitDispositionDecision } from "./app-browser-state.js";
7
7
  //#region src/server/app-browser-visible-commit.ts
8
8
  const approvedVisibleCommitBrand = Symbol("ApprovedVisibleCommit");
@@ -19,6 +19,7 @@ type ApplyAppMiddlewareOptions = {
19
19
  * INTERNAL_HEADERS and is stripped before this function runs.
20
20
  */
21
21
  isDataRequest?: boolean;
22
+ filePath?: string;
22
23
  isProxy: boolean;
23
24
  module: MiddlewareModule;
24
25
  request: Request;
@@ -117,6 +117,7 @@ async function applyAppMiddleware(options) {
117
117
  const result = await executeMiddleware({
118
118
  basePath: options.basePath,
119
119
  hadBasePath: true,
120
+ filePath: options.filePath,
120
121
  i18nConfig: options.i18nConfig,
121
122
  isDataRequest: options.isDataRequest,
122
123
  isProxy: options.isProxy,
@@ -31,7 +31,7 @@ type AppPageBackgroundRegenerationErrorContext = {
31
31
  };
32
32
  type AppPageBackgroundRegenerator = (key: string, renderFn: () => Promise<void>, errorContext?: AppPageBackgroundRegenerationErrorContext) => void;
33
33
  type AppPageDispatchIntercept<TPage = unknown> = {
34
- interceptLayouts?: readonly AppPageModule[] | null;
34
+ interceptLayouts?: readonly unknown[] | null;
35
35
  matchedParams: AppPageParams;
36
36
  page: TPage;
37
37
  slotId?: string | null;
@@ -41,7 +41,7 @@ type AppPageDispatchIntercept<TPage = unknown> = {
41
41
  };
42
42
  type AppPageDispatchInterceptOptions<TPage = unknown> = {
43
43
  interceptionContext: string | null;
44
- interceptLayouts?: readonly AppPageModule[] | null;
44
+ interceptLayouts?: readonly unknown[] | null;
45
45
  interceptPage: TPage;
46
46
  interceptParams: AppPageParams;
47
47
  interceptSlotId?: string | null;
@@ -1,7 +1,6 @@
1
1
  import { runWithFetchDedupe } from "../shims/fetch-cache.js";
2
2
  import { mergeMetadataEntries, mergeViewport, postProcessMetadata, resolveModuleMetadata as resolveModuleMetadata$1, resolveModuleViewport } from "../shims/metadata.js";
3
3
  import { resolveAppPageSegmentParams } from "./app-page-params.js";
4
- import { applyFileBasedMetadata } from "./file-based-metadata.js";
5
4
  import { tagAppPageMetadataError } from "./app-page-execution.js";
6
5
  //#region src/server/app-page-head.ts
7
6
  /**
@@ -206,7 +205,8 @@ async function resolveAppPageHeadInner(options) {
206
205
  const metadataSources = createMetadataSources(layoutMetadataResults, routeSegments, layoutSourcePositions, pageMetadata, Boolean(options.pageModule));
207
206
  metadataSources.push(...parallelMetadataSources);
208
207
  let metadata = resolvedMetadataBase;
209
- try {
208
+ if (options.metadataRoutes.length > 0) try {
209
+ const { applyFileBasedMetadata } = await import("./file-based-metadata.js");
210
210
  metadata = await applyFileBasedMetadata(resolvedMetadataBase, options.routePath, options.params, options.metadataRoutes, {
211
211
  routeSegments,
212
212
  metadataSources,
@@ -39,9 +39,16 @@ type BuildAppPageElementResult<TElement> = {
39
39
  response: Response | null;
40
40
  };
41
41
  type AppPageInterceptMatch<TPage = unknown> = {
42
+ interceptLayouts?: readonly unknown[] | null;
43
+ __loadInterceptLayouts?: readonly (() => Promise<unknown>)[] | null;
42
44
  matchedParams: AppPageParams;
43
45
  page: TPage;
44
46
  __pageLoader?: (() => Promise<TPage>) | null;
47
+ __loadState?: {
48
+ page: TPage;
49
+ pageLoading: Promise<TPage> | null;
50
+ interceptLayoutsLoading: Promise<readonly unknown[]> | null;
51
+ };
45
52
  slotId?: string | null;
46
53
  slotKey: string;
47
54
  sourceRouteIndex: number;
@@ -1,6 +1,7 @@
1
1
  import { notFoundResponse } from "./http-error-responses.js";
2
2
  import { runWithFetchDedupe } from "../shims/fetch-cache.js";
3
3
  import { getAppPageSegmentParamName } from "./app-page-params.js";
4
+ import { loadAppInterceptLayouts } from "./app-route-module-loader.js";
4
5
  //#region src/server/app-page-request.ts
5
6
  function pickRouteParams(matchedParams, routeParamNames) {
6
7
  const params = {};
@@ -104,7 +105,24 @@ async function resolveAppPageInterceptState(options) {
104
105
  if (!options.isRscRequest) return { kind: "none" };
105
106
  const intercept = options.findIntercept(options.cleanPathname);
106
107
  if (!intercept) return { kind: "none" };
107
- if (intercept.__pageLoader && intercept.page == null) intercept.page = await intercept.__pageLoader();
108
+ const loadState = intercept.__loadState;
109
+ if (loadState?.page != null) intercept.page = loadState.page;
110
+ if (intercept.__pageLoader && intercept.page == null) {
111
+ const loading = loadState?.pageLoading ?? intercept.__pageLoader().then((page) => {
112
+ intercept.page = page;
113
+ if (loadState) {
114
+ loadState.page = page;
115
+ loadState.pageLoading = null;
116
+ }
117
+ return page;
118
+ }).catch((error) => {
119
+ if (loadState) loadState.pageLoading = null;
120
+ throw error;
121
+ });
122
+ if (loadState) loadState.pageLoading = loading;
123
+ await loading;
124
+ }
125
+ if (intercept.__loadInterceptLayouts) await loadAppInterceptLayouts(intercept);
108
126
  const sourceRoute = await options.getSourceRoute(intercept.sourceRouteIndex);
109
127
  if (!sourceRoute) return { kind: "none" };
110
128
  if (sourceRoute === options.currentRoute) return {
@@ -5,14 +5,14 @@ type GenerateStaticParams = (args: {
5
5
  params: RootParams;
6
6
  }) => unknown;
7
7
  type AppPrerenderStaticParamsMap = Record<string, GenerateStaticParams | null | undefined>;
8
- type RootParamNamesMap = Record<string, readonly string[] | undefined>;
8
+ type AppPrerenderRootParamNamesMap = Record<string, readonly string[] | undefined>;
9
9
  type HandleAppPrerenderEndpointOptions = {
10
10
  isPrerenderEnabled?: () => boolean;
11
11
  loadPagesRoutes?: () => Promise<unknown>;
12
12
  pathname: string;
13
- rootParamNamesByPattern?: RootParamNamesMap;
13
+ rootParamNamesByPattern?: AppPrerenderRootParamNamesMap;
14
14
  staticParamsMap: AppPrerenderStaticParamsMap;
15
15
  };
16
16
  declare function handleAppPrerenderEndpoint(request: Request, options: HandleAppPrerenderEndpointOptions): Promise<Response | null>;
17
17
  //#endregion
18
- export { handleAppPrerenderEndpoint };
18
+ export { AppPrerenderRootParamNamesMap, AppPrerenderStaticParamsMap, handleAppPrerenderEndpoint };
@@ -1,12 +1,11 @@
1
+ import "./headers.js";
1
2
  import { notFoundResponse } from "./http-error-responses.js";
2
3
  import { callAppPrerenderStaticParams } from "./app-prerender-static-params.js";
3
4
  //#region src/server/app-prerender-endpoints.ts
4
- const STATIC_PARAMS_ENDPOINT = "/__vinext/prerender/static-params";
5
- const PAGES_STATIC_PATHS_ENDPOINT = "/__vinext/prerender/pages-static-paths";
6
5
  const JSON_HEADERS = { "content-type": "application/json" };
7
6
  async function handleAppPrerenderEndpoint(request, options) {
8
- if (options.pathname === STATIC_PARAMS_ENDPOINT) return handleStaticParamsEndpoint(request, options);
9
- if (options.pathname === PAGES_STATIC_PATHS_ENDPOINT) {
7
+ if (options.pathname === "/__vinext/prerender/static-params") return handleStaticParamsEndpoint(request, options);
8
+ if (options.pathname === "/__vinext/prerender/pages-static-paths") {
10
9
  if (!options.loadPagesRoutes) return null;
11
10
  return handlePagesStaticPathsEndpoint(request, options);
12
11
  }
@@ -10,34 +10,82 @@
10
10
  * module at Worker startup. Only the module(s) for the matched route are
11
11
  * evaluated, on demand.
12
12
  *
13
- * `ensureAppRouteModulesLoaded` resolves a route's lazy thunks and populates the
14
- * synchronous `page` / `routeHandler` fields that the rest of the request
15
- * pipeline reads directly. It is:
13
+ * `ensureAppRouteModulesLoaded` resolves a route's lazy thunks and populates
14
+ * the synchronous module fields that the rest of the request pipeline reads
15
+ * directly (`page`, `routeHandler`, layouts, templates, boundaries, and
16
+ * parallel-slot modules). It is:
16
17
  *
17
18
  * - idempotent: once a route is loaded it returns immediately;
18
19
  * - dedup'd: concurrent calls for the same route share one in-flight promise,
19
20
  * so a burst of requests to the same route triggers a single import.
20
21
  *
21
- * Callers must `await` it before any synchronous read of `route.page` or
22
- * `route.routeHandler` (segment config, fetch-cache mode, runtime resolution,
23
- * dispatch branch, element building, etc.).
22
+ * Callers must `await` it before any synchronous read of route modules
23
+ * (segment config, fetch-cache mode, runtime resolution, dispatch branch,
24
+ * element building, etc.).
24
25
  */
25
26
  type LazyModuleThunk = () => Promise<unknown>;
27
+ type LazyModuleLoaderArray = readonly (LazyModuleThunk | null | undefined)[];
28
+ type LazyLoadableIntercept = {
29
+ interceptLayouts?: readonly unknown[] | null;
30
+ __loadInterceptLayouts?: LazyModuleLoaderArray | null;
31
+ __loadState?: {
32
+ interceptLayoutsLoading: Promise<readonly unknown[]> | null;
33
+ };
34
+ };
35
+ type LazyLoadableSlot = {
36
+ page?: unknown;
37
+ default?: unknown;
38
+ layout?: unknown;
39
+ loading?: unknown;
40
+ error?: unknown;
41
+ __loadPage?: LazyModuleThunk | null;
42
+ __loadDefault?: LazyModuleThunk | null;
43
+ __loadLayout?: LazyModuleThunk | null;
44
+ __loadLoading?: LazyModuleThunk | null;
45
+ __loadError?: LazyModuleThunk | null; /** Hydrated only after an intercept matches, not with the slot's base modules. */
46
+ intercepts?: LazyLoadableIntercept[];
47
+ };
26
48
  type LazyLoadableRoute = {
27
49
  page?: unknown;
28
- routeHandler?: unknown; /** Lazy loader for the page module; `null`/absent when the page is eager. */
50
+ routeHandler?: unknown;
51
+ layouts?: unknown[];
52
+ templates?: unknown[];
53
+ errors?: unknown[];
54
+ errorPaths?: unknown[];
55
+ notFounds?: unknown[];
56
+ forbiddens?: unknown[];
57
+ unauthorizeds?: unknown[];
58
+ loading?: unknown;
59
+ error?: unknown;
60
+ notFound?: unknown;
61
+ forbidden?: unknown;
62
+ unauthorized?: unknown;
63
+ slots?: Record<string, LazyLoadableSlot>;
64
+ siblingIntercepts?: LazyLoadableIntercept[]; /** Lazy loader for the page module; `null`/absent when the page is eager. */
29
65
  __loadPage?: LazyModuleThunk | null; /** Lazy loader for the route-handler module; `null`/absent when none. */
30
- __loadRouteHandler?: LazyModuleThunk | null; /** Set once the lazy modules have been resolved onto `page`/`routeHandler`. */
66
+ __loadRouteHandler?: LazyModuleThunk | null;
67
+ __loadLayouts?: LazyModuleLoaderArray | null;
68
+ __loadTemplates?: LazyModuleLoaderArray | null;
69
+ __loadErrors?: LazyModuleLoaderArray | null;
70
+ __loadErrorPaths?: LazyModuleLoaderArray | null;
71
+ __loadNotFounds?: LazyModuleLoaderArray | null;
72
+ __loadForbiddens?: LazyModuleLoaderArray | null;
73
+ __loadUnauthorizeds?: LazyModuleLoaderArray | null;
74
+ __loadLoading?: LazyModuleThunk | null;
75
+ __loadError?: LazyModuleThunk | null;
76
+ __loadNotFound?: LazyModuleThunk | null;
77
+ __loadForbidden?: LazyModuleThunk | null;
78
+ __loadUnauthorized?: LazyModuleThunk | null; /** Set once the route's lazy module fields have been resolved. */
31
79
  __loaded?: boolean; /** In-flight hydration promise, used to dedup concurrent loads. */
32
80
  __loading?: Promise<unknown> | null;
33
81
  };
82
+ declare function loadAppInterceptLayouts(intercept: LazyLoadableIntercept): Promise<readonly unknown[]>;
34
83
  /**
35
- * Resolve a route's lazy page/route-handler modules and assign them onto the
36
- * route's synchronous `page` / `routeHandler` fields. Returns the same route
37
- * reference (synchronously when already loaded, otherwise after the in-flight
38
- * import resolves). Safe to call on `null`/`undefined` routes and on eager
39
- * routes that have no lazy thunks.
84
+ * Resolve a route's lazy modules and assign them onto the route's synchronous
85
+ * module fields. Returns the same route reference (synchronously when already
86
+ * loaded, otherwise after the in-flight import resolves). Safe to call on
87
+ * `null`/`undefined` routes and on eager routes that have no lazy thunks.
40
88
  */
41
89
  declare function ensureAppRouteModulesLoaded<TRoute extends LazyLoadableRoute>(route: TRoute | null | undefined): TRoute | Promise<TRoute>;
42
90
  //#endregion
43
- export { LazyLoadableRoute, ensureAppRouteModulesLoaded };
91
+ export { LazyLoadableRoute, ensureAppRouteModulesLoaded, loadAppInterceptLayouts };
@@ -1,23 +1,74 @@
1
1
  //#region src/server/app-route-module-loader.ts
2
+ function pushFieldLoad(loads, target, field, loader) {
3
+ if (!loader || target[field] != null) return;
4
+ loads.push(loader().then((module) => {
5
+ target[field] = module;
6
+ }));
7
+ }
8
+ function pushArrayLoads(loads, target, loaders) {
9
+ if (!target || !loaders) return;
10
+ const slots = target;
11
+ for (const [index, loader] of loaders.entries()) {
12
+ if (index >= slots.length || !loader || slots[index] != null) continue;
13
+ loads.push(loader().then((module) => {
14
+ slots[index] = module;
15
+ }));
16
+ }
17
+ }
18
+ function loadAppInterceptLayouts(intercept) {
19
+ const loadState = intercept.__loadState;
20
+ if (loadState?.interceptLayoutsLoading) return loadState.interceptLayoutsLoading;
21
+ const loads = [];
22
+ pushArrayLoads(loads, intercept.interceptLayouts, intercept.__loadInterceptLayouts);
23
+ if (loads.length === 0) return Promise.resolve(intercept.interceptLayouts ?? []);
24
+ const loading = Promise.all(loads).then(() => {
25
+ if (loadState) loadState.interceptLayoutsLoading = null;
26
+ return intercept.interceptLayouts ?? [];
27
+ }).catch((error) => {
28
+ if (loadState) loadState.interceptLayoutsLoading = null;
29
+ throw error;
30
+ });
31
+ if (loadState) loadState.interceptLayoutsLoading = loading;
32
+ return loading;
33
+ }
2
34
  /**
3
- * Resolve a route's lazy page/route-handler modules and assign them onto the
4
- * route's synchronous `page` / `routeHandler` fields. Returns the same route
5
- * reference (synchronously when already loaded, otherwise after the in-flight
6
- * import resolves). Safe to call on `null`/`undefined` routes and on eager
7
- * routes that have no lazy thunks.
35
+ * Resolve a route's lazy modules and assign them onto the route's synchronous
36
+ * module fields. Returns the same route reference (synchronously when already
37
+ * loaded, otherwise after the in-flight import resolves). Safe to call on
38
+ * `null`/`undefined` routes and on eager routes that have no lazy thunks.
8
39
  */
9
40
  function ensureAppRouteModulesLoaded(route) {
10
41
  if (!route || route.__loaded) return route;
11
42
  if (route.__loading) return route.__loading;
12
43
  const loadPage = route.__loadPage;
13
44
  const loadRouteHandler = route.__loadRouteHandler;
14
- if (!loadPage && !loadRouteHandler) {
45
+ const loads = [];
46
+ pushFieldLoad(loads, route, "page", loadPage);
47
+ pushFieldLoad(loads, route, "routeHandler", loadRouteHandler);
48
+ pushFieldLoad(loads, route, "loading", route.__loadLoading);
49
+ pushFieldLoad(loads, route, "error", route.__loadError);
50
+ pushFieldLoad(loads, route, "notFound", route.__loadNotFound);
51
+ pushFieldLoad(loads, route, "forbidden", route.__loadForbidden);
52
+ pushFieldLoad(loads, route, "unauthorized", route.__loadUnauthorized);
53
+ pushArrayLoads(loads, route.layouts, route.__loadLayouts);
54
+ pushArrayLoads(loads, route.templates, route.__loadTemplates);
55
+ pushArrayLoads(loads, route.errors, route.__loadErrors);
56
+ pushArrayLoads(loads, route.errorPaths, route.__loadErrorPaths);
57
+ pushArrayLoads(loads, route.notFounds, route.__loadNotFounds);
58
+ pushArrayLoads(loads, route.forbiddens, route.__loadForbiddens);
59
+ pushArrayLoads(loads, route.unauthorizeds, route.__loadUnauthorizeds);
60
+ for (const slot of Object.values(route.slots ?? {})) {
61
+ pushFieldLoad(loads, slot, "page", slot.__loadPage);
62
+ pushFieldLoad(loads, slot, "default", slot.__loadDefault);
63
+ pushFieldLoad(loads, slot, "layout", slot.__loadLayout);
64
+ pushFieldLoad(loads, slot, "loading", slot.__loadLoading);
65
+ pushFieldLoad(loads, slot, "error", slot.__loadError);
66
+ }
67
+ if (loads.length === 0) {
15
68
  route.__loaded = true;
16
69
  return route;
17
70
  }
18
- const loading = Promise.all([loadPage ? loadPage() : void 0, loadRouteHandler ? loadRouteHandler() : void 0]).then(([pageModule, routeHandlerModule]) => {
19
- if (loadPage) route.page = pageModule;
20
- if (loadRouteHandler) route.routeHandler = routeHandlerModule;
71
+ const loading = Promise.all(loads).then(() => {
21
72
  route.__loaded = true;
22
73
  route.__loading = null;
23
74
  return route;
@@ -29,4 +80,4 @@ function ensureAppRouteModulesLoaded(route) {
29
80
  return loading;
30
81
  }
31
82
  //#endregion
32
- export { ensureAppRouteModulesLoaded };
83
+ export { ensureAppRouteModulesLoaded, loadAppInterceptLayouts };
@@ -5,16 +5,16 @@ import { RootParams } from "../shims/root-params.js";
5
5
  import { MiddlewareModule } from "./middleware-runtime.js";
6
6
  import { AppMiddlewareContext } from "./app-middleware.js";
7
7
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
8
- import { handleAppPrerenderEndpoint } from "./app-prerender-endpoints.js";
9
- import { handleMetadataRouteRequest } from "./metadata-route-response.js";
8
+ import { AppPrerenderRootParamNamesMap, AppPrerenderStaticParamsMap } from "./app-prerender-endpoints.js";
9
+ import { MetadataRouteMakeThenableParams, MetadataRuntimeRoute } from "./metadata-route-response.js";
10
10
  import { ReactFormState } from "react-dom/client";
11
11
 
12
12
  //#region src/server/app-rsc-handler.d.ts
13
13
  type AppPageParams = Record<string, string | string[]>;
14
- type MetadataRoutes = Parameters<typeof handleMetadataRouteRequest>[0]["metadataRoutes"];
15
- type MakeThenableParams = Parameters<typeof handleMetadataRouteRequest>[0]["makeThenableParams"];
16
- type StaticParamsMap = Parameters<typeof handleAppPrerenderEndpoint>[1]["staticParamsMap"];
17
- type RootParamNamesMap = Parameters<typeof handleAppPrerenderEndpoint>[1]["rootParamNamesByPattern"];
14
+ type MetadataRoutes = readonly MetadataRuntimeRoute[];
15
+ type MakeThenableParams = MetadataRouteMakeThenableParams;
16
+ type StaticParamsMap = AppPrerenderStaticParamsMap;
17
+ type RootParamNamesMap = AppPrerenderRootParamNamesMap;
18
18
  type AppRscMiddlewareContext = AppMiddlewareContext;
19
19
  type AppRscHandlerRoute = {
20
20
  isDynamic: boolean;
@@ -183,6 +183,7 @@ type CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {
183
183
  makeThenableParams: MakeThenableParams;
184
184
  matchRoute: (pathname: string) => AppRscRouteMatch<TRoute> | null;
185
185
  metadataRoutes: MetadataRoutes;
186
+ middlewareFilePath: string | null;
186
187
  middlewareModule: MiddlewareModule | null;
187
188
  publicFiles: ReadonlySet<string>;
188
189
  renderNotFound: (options: RenderNotFoundOptions<TRoute>) => Promise<Response | null>;
@@ -17,14 +17,12 @@ import "./app-page-response.js";
17
17
  import { buildNextDataNotFoundResponse, normalizePagesDataRequest } from "./pages-data-route.js";
18
18
  import { matchPrerenderRouteParamsPayload, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
19
19
  import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
20
+ import { pickRootParams, setRootParams } from "../shims/root-params.js";
20
21
  import { flattenErrorCauses } from "../utils/error-cause.js";
21
22
  import { buildPageCacheTags } from "./implicit-tags.js";
22
23
  import { buildPostMwRequestContext } from "./app-post-middleware-context.js";
23
- import { pickRootParams, setRootParams } from "../shims/root-params.js";
24
- import { handleAppPrerenderEndpoint } from "./app-prerender-endpoints.js";
25
24
  import { finalizeAppRscResponse } from "./app-rsc-response-finalizer.js";
26
25
  import { normalizeRscRequest } from "./app-rsc-request-normalization.js";
27
- import { handleMetadataRouteRequest } from "./metadata-route-response.js";
28
26
  import { runWithPrerenderWorkUnit } from "./prerender-work-unit-setup.js";
29
27
  //#region src/server/app-rsc-handler.ts
30
28
  const STATIC_METADATA_CONFIG_HEADER_OVERRIDES = new Set(["cache-control"]);
@@ -125,16 +123,19 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
125
123
  basePath: options.basePath,
126
124
  hadBasePath: true
127
125
  };
128
- const prerenderEndpointResponse = await handleAppPrerenderEndpoint(request, {
129
- isPrerenderEnabled() {
130
- return process.env.VINEXT_PRERENDER === "1";
131
- },
132
- loadPagesRoutes: options.loadPrerenderPagesRoutes,
133
- pathname,
134
- rootParamNamesByPattern: options.rootParamNamesByPattern,
135
- staticParamsMap: options.staticParamsMap
136
- });
137
- if (prerenderEndpointResponse) return prerenderEndpointResponse;
126
+ if (pathname === "/__vinext/prerender/static-params" || pathname === "/__vinext/prerender/pages-static-paths") {
127
+ const { handleAppPrerenderEndpoint } = await import("./app-prerender-endpoints.js");
128
+ const prerenderEndpointResponse = await handleAppPrerenderEndpoint(request, {
129
+ isPrerenderEnabled() {
130
+ return process.env.VINEXT_PRERENDER === "1";
131
+ },
132
+ loadPagesRoutes: options.loadPrerenderPagesRoutes,
133
+ pathname,
134
+ rootParamNamesByPattern: options.rootParamNamesByPattern,
135
+ staticParamsMap: options.staticParamsMap
136
+ });
137
+ if (prerenderEndpointResponse) return prerenderEndpointResponse;
138
+ }
138
139
  const trailingSlashRedirect = normalizeTrailingSlash(pathname, options.basePath, options.trailingSlash, url.search);
139
140
  if (trailingSlashRedirect) return trailingSlashRedirect;
140
141
  const matchPathname = (p) => normalizeDefaultLocalePathname(p, options.i18nConfig, { hostname: url.hostname });
@@ -170,6 +171,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
170
171
  basePath: options.basePath,
171
172
  cleanPathname,
172
173
  context: middlewareContext,
174
+ filePath: options.middlewareFilePath ?? void 0,
173
175
  i18nConfig: options.i18nConfig,
174
176
  isDataRequest,
175
177
  isProxy: options.isMiddlewareProxy,
@@ -209,20 +211,23 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
209
211
  if (!imageRedirect) return new Response("Invalid image optimization parameters", { status: 400 });
210
212
  return Response.redirect(new URL(imageRedirect, url.origin).href, 302);
211
213
  }
212
- const metadataRouteResponse = await handleMetadataRouteRequest({
213
- metadataRoutes: options.metadataRoutes,
214
- cleanPathname,
215
- makeThenableParams: options.makeThenableParams
216
- });
217
- if (metadataRouteResponse) {
218
- applyConfigHeadersToResponse(metadataRouteResponse.headers, {
219
- basePathState,
220
- configHeaders: options.configHeaders,
221
- overwriteExisting: STATIC_METADATA_CONFIG_HEADER_OVERRIDES,
222
- pathname: matchPathname(cleanPathname),
223
- requestContext: preMiddlewareRequestContext
214
+ if (options.metadataRoutes.length > 0) {
215
+ const { handleMetadataRouteRequest } = await import("./metadata-route-response.js");
216
+ const metadataRouteResponse = await handleMetadataRouteRequest({
217
+ metadataRoutes: options.metadataRoutes,
218
+ cleanPathname,
219
+ makeThenableParams: options.makeThenableParams
224
220
  });
225
- return applyMiddlewareContextToResponse(metadataRouteResponse, middlewareContext);
221
+ if (metadataRouteResponse) {
222
+ applyConfigHeadersToResponse(metadataRouteResponse.headers, {
223
+ basePathState,
224
+ configHeaders: options.configHeaders,
225
+ overwriteExisting: STATIC_METADATA_CONFIG_HEADER_OVERRIDES,
226
+ pathname: matchPathname(cleanPathname),
227
+ requestContext: preMiddlewareRequestContext
228
+ });
229
+ return applyMiddlewareContextToResponse(metadataRouteResponse, middlewareContext);
230
+ }
226
231
  }
227
232
  const publicFileResponse = resolvePublicFileRoute({
228
233
  cleanPathname,
@@ -31,6 +31,7 @@ type AppRscInterceptForMatching = {
31
31
  sourceMatchPattern?: string;
32
32
  sourcePageSegments?: readonly string[];
33
33
  interceptLayouts: readonly unknown[];
34
+ __loadInterceptLayouts?: readonly (() => Promise<unknown>)[] | null;
34
35
  page: unknown;
35
36
  __pageLoader?: (() => Promise<unknown>) | null;
36
37
  params: readonly string[];
@@ -45,6 +46,7 @@ type AppRscSiblingInterceptForMatching = {
45
46
  sourcePageSegments?: readonly string[];
46
47
  slotId: string | null;
47
48
  interceptLayouts: readonly unknown[];
49
+ __loadInterceptLayouts?: readonly (() => Promise<unknown>)[] | null;
48
50
  page: unknown;
49
51
  __pageLoader?: (() => Promise<unknown>) | null;
50
52
  params: readonly string[];
@@ -58,6 +60,11 @@ type AppRscRouteForMatching = {
58
60
  type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
59
61
  matchedParams: AppRscRouteParams;
60
62
  };
63
+ type AppRscInterceptLoadState = {
64
+ page: unknown;
65
+ pageLoading: Promise<unknown> | null;
66
+ interceptLayoutsLoading: Promise<readonly unknown[]> | null;
67
+ };
61
68
  type AppRscInterceptLookupEntry = {
62
69
  sourceRouteIndex: number;
63
70
  slotKey: string;
@@ -67,8 +74,10 @@ type AppRscInterceptLookupEntry = {
67
74
  sourceMatchPatternParts: string[] | null;
68
75
  sourcePageSegments: readonly string[] | null;
69
76
  interceptLayouts: readonly unknown[];
77
+ __loadInterceptLayouts?: readonly (() => Promise<unknown>)[] | null;
70
78
  page: unknown;
71
79
  __pageLoader?: (() => Promise<unknown>) | null;
80
+ __loadState: AppRscInterceptLoadState;
72
81
  params: readonly string[];
73
82
  slotId: string | null;
74
83
  };
@@ -39,6 +39,7 @@ function createAppRscRouteMatcher(routes) {
39
39
  const sourceParams = matchedSourceParams ?? createRouteParams();
40
40
  return {
41
41
  ...entry,
42
+ page: entry.__loadState.page,
42
43
  sourceRouteIndex: concreteSourceRouteIndex,
43
44
  matchedParams: mergeMatchedParams(sourceParams, params)
44
45
  };
@@ -84,8 +85,14 @@ function createInterceptLookup(routes) {
84
85
  sourceMatchPatternParts,
85
86
  sourcePageSegments: intercept.sourcePageSegments ?? null,
86
87
  interceptLayouts: intercept.interceptLayouts,
88
+ __loadInterceptLayouts: intercept.__loadInterceptLayouts,
87
89
  page: intercept.page,
88
90
  __pageLoader: intercept.__pageLoader,
91
+ __loadState: {
92
+ page: intercept.page,
93
+ pageLoading: null,
94
+ interceptLayoutsLoading: null
95
+ },
89
96
  params: intercept.params
90
97
  });
91
98
  }
@@ -103,8 +110,14 @@ function createInterceptLookup(routes) {
103
110
  sourceMatchPatternParts,
104
111
  sourcePageSegments: intercept.sourcePageSegments ?? null,
105
112
  interceptLayouts: intercept.interceptLayouts,
113
+ __loadInterceptLayouts: intercept.__loadInterceptLayouts,
106
114
  page: intercept.page,
107
115
  __pageLoader: intercept.__pageLoader,
116
+ __loadState: {
117
+ page: intercept.page,
118
+ pageLoading: null,
119
+ interceptLayoutsLoading: null
120
+ },
108
121
  params: intercept.params
109
122
  });
110
123
  }