Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/vinext/src/server/app-metadata-tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { frameworkTracer } from "./tracer.js";

type AppMetadataModuleType = "forbidden" | "layout" | "not-found" | "page" | "unauthorized";

export function createAppMetadataModuleRoute(
routeSegments: readonly string[],
moduleType: AppMetadataModuleType,
): string {
return `/${[...routeSegments.filter((segment) => !segment.startsWith("@")), moduleType].join("/")}`;
}

export function traceGenerateMetadata<T>(moduleRoute: string, callback: () => T): T {
return frameworkTracer.trace(createGenerateMetadataSpanDescriptor(moduleRoute), callback);
}

export function createGenerateMetadataSpanDescriptor(moduleRoute: string) {
return {
attributes: { "next.page": moduleRoute },
name: `generateMetadata ${moduleRoute}`,
type: "ResolveMetadata.generateMetadata",
} as const;
}
12 changes: 12 additions & 0 deletions packages/vinext/src/server/app-page-boundary-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,11 @@ export async function renderAppPageHttpAccessFallback<TModule extends AppPageMod
...(intercept?.interceptNotFound
? {
notFoundModule: intercept.interceptNotFound,
notFoundModuleRouteSegments: (
intercept.interceptNotFoundBranchSegments ??
intercept.interceptSourcePageSegments ??
fallbackRouteSegments
).slice(0, intercept.interceptNotFoundTreePosition ?? 0),
notFoundParams: resolveAppPageBranchParams(
intercept.interceptNotFoundBranchSegments ??
intercept.interceptBranchSegments ??
Expand All @@ -640,7 +645,14 @@ export async function renderAppPageHttpAccessFallback<TModule extends AppPageMod
const fallbackHeadOptions = {
boundaryModule,
boundaryParams,
boundaryRouteSegments: fallbackRouteSegments.slice(0, boundaryTreePosition ?? 0),
branchNotFoundConventions: options.statusCode === 404,
errorConvention:
options.statusCode === 403
? ("forbidden" as const)
: options.statusCode === 401
? ("unauthorized" as const)
: ("not-found" as const),
layoutModules,
layoutTreePositions: resolveHttpAccessFallbackHeadLayoutTreePositions(
options.route,
Expand Down
22 changes: 10 additions & 12 deletions packages/vinext/src/server/app-page-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1132,23 +1132,12 @@ async function dispatchAppPageInner<TRoute extends AppPageDispatchRoute>(
discardAppPageRenderState();
}

const pageBuildResult = await buildCurrentPageElement();
if (pageBuildResult.response) {
return pageBuildResult.response;
}

const navigationParams = resolveAppPageNavigationParams(
route,
options.params,
options.cleanPathname,
interceptResult.interceptOpts,
);
options.setNavigationContext({
pathname: options.displayPathname ?? options.cleanPathname,
searchParams: pageSearchParams,
params: navigationParams,
});

const layoutClassifications = getEffectiveLayoutClassifications(
route,
options.debugClassification,
Expand All @@ -1174,7 +1163,16 @@ async function dispatchAppPageInner<TRoute extends AppPageDispatchRoute>(
createRscOnErrorHandler(pathname, routePath, overrides) {
return options.createRscOnErrorHandler(pathname, routePath, overrides);
},
element: pageBuildResult.element,
async prepareElement() {
const pageBuildResult = await buildCurrentPageElement();
if (pageBuildResult.response) return { response: pageBuildResult.response };
options.setNavigationContext({
pathname: options.displayPathname ?? options.cleanPathname,
searchParams: pageSearchParams,
params: navigationParams,
});
return { element: pageBuildResult.element };
},
clientReuseManifest: options.clientReuseManifest,
getDraftModeCookieHeader,
getFontLinks: options.getFontLinks,
Expand Down
8 changes: 8 additions & 0 deletions packages/vinext/src/server/app-page-element-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ export async function buildPageElements<
...(opts?.interceptNotFound
? {
notFoundModule: opts.interceptNotFound,
notFoundModuleRouteSegments: (
opts.interceptNotFoundBranchSegments ??
opts.interceptSourcePageSegments ??
route.routeSegments ??
[]
).slice(0, opts.interceptNotFoundTreePosition ?? 0),
notFoundParams: resolveAppPageBranchParams(
opts.interceptNotFoundBranchSegments ??
opts.interceptBranchSegments ??
Expand Down Expand Up @@ -426,6 +432,8 @@ export async function buildPageElements<
return {
boundaryModule,
boundaryParams,
boundaryRouteSegments: (route.routeSegments ?? []).slice(0, boundaryTreePosition ?? 0),
errorConvention: "not-found" as const,
layoutModules: route.layouts,
layoutTreePositions: route.layoutTreePositions,
parallelBranches: activeParallelRouteHeadInputs,
Expand Down
59 changes: 54 additions & 5 deletions packages/vinext/src/server/app-page-head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { runWithFetchDedupe } from "vinext/shims/fetch-cache";
import type { ThenableParamsObserver } from "vinext/shims/thenable-params";
import type { AppPageParams } from "./app-page-boundary.js";
import { tagAppPageMetadataError } from "./app-page-execution.js";
import { createAppMetadataModuleRoute, traceGenerateMetadata } from "./app-metadata-tracing.js";
import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
import type { MetadataFileRoute } from "./metadata-routes.js";

Expand All @@ -29,10 +30,13 @@ import type { MetadataFileRoute } from "./metadata-routes.js";
* and Next.js test/e2e/app-dir/metadata-streaming.
*/
async function resolveModuleMetadata(
moduleRoute: string,
...args: Parameters<typeof _resolveModuleMetadata>
): Promise<Metadata | null> {
try {
return await _resolveModuleMetadata(...args);
return await (typeof args[0].generateMetadata === "function"
? traceGenerateMetadata(moduleRoute, () => _resolveModuleMetadata(...args))
: _resolveModuleMetadata(...args));
} catch (error) {
throw tagAppPageMetadataError(error);
}
Expand All @@ -54,6 +58,7 @@ export type OrderedAppPageMetadataSource<TModule extends AppPageHeadModule = App
/** Preserve an empty result as the most specific file-metadata source. */
includeWhenEmpty?: boolean;
module: TModule;
moduleRoute: string;
params: AppPageParams;
routeSegments: readonly string[];
searchParams?: AppPageSearchParams;
Expand All @@ -70,6 +75,8 @@ export type AppPageHeadParallelRoute<TModule extends AppPageHeadModule = AppPage
layoutModule?: TModule | null;
layoutModules?: readonly (TModule | null | undefined)[] | null;
layoutTreePositions?: readonly number[] | null;
moduleRoutePrefixSegments?: readonly string[] | null;
moduleRouteSegments?: readonly string[] | null;
pageModule?: TModule | null;
params?: AppPageParams | null;
routeSegments?: readonly string[] | null;
Expand All @@ -78,6 +85,7 @@ export type AppPageHeadParallelRoute<TModule extends AppPageHeadModule = AppPage
export type ActiveParallelRouteHeadInput<TModule extends AppPageHeadModule = AppPageHeadModule> = {
head: AppPageHeadParallelRoute<TModule>;
notFoundModule?: TModule | null;
notFoundModuleRouteSegments?: readonly string[] | null;
notFoundParams?: AppPageParams | null;
ownerTreePosition: number;
};
Expand Down Expand Up @@ -184,6 +192,7 @@ export function resolveActiveParallelRouteHeadInputs<TModule extends AppPageHead
): ActiveParallelRouteHeadInput<TModule>[] {
const inputs = Object.entries(options.slots ?? {}).map(([slotKey, slot]) => {
const ownerTreePosition = options.layoutTreePositions?.[slot.layoutIndex ?? 0] ?? 0;
const ownerRouteSegments = options.routeSegments.slice(0, ownerTreePosition);
const ownerParams = resolveAppPageSegmentParams(
options.routeSegments,
ownerTreePosition,
Expand All @@ -209,6 +218,16 @@ export function resolveActiveParallelRouteHeadInputs<TModule extends AppPageHead
const inheritedSlotNotFound =
slot.notFoundTreePosition === 0 ? (slot.notFound ?? null) : null;
const interceptNotFound = options.interceptNotFound ?? inheritedSlotNotFound;
const interceptRouteSegments = options.interceptSourcePageSegments ?? options.routeSegments;
const interceptNotFoundRouteSegments = options.interceptNotFound
? (options.interceptNotFoundBranchSegments ?? interceptRouteSegments).slice(
0,
options.interceptNotFoundTreePosition ?? 0,
)
: [
...ownerRouteSegments,
...(slot.routeSegments ?? []).slice(0, slot.notFoundTreePosition ?? 0),
];
const interceptNotFoundParams = interceptNotFound
? {
...ownerParams,
Expand Down Expand Up @@ -246,15 +265,21 @@ export function resolveActiveParallelRouteHeadInputs<TModule extends AppPageHead
],
pageModule: options.interceptPage,
params: options.interceptParams ?? options.params,
routeSegments: options.interceptSourcePageSegments ?? options.routeSegments,
routeSegments: interceptRouteSegments,
},
...(interceptNotFound
? { notFoundModule: interceptNotFound, notFoundParams: interceptNotFoundParams }
? {
notFoundModule: interceptNotFound,
notFoundModuleRouteSegments: interceptNotFoundRouteSegments,
notFoundParams: interceptNotFoundParams,
}
: {}),
ownerTreePosition,
};
}

const slotRouteSegments = slot.routeSegments ?? options.routeSegments;
const moduleRouteSegments = slot.routeSegments ?? [];
return {
head: {
layoutModules: [slot.layout, ...(slot.configLayouts ?? [])].filter(isPresent),
Expand All @@ -275,9 +300,20 @@ export function resolveActiveParallelRouteHeadInputs<TModule extends AppPageHead
],
pageModule: slot.page,
params: slotParams,
routeSegments: slot.routeSegments ?? options.routeSegments,
...(ownerRouteSegments.length > 0 ? { moduleRoutePrefixSegments: ownerRouteSegments } : {}),
...(slot.routeSegments ? {} : { moduleRouteSegments }),
routeSegments: slotRouteSegments,
},
...(slot.notFound ? { notFoundModule: slot.notFound, notFoundParams } : {}),
...(slot.notFound
? {
notFoundModule: slot.notFound,
notFoundModuleRouteSegments: [
...ownerRouteSegments,
...moduleRouteSegments.slice(0, slot.notFoundTreePosition ?? 0),
],
notFoundParams,
}
: {}),
ownerTreePosition,
};
});
Expand Down Expand Up @@ -414,6 +450,7 @@ async function resolveLayoutMetadata<TModule extends AppPageHeadModule>(
params,
);
const metadataPromise = resolveModuleMetadata(
createAppMetadataModuleRoute(routeSegments.slice(0, layoutInput.treePosition), "layout"),
layoutInput.module,
layoutParams,
undefined,
Expand Down Expand Up @@ -505,6 +542,8 @@ async function resolveParallelRouteMetadata<TModule extends AppPageHeadModule>(
): Promise<ResolvedParallelRouteMetadata> {
const params = parallelRoute.params ?? fallbackParams;
const routeSegments = parallelRoute.routeSegments ?? fallbackRouteSegments;
const moduleRoutePrefixSegments = parallelRoute.moduleRoutePrefixSegments ?? [];
const moduleRouteSegments = parallelRoute.moduleRouteSegments ?? routeSegments;
const metadataResults: (Metadata | null)[] = [];
const metadataSources: AppPageHeadSource[] = [];
let accumulatedMetadata = parent;
Expand All @@ -517,6 +556,13 @@ async function resolveParallelRouteMetadata<TModule extends AppPageHeadModule>(
layoutParams[index] ??
resolveParallelLayoutParams(routeSegments, layoutTreePositions[index] ?? 0, params);
const layoutMetadata = await resolveModuleMetadata(
createAppMetadataModuleRoute(
[
...moduleRoutePrefixSegments,
...moduleRouteSegments.slice(0, layoutTreePositions[index] ?? 0),
],
"layout",
),
layoutModule,
currentLayoutParams,
undefined,
Expand All @@ -537,6 +583,7 @@ async function resolveParallelRouteMetadata<TModule extends AppPageHeadModule>(

if (parallelRoute.pageModule) {
const pageMetadata = await resolveModuleMetadata(
createAppMetadataModuleRoute([...moduleRoutePrefixSegments, ...moduleRouteSegments], "page"),
parallelRoute.pageModule,
params,
pageSearchParams,
Expand Down Expand Up @@ -622,6 +669,7 @@ export function resolveOrderedAppPageMetadata<TModule extends AppPageHeadModule>
entries.length > 0 ? mergeMetadataEntries(entries) : {},
);
const metadataPromise = resolveModuleMetadata(
source.moduleRoute,
source.module,
source.params,
source.searchParams,
Expand Down Expand Up @@ -704,6 +752,7 @@ function prepareAppPageHeadInner<TModule extends AppPageHeadModule>(
void pageParentPromise.catch(() => null);
const pageMetadataPromise = options.pageModule
? resolveModuleMetadata(
createAppMetadataModuleRoute(routeSegments, "page"),
options.pageModule,
options.params,
pageSearchParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ import {
type ApplyAppPageFileBasedMetadata,
type OrderedAppPageMetadataSource,
} from "./app-page-head.js";
import { createAppMetadataModuleRoute } from "./app-metadata-tracing.js";
import { resolveAppPageBranchParams, resolveAppPageSegmentParams } from "./app-page-params.js";
import type { MetadataFileRoute } from "./metadata-routes.js";

type HttpAccessFallbackMetadataPlanOptions<TModule extends AppPageHeadModule = AppPageHeadModule> =
{
boundaryModule?: TModule | null;
boundaryParams: AppPageParams;
boundaryRouteSegments?: readonly string[] | null;
/** Whether active branches may replace the fallback with their local not-found convention. */
branchNotFoundConventions?: boolean;
errorConvention: "forbidden" | "not-found" | "unauthorized";
layoutModules: readonly (TModule | null | undefined)[];
layoutTreePositions?: readonly number[] | null;
parallelBranches?: readonly ActiveParallelRouteHeadInput<TModule>[] | null;
Expand Down Expand Up @@ -70,6 +73,10 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(
source: {
includeWhenEmpty: true,
module: layoutModule,
moduleRoute: createAppMetadataModuleRoute(
routeSegments.slice(0, treePosition),
options.errorConvention,
),
params: resolveAppPageSegmentParams(routeSegments, treePosition, options.params),
routeSegments: routeSegments.slice(0, treePosition),
},
Expand All @@ -78,7 +85,7 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(

let activeBoundaryModule = options.boundaryModule;
let activeBoundaryParams = options.boundaryParams;
let activeBoundaryRouteSegments = routeSegments;
let activeBoundaryRouteSegments = options.boundaryRouteSegments ?? [];
const appendFallbackLeaf = () => {
if (fallbackLeafMode === "final") {
plan.push({ kind: "fallback-leaf" });
Expand All @@ -90,6 +97,10 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(
source: {
includeWhenEmpty: true,
module: activeBoundaryModule,
moduleRoute: createAppMetadataModuleRoute(
activeBoundaryRouteSegments,
options.errorConvention,
),
params: activeBoundaryParams,
routeSegments: activeBoundaryRouteSegments,
},
Expand All @@ -111,6 +122,8 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(
const parallelRoute = branch.head;
const parallelParams = parallelRoute.params ?? options.params;
const parallelRouteSegments = parallelRoute.routeSegments ?? routeSegments;
const parallelModuleRoutePrefix = parallelRoute.moduleRoutePrefixSegments ?? [];
const parallelModuleRouteSegments = parallelRoute.moduleRouteSegments ?? parallelRouteSegments;
const layoutModules = [
...(parallelRoute.layoutModules ?? []),
parallelRoute.layoutModule,
Expand All @@ -124,6 +137,13 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(
source: {
includeWhenEmpty: true,
module: layoutModule,
moduleRoute: createAppMetadataModuleRoute(
[
...parallelModuleRoutePrefix,
...parallelModuleRouteSegments.slice(0, layoutTreePositions[index] ?? 0),
],
options.errorConvention,
),
params:
layoutParams[index] ??
resolveAppPageBranchParams(
Expand All @@ -138,7 +158,10 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(
if (options.branchNotFoundConventions !== false && branch.notFoundModule) {
activeBoundaryModule = branch.notFoundModule;
activeBoundaryParams = branch.notFoundParams ?? parallelParams;
activeBoundaryRouteSegments = parallelRouteSegments;
activeBoundaryRouteSegments = branch.notFoundModuleRouteSegments ?? [
...parallelModuleRoutePrefix,
...parallelModuleRouteSegments,
];
}
appendFallbackLeaf();
}
Expand All @@ -150,6 +173,10 @@ function createHttpAccessFallbackPlan<TModule extends AppPageHeadModule>(
{
includeWhenEmpty: true,
module: activeBoundaryModule,
moduleRoute: createAppMetadataModuleRoute(
activeBoundaryRouteSegments,
options.errorConvention,
),
params: activeBoundaryParams,
routeSegments: activeBoundaryRouteSegments,
},
Expand Down
Loading
Loading