vinext 1.0.1 → 1.1.0
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.
- package/README.md +72 -1
- package/dist/build/app-page-static-eligibility.d.ts +15 -0
- package/dist/build/app-page-static-eligibility.js +82 -0
- package/dist/build/prerender-paths.js +3 -78
- package/dist/build/prerender.js +33 -8
- package/dist/build/server-manifest.d.ts +7 -0
- package/dist/build/server-manifest.js +11 -1
- package/dist/cache/cache-adapters-virtual.d.ts +7 -1
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/client/navigation-runtime.d.ts +11 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/config/config-matchers.js +3 -1
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +64 -43
- package/dist/index.d.ts +4 -1
- package/dist/index.js +59 -30
- package/dist/plugins/cache-flight-codec.d.ts +19 -0
- package/dist/plugins/cache-flight-codec.js +68 -0
- package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
- package/dist/plugins/commonjs-esm-facade.js +80 -0
- package/dist/plugins/fonts.d.ts +8 -11
- package/dist/plugins/fonts.js +71 -34
- package/dist/routing/file-matcher.d.ts +1 -0
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/api-handler.js +17 -7
- package/dist/server/app-browser-entry.js +11 -7
- package/dist/server/app-browser-history-controller.d.ts +2 -0
- package/dist/server/app-browser-history-controller.js +8 -0
- package/dist/server/app-browser-interception-context.js +26 -2
- package/dist/server/app-browser-navigation-controller.d.ts +7 -1
- package/dist/server/app-browser-navigation-controller.js +93 -10
- package/dist/server/app-fallback-renderer.d.ts +2 -0
- package/dist/server/app-fallback-renderer.js +1 -0
- package/dist/server/app-history-state.d.ts +3 -0
- package/dist/server/app-history-state.js +7 -0
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-optimistic-routing.js +1 -1
- package/dist/server/app-page-boundary-render.d.ts +5 -0
- package/dist/server/app-page-boundary-render.js +6 -4
- package/dist/server/app-page-cache-finalizer.d.ts +11 -0
- package/dist/server/app-page-cache-finalizer.js +41 -7
- package/dist/server/app-page-cache-render.d.ts +18 -0
- package/dist/server/app-page-cache-render.js +63 -43
- package/dist/server/app-page-cache.d.ts +5 -0
- package/dist/server/app-page-cache.js +27 -7
- package/dist/server/app-page-dispatch.d.ts +9 -1
- package/dist/server/app-page-dispatch.js +47 -30
- package/dist/server/app-page-element-builder.d.ts +7 -0
- package/dist/server/app-page-element-builder.js +27 -42
- package/dist/server/app-page-execution.d.ts +15 -12
- package/dist/server/app-page-execution.js +30 -40
- package/dist/server/app-page-head.d.ts +5 -0
- package/dist/server/app-page-head.js +9 -3
- package/dist/server/app-page-probe.d.ts +0 -109
- package/dist/server/app-page-probe.js +3 -136
- package/dist/server/app-page-render-identity.d.ts +0 -1
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render-observation.js +2 -2
- package/dist/server/app-page-render.d.ts +13 -5
- package/dist/server/app-page-render.js +219 -119
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +1 -2
- package/dist/server/app-page-response.d.ts +4 -3
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-route-wiring.d.ts +17 -5
- package/dist/server/app-page-route-wiring.js +55 -33
- package/dist/server/app-page-rsc-render-status.d.ts +48 -0
- package/dist/server/app-page-rsc-render-status.js +54 -0
- package/dist/server/app-page-stream.d.ts +15 -8
- package/dist/server/app-page-stream.js +6 -0
- package/dist/server/app-request-stage-dispatch.js +1 -1
- package/dist/server/app-request-stage-independent-entry.js +4 -4
- package/dist/server/app-response-header-provenance.d.ts +2 -0
- package/dist/server/app-response-header-provenance.js +10 -4
- package/dist/server/app-route-handler-dispatch.js +1 -1
- package/dist/server/app-router-entry.js +3 -3
- package/dist/server/app-rsc-handler.d.ts +4 -0
- package/dist/server/app-rsc-handler.js +212 -96
- package/dist/server/app-rsc-request-normalization.d.ts +3 -1
- package/dist/server/app-rsc-request-normalization.js +4 -2
- package/dist/server/app-rsc-response-stage.d.ts +1 -1
- package/dist/server/app-rsc-response-stage.js +6 -4
- package/dist/server/app-rsc-route-matching.d.ts +9 -1
- package/dist/server/app-rsc-route-matching.js +36 -19
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +3 -2
- package/dist/server/app-ssr-entry.js +6 -4
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +34 -15
- package/dist/server/app-worker-stages.d.ts +2 -2
- package/dist/server/app-worker-stages.js +3 -3
- package/dist/server/cacheability-request.js +9 -5
- package/dist/server/config-headers.js +2 -0
- package/dist/server/dev-server.js +12 -2
- package/dist/server/headers.d.ts +14 -0
- package/dist/server/headers.js +15 -1
- package/dist/server/image-optimization.d.ts +16 -0
- package/dist/server/image-optimization.js +26 -6
- package/dist/server/isr-cache.js +2 -2
- package/dist/server/middleware-matcher-pattern.js +8 -4
- package/dist/server/middleware-response-headers.js +2 -0
- package/dist/server/multi-stage.d.ts +8 -0
- package/dist/server/navigation-planner.js +1 -1
- package/dist/server/node-response-signal.d.ts +16 -0
- package/dist/server/node-response-signal.js +26 -0
- package/dist/server/open-redirect.d.ts +47 -0
- package/dist/server/open-redirect.js +74 -1
- package/dist/server/pages-page-handler.js +21 -4
- package/dist/server/pages-request-stage-entry.js +8 -4
- package/dist/server/prod-server.d.ts +8 -7
- package/dist/server/prod-server.js +153 -69
- package/dist/server/request-pipeline.d.ts +20 -8
- package/dist/server/request-pipeline.js +21 -7
- package/dist/server/response-compression.d.ts +43 -0
- package/dist/server/response-compression.js +111 -0
- package/dist/server/seed-cache.js +10 -7
- package/dist/server/socket-error-backstop.d.ts +14 -0
- package/dist/server/socket-error-backstop.js +27 -4
- package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
- package/dist/shims/cache-adapter-instantiate.js +118 -0
- package/dist/shims/cache-callable-runtime.d.ts +2 -40
- package/dist/shims/cache-callable-runtime.js +50 -168
- package/dist/shims/cache-flight-arguments.d.ts +22 -0
- package/dist/shims/cache-flight-arguments.js +56 -0
- package/dist/shims/cache-request-state.d.ts +15 -0
- package/dist/shims/cache-request-state.js +31 -8
- package/dist/shims/cache-runtime.d.ts +6 -19
- package/dist/shims/cache-runtime.js +165 -225
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +5 -4
- package/dist/shims/cacheability-classification.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.js +19 -1
- package/dist/shims/error-boundary.d.ts +10 -10
- package/dist/shims/error-boundary.js +26 -7
- package/dist/shims/fetch-cache.d.ts +6 -0
- package/dist/shims/fetch-cache.js +19 -1
- package/dist/shims/form.js +1 -1
- package/dist/shims/headers.d.ts +13 -0
- package/dist/shims/headers.js +47 -4
- package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
- package/dist/shims/internal/app-page-props-cache-key.js +24 -15
- package/dist/shims/internal/headers-state.d.ts +0 -1
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/navigation-errors.d.ts +6 -0
- package/dist/shims/navigation-errors.js +11 -1
- package/dist/shims/navigation.d.ts +7 -0
- package/dist/shims/navigation.js +14 -4
- package/dist/shims/request-context.d.ts +9 -0
- package/dist/shims/thenable-params.d.ts +4 -0
- package/dist/shims/thenable-params.js +25 -3
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/mdx-scan.d.ts +5 -0
- package/dist/utils/mdx-scan.js +8 -1
- package/dist/utils/regex-safety.d.ts +10 -0
- package/dist/utils/regex-safety.js +70 -18
- package/package.json +3 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER } from "./headers.js";
|
|
2
2
|
import { isPromiseLike } from "../utils/promise.js";
|
|
3
3
|
import { runWithConnectionProbe } from "../shims/headers.js";
|
|
4
|
+
import { isThrownNavigationError } from "../shims/navigation-errors.js";
|
|
4
5
|
import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader, createRscRedirectLocation } from "./app-rsc-cache-busting.js";
|
|
5
6
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
6
|
-
import { copyLinkHeaderProvenance,
|
|
7
|
+
import { copyLinkHeaderProvenance, getFrameworkLinkHeader, markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
|
|
7
8
|
import { applyEdgeRuntimeHeader } from "./app-page-response.js";
|
|
8
9
|
import { parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
|
|
9
10
|
import { renderSsrErrorMetaTags } from "./app-ssr-error-meta.js";
|
|
@@ -43,7 +44,7 @@ function getAppPageStatusText(statusCode) {
|
|
|
43
44
|
return statusCode === 403 ? "Forbidden" : statusCode === 401 ? "Unauthorized" : "Not Found";
|
|
44
45
|
}
|
|
45
46
|
function mergeAppPageSpecialErrorHeaders(response, middlewareContext) {
|
|
46
|
-
const frameworkLink =
|
|
47
|
+
const frameworkLink = getFrameworkLinkHeader(response.headers);
|
|
47
48
|
const headers = new Headers(response.headers);
|
|
48
49
|
if (frameworkLink) headers.delete("link");
|
|
49
50
|
mergeMiddlewareResponseHeaders(headers, middlewareContext?.headers ?? null);
|
|
@@ -77,6 +78,23 @@ function resolveAppPageSpecialError(error) {
|
|
|
77
78
|
return null;
|
|
78
79
|
}
|
|
79
80
|
/**
|
|
81
|
+
* Resolve the special error that rejected an SSR shell. An error from the RSC
|
|
82
|
+
* render reaches the shell decoded from its Flight digest, so whether
|
|
83
|
+
* generateMetadata() threw it comes from the errors the RSC render threw with
|
|
84
|
+
* that digest. When the page threw one too, the page's decides. An error a
|
|
85
|
+
* client component threw during SSR is the page's, since generateMetadata()
|
|
86
|
+
* runs only in the RSC render.
|
|
87
|
+
*/
|
|
88
|
+
function resolveAppPageShellSpecialError(error, renderedSpecialErrors) {
|
|
89
|
+
const specialError = resolveAppPageSpecialError(error);
|
|
90
|
+
if (!specialError || specialError.fromMetadata === true) return specialError;
|
|
91
|
+
if (isThrownNavigationError(error)) return specialError;
|
|
92
|
+
return renderedSpecialErrors.length > 0 && renderedSpecialErrors.every((rendered) => resolveAppPageSpecialError(rendered)?.fromMetadata === true) ? {
|
|
93
|
+
...specialError,
|
|
94
|
+
fromMetadata: true
|
|
95
|
+
} : specialError;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
80
98
|
* Resolves a redirect() target against the request URL and prepends the
|
|
81
99
|
* configured basePath when the target is an app-internal absolute path.
|
|
82
100
|
*
|
|
@@ -95,6 +113,15 @@ function applyAppPageRedirectBasePath(location, basePath) {
|
|
|
95
113
|
return `${basePath}${suffixIndex === -1 ? location : location.slice(0, suffixIndex)}${suffixIndex === -1 ? "" : location.slice(suffixIndex)}`;
|
|
96
114
|
}
|
|
97
115
|
/**
|
|
116
|
+
* The response headers stored with a page's special-error entries: a
|
|
117
|
+
* redirect's own `location`, not the response's, which middleware may have
|
|
118
|
+
* replaced for this request.
|
|
119
|
+
*/
|
|
120
|
+
function resolveAppPageSpecialErrorStoredHeaders(specialError, basePath) {
|
|
121
|
+
if (specialError.kind !== "redirect") return void 0;
|
|
122
|
+
return { location: applyAppPageRedirectBasePath(specialError.location, basePath) };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
98
125
|
* Returns a path-relative form (`/foo?bar`) of an absolute URL when it shares
|
|
99
126
|
* the request's origin; otherwise returns the URL verbatim. Used so the digest
|
|
100
127
|
* we embed in the flight payload matches Next.js's convention — the digest
|
|
@@ -254,43 +281,6 @@ async function probeLayoutForErrors(options, layoutIndex) {
|
|
|
254
281
|
});
|
|
255
282
|
return outcome.completed ? outcome.result : null;
|
|
256
283
|
}
|
|
257
|
-
async function probeAppPageComponent(options) {
|
|
258
|
-
return options.runWithSuppressedHookWarning(async () => {
|
|
259
|
-
const outcome = await runWithConnectionProbe(async () => {
|
|
260
|
-
try {
|
|
261
|
-
const pageResult = options.probePage();
|
|
262
|
-
if (isPromiseLike(pageResult)) {
|
|
263
|
-
if (options.awaitAsyncResult) await pageResult;
|
|
264
|
-
else Promise.resolve(pageResult).catch(() => {});
|
|
265
|
-
}
|
|
266
|
-
} catch (error) {
|
|
267
|
-
return options.onError(error);
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
});
|
|
271
|
-
return outcome.completed ? outcome.result : null;
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
async function probeAppPageThrownError(options) {
|
|
275
|
-
return options.runWithSuppressedHookWarning(async () => {
|
|
276
|
-
const outcome = await runWithConnectionProbe(async () => {
|
|
277
|
-
try {
|
|
278
|
-
const pageResult = options.probePage();
|
|
279
|
-
if (isPromiseLike(pageResult)) await pageResult;
|
|
280
|
-
} catch (error) {
|
|
281
|
-
return {
|
|
282
|
-
error,
|
|
283
|
-
thrown: true
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
error: null,
|
|
288
|
-
thrown: false
|
|
289
|
-
};
|
|
290
|
-
});
|
|
291
|
-
return outcome.completed && outcome.result.thrown ? outcome.result.error : null;
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
284
|
async function readAppPageBinaryStream(stream) {
|
|
295
285
|
const reader = stream.getReader();
|
|
296
286
|
const chunks = [];
|
|
@@ -335,4 +325,4 @@ function buildAppPageFontLinkHeader(preloads) {
|
|
|
335
325
|
return preloads.map((preload) => `<${preload.href}>; rel=preload; as=font; type=${preload.type}; crossorigin`).join(", ");
|
|
336
326
|
}
|
|
337
327
|
//#endregion
|
|
338
|
-
export { bufferAppPageBinaryStream, buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse,
|
|
328
|
+
export { bufferAppPageBinaryStream, buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse, probeAppPageLayouts, readAppPageBinaryStream, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, tagAppPageMetadataError, teeAppPageRscStreamForCapture };
|
|
@@ -121,6 +121,11 @@ export declare function collectAppPageSearchParams(searchParams: URLSearchParams
|
|
|
121
121
|
*/
|
|
122
122
|
export declare function resolveOrderedAppPageMetadata<TModule extends AppPageHeadModule>(options: ResolveOrderedAppPageMetadataOptions<TModule>): Promise<Metadata | null>;
|
|
123
123
|
export declare function resolveAppPageHead<TModule extends AppPageHeadModule>(options: ResolveAppPageHeadOptions<TModule>): Promise<ResolveAppPageHeadResult>;
|
|
124
|
+
/**
|
|
125
|
+
* Whether any segment of the page's head exports `generateMetadata()`. Known
|
|
126
|
+
* from the modules alone, so callers can place the head before resolving it.
|
|
127
|
+
*/
|
|
128
|
+
export declare function hasAppPageDynamicMetadata<TModule extends AppPageHeadModule>(options: Pick<ResolveAppPageHeadOptions<TModule>, "layoutModules" | "pageModule" | "parallelRoutes">): boolean;
|
|
124
129
|
/**
|
|
125
130
|
* Start metadata and viewport resolution without coupling their completion.
|
|
126
131
|
*
|
|
@@ -294,6 +294,13 @@ async function resolveAppPageHead(options) {
|
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
296
|
/**
|
|
297
|
+
* Whether any segment of the page's head exports `generateMetadata()`. Known
|
|
298
|
+
* from the modules alone, so callers can place the head before resolving it.
|
|
299
|
+
*/
|
|
300
|
+
function hasAppPageDynamicMetadata(options) {
|
|
301
|
+
return options.layoutModules.some(hasGenerateMetadata) || hasGenerateMetadata(options.pageModule) || (options.parallelRoutes ?? []).some(parallelRouteHasDynamicMetadata);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
297
304
|
* Start metadata and viewport resolution without coupling their completion.
|
|
298
305
|
*
|
|
299
306
|
* Live document renders can place the metadata promise behind Suspense while
|
|
@@ -308,7 +315,6 @@ function prepareAppPageHeadInner(options) {
|
|
|
308
315
|
const layoutTreePositions = options.layoutTreePositions ?? [];
|
|
309
316
|
const layoutInputs = createLayoutInputs(options.layoutModules, layoutTreePositions);
|
|
310
317
|
const layoutSourcePositions = layoutInputs.map((input) => input.treePosition);
|
|
311
|
-
const primaryHasDynamicMetadata = layoutInputs.some((input) => hasGenerateMetadata(input.module)) || hasGenerateMetadata(options.pageModule);
|
|
312
318
|
const { hasSearchParams, pageSearchParams } = collectAppPageSearchParams(options.searchParams);
|
|
313
319
|
const layoutMetadataPromise = resolveLayoutMetadata(layoutInputs, options.params, routeSegments);
|
|
314
320
|
const layoutViewport = resolveLayoutViewport(layoutInputs, options.params, routeSegments);
|
|
@@ -334,7 +340,7 @@ function prepareAppPageHeadInner(options) {
|
|
|
334
340
|
accumulatedViewport = parallelViewport.resolvedViewport;
|
|
335
341
|
}
|
|
336
342
|
const parallelRouteViewportPromise = Promise.all(parallelRouteViewportPromises);
|
|
337
|
-
const hasDynamicMetadata =
|
|
343
|
+
const hasDynamicMetadata = hasAppPageDynamicMetadata(options);
|
|
338
344
|
const metadata = Promise.all([
|
|
339
345
|
layoutMetadataPromise,
|
|
340
346
|
pageMetadataPromise,
|
|
@@ -379,4 +385,4 @@ function prepareAppPageHeadInner(options) {
|
|
|
379
385
|
};
|
|
380
386
|
}
|
|
381
387
|
//#endregion
|
|
382
|
-
export { collectAppPageSearchParams, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveAppPageHead, resolveOrderedAppPageMetadata };
|
|
388
|
+
export { collectAppPageSearchParams, hasAppPageDynamicMetadata, prepareAppPageHead, resolveActiveParallelRouteHeadInputs, resolveAppPageHead, resolveOrderedAppPageMetadata };
|
|
@@ -12,124 +12,15 @@ type ProbeReactServerSubtreeOptions = Readonly<{
|
|
|
12
12
|
* failures only make static-layout skip fall back to render-and-send.
|
|
13
13
|
*/
|
|
14
14
|
export declare function probeReactServerSubtree(node: unknown, options?: ProbeReactServerSubtreeOptions): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Build a probePage() invocation for the App Router request lifecycle.
|
|
17
|
-
*
|
|
18
|
-
* The generated RSC entry calls this once per request after route matching to
|
|
19
|
-
* eagerly invoke the page component. Surfacing redirect()/notFound() throws
|
|
20
|
-
* here lets the probe lifecycle turn them into proper HTTP responses before
|
|
21
|
-
* RSC streaming begins (see `probeAppPageBeforeRender`).
|
|
22
|
-
*
|
|
23
|
-
* The helper exists to keep the generated entry thin (a single delegation
|
|
24
|
-
* call) and to make the search-params wiring directly unit-testable. A bug
|
|
25
|
-
* here previously slipped through because the entry hand-rolled the call and
|
|
26
|
-
* read a non-existent key off `collectAppPageSearchParams`'s return value
|
|
27
|
-
* (see https://github.com/cloudflare/vinext/issues/1235).
|
|
28
|
-
*
|
|
29
|
-
* Returns `null` when the route has no page component (eg. interception-only
|
|
30
|
-
* routes), matching the caller contract on `probePage`.
|
|
31
|
-
*/
|
|
32
|
-
export declare function probeAppPage(options: {
|
|
33
|
-
pageComponent: unknown;
|
|
34
|
-
asyncRouteParams: unknown;
|
|
35
|
-
searchParams: URLSearchParams | null | undefined;
|
|
36
|
-
}): unknown;
|
|
37
|
-
type AppPageProbeModule = Readonly<{
|
|
38
|
-
default?: unknown;
|
|
39
|
-
}> | null | undefined;
|
|
40
|
-
type AppPageProbeSlot = Readonly<{
|
|
41
|
-
page?: AppPageProbeModule;
|
|
42
|
-
loading?: AppPageProbeModule;
|
|
43
|
-
loadings?: readonly AppPageProbeModule[] | null;
|
|
44
|
-
loadingTreePositions?: readonly number[] | null;
|
|
45
|
-
}> | null | undefined;
|
|
46
|
-
type AppPageProbeRoute = Readonly<{
|
|
47
|
-
slots?: Readonly<Record<string, AppPageProbeSlot>> | null;
|
|
48
|
-
}>;
|
|
49
|
-
type AppPageProbeIntercept = Readonly<{
|
|
50
|
-
page?: AppPageProbeModule;
|
|
51
|
-
interceptLoadings?: readonly AppPageProbeModule[] | null;
|
|
52
|
-
matchedParams?: unknown;
|
|
53
|
-
/**
|
|
54
|
-
* Key of the parallel-route slot this interception overrides. At render
|
|
55
|
-
* time the matched route's `slots[slotKey].page` is replaced by the
|
|
56
|
-
* interception page (see app-page-element-builder.ts), so the slot's own
|
|
57
|
-
* page never renders for this request.
|
|
58
|
-
*/
|
|
59
|
-
slotKey?: string | null;
|
|
60
|
-
}> | null | undefined;
|
|
61
|
-
/**
|
|
62
|
-
* Fan out the per-request page probes for the App Router dispatch lifecycle.
|
|
63
|
-
*
|
|
64
|
-
* A single request can render more than one page component: the matched page,
|
|
65
|
-
* each active parallel-route slot page, and an interception page when one
|
|
66
|
-
* matches. Each must be probed so searchParams access anywhere in the rendered
|
|
67
|
-
* tree bails the request out of the query-invariant static cache.
|
|
68
|
-
*
|
|
69
|
-
* Extracted out of the generated RSC entry so the fan-out is directly
|
|
70
|
-
* unit-testable and the entry stays codegen glue (see AGENTS.md "Generated
|
|
71
|
-
* Entry Modules Should Stay Thin"). Returns a list of resolved promises so the
|
|
72
|
-
* caller can `Promise.all` them.
|
|
73
|
-
*
|
|
74
|
-
* The fan-out is scoped to the page components that render for this request:
|
|
75
|
-
*
|
|
76
|
-
* - **Interception override:** when an interception matches it replaces the
|
|
77
|
-
* page of the slot named by `intercept.slotKey` (the element builder sets
|
|
78
|
-
* `overrides[slotKey].pageModule` to the interception page, which wins over
|
|
79
|
-
* `slot.page` in `app-page-route-wiring.tsx`). We probe the interception page
|
|
80
|
-
* in place of that slot's own page rather than probing both — probing the
|
|
81
|
-
* overridden slot page would mark an otherwise-static request dynamic for a
|
|
82
|
-
* component that never renders.
|
|
83
|
-
* - **Non-overridden slots:** `slot.page?.default` is exactly what renders.
|
|
84
|
-
* `app-page-route-wiring.tsx` resolves a slot to `overrideOrPageComponent ??
|
|
85
|
-
* defaultComponent`, so whenever a slot has a `page.tsx` that page renders.
|
|
86
|
-
* When a slot has only a `default.tsx` (including the soft-nav case at
|
|
87
|
-
* `app-page-route-wiring.tsx:741` that skips an already-mounted slot), there
|
|
88
|
-
* is no `slot.page?.default`, so `probeAppPage` short-circuits to `null` and
|
|
89
|
-
* probes nothing — a no-op, not an over-bail.
|
|
90
|
-
*
|
|
91
|
-
* Interception only fires for RSC navigations (`resolveAppPageInterceptState`
|
|
92
|
-
* returns `kind: "none"` when `!isRscRequest`, app-page-request.ts:324), so the
|
|
93
|
-
* interception handling here is gated on `isRscRequest`. For non-RSC (HTML)
|
|
94
|
-
* requests the matched route renders normally, so we probe every slot's own
|
|
95
|
-
* page and skip the interception probe entirely. The remaining "source-route"
|
|
96
|
-
* interception case (where a *different* route renders, app-page-request.ts:342)
|
|
97
|
-
* never reaches this probe: `dispatchAppPage` returns the intercepted response
|
|
98
|
-
* before calling `probePage`, so by the time this runs any matched interception
|
|
99
|
-
* is the current-route override case above.
|
|
100
|
-
*
|
|
101
|
-
* A `default.tsx` that itself awaits `searchParams` is not probed here, but the
|
|
102
|
-
* real render still observes that access and skips the query-invariant cache
|
|
103
|
-
* write (the same loading.tsx backstop), so this cannot under-bail.
|
|
104
|
-
*/
|
|
105
|
-
export declare function buildAppPageProbes(options: {
|
|
106
|
-
route: AppPageProbeRoute;
|
|
107
|
-
pageComponent: unknown;
|
|
108
|
-
asyncRouteParams: unknown;
|
|
109
|
-
searchParams: URLSearchParams | null | undefined;
|
|
110
|
-
intercept?: AppPageProbeIntercept;
|
|
111
|
-
/**
|
|
112
|
-
* Whether this is an RSC navigation. Interception only fires for RSC
|
|
113
|
-
* requests, so the interception probe is ignored when this is false.
|
|
114
|
-
*/
|
|
115
|
-
isRscRequest: boolean;
|
|
116
|
-
/** Fallback raw params used when an interception match omits its own. */
|
|
117
|
-
matchedParams: unknown;
|
|
118
|
-
makeThenableParams: (params: unknown) => unknown;
|
|
119
|
-
}): Promise<unknown>[];
|
|
120
15
|
type ProbeAppPageBeforeRenderResult = {
|
|
121
16
|
response: Response | null;
|
|
122
17
|
layoutFlags: LayoutFlags;
|
|
123
18
|
};
|
|
124
19
|
type ProbeAppPageBeforeRenderOptions = {
|
|
125
|
-
hasLoadingBoundary: boolean;
|
|
126
|
-
probePageBeforeRender?: boolean;
|
|
127
20
|
skipProbes?: boolean;
|
|
128
21
|
layoutCount: number;
|
|
129
22
|
probeLayoutAt: (layoutIndex: number) => unknown;
|
|
130
|
-
probePage: () => unknown;
|
|
131
23
|
renderLayoutSpecialError: (specialError: AppPageSpecialError, layoutIndex: number) => Promise<Response>;
|
|
132
|
-
renderPageSpecialError: (specialError: AppPageSpecialError) => Promise<Response>;
|
|
133
24
|
resolveSpecialError: (error: unknown) => AppPageSpecialError | null;
|
|
134
25
|
runWithSuppressedHookWarning<T>(probe: () => Promise<T>): Promise<T>;
|
|
135
26
|
/** When provided, enables per-layout static/dynamic classification. */
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { isPromiseLike } from "../utils/promise.js";
|
|
2
|
-
import {
|
|
3
|
-
import "../shims/navigation-server.js";
|
|
4
|
-
import { markAppPagePropsForUseCache, withUseCachePageMarker } from "../shims/internal/app-page-props-cache-key.js";
|
|
5
|
-
import { probeAppPageComponent, probeAppPageLayouts } from "./app-page-execution.js";
|
|
6
|
-
import { collectAppPageSearchParams } from "./app-page-head.js";
|
|
7
|
-
import { makeObservedAppPageSearchParamsThenable } from "./app-page-search-params-observation.js";
|
|
2
|
+
import { probeAppPageLayouts } from "./app-page-execution.js";
|
|
8
3
|
import { Fragment, isValidElement } from "react";
|
|
9
4
|
//#region src/server/app-page-probe.ts
|
|
10
5
|
const DEFAULT_SUBTREE_PROBE_MAX_DEPTH = 32;
|
|
@@ -123,119 +118,6 @@ async function probeReactServerSubtree(node, options = {}) {
|
|
|
123
118
|
};
|
|
124
119
|
await visit(node, 0);
|
|
125
120
|
}
|
|
126
|
-
async function probeReactServerSubtreeForDynamicUsage(node) {
|
|
127
|
-
try {
|
|
128
|
-
await probeReactServerSubtree(node);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
if (isNextRouterError(error)) return;
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Build a probePage() invocation for the App Router request lifecycle.
|
|
136
|
-
*
|
|
137
|
-
* The generated RSC entry calls this once per request after route matching to
|
|
138
|
-
* eagerly invoke the page component. Surfacing redirect()/notFound() throws
|
|
139
|
-
* here lets the probe lifecycle turn them into proper HTTP responses before
|
|
140
|
-
* RSC streaming begins (see `probeAppPageBeforeRender`).
|
|
141
|
-
*
|
|
142
|
-
* The helper exists to keep the generated entry thin (a single delegation
|
|
143
|
-
* call) and to make the search-params wiring directly unit-testable. A bug
|
|
144
|
-
* here previously slipped through because the entry hand-rolled the call and
|
|
145
|
-
* read a non-existent key off `collectAppPageSearchParams`'s return value
|
|
146
|
-
* (see https://github.com/cloudflare/vinext/issues/1235).
|
|
147
|
-
*
|
|
148
|
-
* Returns `null` when the route has no page component (eg. interception-only
|
|
149
|
-
* routes), matching the caller contract on `probePage`.
|
|
150
|
-
*/
|
|
151
|
-
function probeAppPage(options) {
|
|
152
|
-
const { pageComponent, asyncRouteParams, searchParams } = options;
|
|
153
|
-
if (typeof pageComponent !== "function") return null;
|
|
154
|
-
const { pageSearchParams } = collectAppPageSearchParams(searchParams);
|
|
155
|
-
const asyncSearchParams = makeObservedAppPageSearchParamsThenable(pageSearchParams, { observeReactPromiseStatus: true });
|
|
156
|
-
const result = pageComponent(markAppPagePropsForUseCache(withUseCachePageMarker(pageComponent, {
|
|
157
|
-
params: asyncRouteParams,
|
|
158
|
-
searchParams: asyncSearchParams
|
|
159
|
-
})));
|
|
160
|
-
if (isPromiseLike(result)) return result.then(async (resolved) => {
|
|
161
|
-
await probeReactServerSubtreeForDynamicUsage(resolved);
|
|
162
|
-
return resolved;
|
|
163
|
-
});
|
|
164
|
-
if (isValidElement(result) || Array.isArray(result)) return probeReactServerSubtreeForDynamicUsage(result).then(() => result);
|
|
165
|
-
return result;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Fan out the per-request page probes for the App Router dispatch lifecycle.
|
|
169
|
-
*
|
|
170
|
-
* A single request can render more than one page component: the matched page,
|
|
171
|
-
* each active parallel-route slot page, and an interception page when one
|
|
172
|
-
* matches. Each must be probed so searchParams access anywhere in the rendered
|
|
173
|
-
* tree bails the request out of the query-invariant static cache.
|
|
174
|
-
*
|
|
175
|
-
* Extracted out of the generated RSC entry so the fan-out is directly
|
|
176
|
-
* unit-testable and the entry stays codegen glue (see AGENTS.md "Generated
|
|
177
|
-
* Entry Modules Should Stay Thin"). Returns a list of resolved promises so the
|
|
178
|
-
* caller can `Promise.all` them.
|
|
179
|
-
*
|
|
180
|
-
* The fan-out is scoped to the page components that render for this request:
|
|
181
|
-
*
|
|
182
|
-
* - **Interception override:** when an interception matches it replaces the
|
|
183
|
-
* page of the slot named by `intercept.slotKey` (the element builder sets
|
|
184
|
-
* `overrides[slotKey].pageModule` to the interception page, which wins over
|
|
185
|
-
* `slot.page` in `app-page-route-wiring.tsx`). We probe the interception page
|
|
186
|
-
* in place of that slot's own page rather than probing both — probing the
|
|
187
|
-
* overridden slot page would mark an otherwise-static request dynamic for a
|
|
188
|
-
* component that never renders.
|
|
189
|
-
* - **Non-overridden slots:** `slot.page?.default` is exactly what renders.
|
|
190
|
-
* `app-page-route-wiring.tsx` resolves a slot to `overrideOrPageComponent ??
|
|
191
|
-
* defaultComponent`, so whenever a slot has a `page.tsx` that page renders.
|
|
192
|
-
* When a slot has only a `default.tsx` (including the soft-nav case at
|
|
193
|
-
* `app-page-route-wiring.tsx:741` that skips an already-mounted slot), there
|
|
194
|
-
* is no `slot.page?.default`, so `probeAppPage` short-circuits to `null` and
|
|
195
|
-
* probes nothing — a no-op, not an over-bail.
|
|
196
|
-
*
|
|
197
|
-
* Interception only fires for RSC navigations (`resolveAppPageInterceptState`
|
|
198
|
-
* returns `kind: "none"` when `!isRscRequest`, app-page-request.ts:324), so the
|
|
199
|
-
* interception handling here is gated on `isRscRequest`. For non-RSC (HTML)
|
|
200
|
-
* requests the matched route renders normally, so we probe every slot's own
|
|
201
|
-
* page and skip the interception probe entirely. The remaining "source-route"
|
|
202
|
-
* interception case (where a *different* route renders, app-page-request.ts:342)
|
|
203
|
-
* never reaches this probe: `dispatchAppPage` returns the intercepted response
|
|
204
|
-
* before calling `probePage`, so by the time this runs any matched interception
|
|
205
|
-
* is the current-route override case above.
|
|
206
|
-
*
|
|
207
|
-
* A `default.tsx` that itself awaits `searchParams` is not probed here, but the
|
|
208
|
-
* real render still observes that access and skips the query-invariant cache
|
|
209
|
-
* write (the same loading.tsx backstop), so this cannot under-bail.
|
|
210
|
-
*/
|
|
211
|
-
function buildAppPageProbes(options) {
|
|
212
|
-
const { route, pageComponent, asyncRouteParams, searchParams, matchedParams } = options;
|
|
213
|
-
const intercept = options.isRscRequest ? options.intercept : null;
|
|
214
|
-
const probes = [probeAppPage({
|
|
215
|
-
pageComponent,
|
|
216
|
-
asyncRouteParams,
|
|
217
|
-
searchParams
|
|
218
|
-
})];
|
|
219
|
-
const overriddenSlotKey = intercept?.slotKey ?? null;
|
|
220
|
-
for (const [slotKey, slot] of Object.entries(route.slots ?? {})) {
|
|
221
|
-
if (overriddenSlotKey !== null && slotKey === overriddenSlotKey) continue;
|
|
222
|
-
if (slot?.loading?.default || slot?.loadings?.some((loading) => loading?.default)) continue;
|
|
223
|
-
probes.push(probeAppPage({
|
|
224
|
-
pageComponent: slot?.page?.default,
|
|
225
|
-
asyncRouteParams,
|
|
226
|
-
searchParams
|
|
227
|
-
}));
|
|
228
|
-
}
|
|
229
|
-
const interceptedSlot = intercept?.slotKey ? route.slots?.[intercept.slotKey] : null;
|
|
230
|
-
const interceptedSlotHasRootLoading = Boolean(interceptedSlot?.loading?.default || interceptedSlot?.loadings?.some((loading, index) => loading?.default && interceptedSlot.loadingTreePositions?.[index] === 0));
|
|
231
|
-
const interceptHasLoadingBoundary = Boolean(intercept?.interceptLoadings?.some((loading) => loading?.default) || interceptedSlotHasRootLoading);
|
|
232
|
-
if (intercept && !interceptHasLoadingBoundary) probes.push(probeAppPage({
|
|
233
|
-
pageComponent: intercept.page?.default,
|
|
234
|
-
asyncRouteParams: options.makeThenableParams(intercept.matchedParams ?? matchedParams),
|
|
235
|
-
searchParams
|
|
236
|
-
}));
|
|
237
|
-
return probes.map((probe) => Promise.resolve(probe));
|
|
238
|
-
}
|
|
239
121
|
async function probeAppPageBeforeRender(options) {
|
|
240
122
|
let layoutFlags = {};
|
|
241
123
|
if (options.skipProbes) return {
|
|
@@ -262,25 +144,10 @@ async function probeAppPageBeforeRender(options) {
|
|
|
262
144
|
layoutFlags
|
|
263
145
|
};
|
|
264
146
|
}
|
|
265
|
-
if (options.hasLoadingBoundary || options.probePageBeforeRender === false) return {
|
|
266
|
-
response: null,
|
|
267
|
-
layoutFlags
|
|
268
|
-
};
|
|
269
147
|
return {
|
|
270
|
-
response:
|
|
271
|
-
awaitAsyncResult: true,
|
|
272
|
-
async onError(pageError) {
|
|
273
|
-
const specialError = options.resolveSpecialError(pageError);
|
|
274
|
-
if (specialError) return options.renderPageSpecialError(specialError);
|
|
275
|
-
return null;
|
|
276
|
-
},
|
|
277
|
-
probePage: options.probePage,
|
|
278
|
-
runWithSuppressedHookWarning(probe) {
|
|
279
|
-
return options.runWithSuppressedHookWarning(probe);
|
|
280
|
-
}
|
|
281
|
-
}),
|
|
148
|
+
response: null,
|
|
282
149
|
layoutFlags
|
|
283
150
|
};
|
|
284
151
|
}
|
|
285
152
|
//#endregion
|
|
286
|
-
export {
|
|
153
|
+
export { probeAppPageBeforeRender, probeReactServerSubtree };
|
|
@@ -18,6 +18,5 @@ export type AppPageRenderIdentity = {
|
|
|
18
18
|
routeId: string;
|
|
19
19
|
targetMatchedPathname: string;
|
|
20
20
|
};
|
|
21
|
-
export declare function normalizeAppPageInterceptionProofPathname(pathname: string | null): string | null;
|
|
22
21
|
export declare function createAppPageRenderIdentity(input: AppPageRenderIdentityInput): AppPageRenderIdentity;
|
|
23
22
|
//#endregion
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { fnv1a64 } from "../utils/hash.js";
|
|
2
2
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
3
|
-
import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
|
|
4
|
-
import "./app-elements.js";
|
|
5
3
|
import { consumeDynamicUsage, consumeInvalidDynamicUsageError, consumeRenderRequestApiUsage } from "../shims/headers.js";
|
|
6
4
|
import { _consumeRequestScopedCacheLife } from "../shims/cache-request-state.js";
|
|
7
5
|
import { consumeDynamicFetchObservations } from "../shims/fetch-cache.js";
|
|
6
|
+
import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
|
|
7
|
+
import "./app-elements.js";
|
|
8
8
|
import { buildRenderObservation, buildRenderRequestApiObservations } from "./cache-proof.js";
|
|
9
9
|
//#region src/server/app-page-render-observation.ts
|
|
10
10
|
function readRootBoundaryId(element) {
|
|
@@ -23,6 +23,13 @@ type AppPageRequestCacheLife = {
|
|
|
23
23
|
stale?: number;
|
|
24
24
|
};
|
|
25
25
|
type AppPageRenderableElement = ReactNode | Readonly<Record<string, ReactNode>>;
|
|
26
|
+
type AppPageSpecialErrorRenderOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* The document may be stored in place of the page's, so like a cache
|
|
29
|
+
* candidate's render it must not carry the request's query.
|
|
30
|
+
*/
|
|
31
|
+
isCacheCandidate?: boolean;
|
|
32
|
+
};
|
|
26
33
|
type PreparedAppPageElement = {
|
|
27
34
|
element: AppPageRenderableElement;
|
|
28
35
|
response?: never;
|
|
@@ -63,7 +70,6 @@ type RenderAppPageLifecycleOptionsBase = {
|
|
|
63
70
|
getDraftModeCookieHeader: () => string | null | undefined;
|
|
64
71
|
handlerStart: number;
|
|
65
72
|
hasCustomGlobalError?: boolean;
|
|
66
|
-
hasLoadingBoundary: boolean;
|
|
67
73
|
dynamicStaleTimeSeconds?: number;
|
|
68
74
|
isDynamicError: boolean;
|
|
69
75
|
isDraftMode: boolean;
|
|
@@ -83,9 +89,12 @@ type RenderAppPageLifecycleOptionsBase = {
|
|
|
83
89
|
isCacheCandidate?: boolean;
|
|
84
90
|
isProgressiveActionRender?: boolean;
|
|
85
91
|
isPrerender?: boolean;
|
|
92
|
+
/** PPR routes send every RSC response as a 200. */
|
|
93
|
+
isRoutePPREnabled?: boolean;
|
|
86
94
|
isSpeculativePrerender?: boolean;
|
|
95
|
+
/** Wait for the whole document before its first byte, as a prerender does. */
|
|
96
|
+
renderWholeDocument?: boolean;
|
|
87
97
|
isProduction: boolean;
|
|
88
|
-
probePageBeforeRender?: boolean;
|
|
89
98
|
omitPendingDynamicCacheState?: boolean;
|
|
90
99
|
isRscRequest: boolean;
|
|
91
100
|
traceOperation?: "prerender" | "render";
|
|
@@ -107,13 +116,12 @@ type RenderAppPageLifecycleOptionsBase = {
|
|
|
107
116
|
rootParams?: RootParams;
|
|
108
117
|
peekRenderObservationState?: () => AppPageRenderObservationState;
|
|
109
118
|
probeLayoutAt: (layoutIndex: number) => unknown;
|
|
110
|
-
probePage: () => unknown;
|
|
111
119
|
expireSeconds?: number;
|
|
112
120
|
formState?: ReactFormState | null;
|
|
113
121
|
revalidateSeconds: number | null;
|
|
114
122
|
renderErrorBoundaryResponse: (error: unknown, errorOrigin: "rsc" | "ssr") => Promise<Response | null>;
|
|
115
123
|
renderLayoutSpecialError: (specialError: AppPageSpecialError, layoutIndex: number) => Promise<Response>;
|
|
116
|
-
renderPageSpecialError: (specialError: AppPageSpecialError) => Promise<Response>;
|
|
124
|
+
renderPageSpecialError: (specialError: AppPageSpecialError, renderOptions?: AppPageSpecialErrorRenderOptions) => Promise<Response>;
|
|
117
125
|
renderToReadableStream: (element: ReactNode | AppOutgoingElements, options: {
|
|
118
126
|
onError: AppPageBoundaryOnError;
|
|
119
127
|
signal?: AbortSignal;
|
|
@@ -150,6 +158,6 @@ type RenderAppPageLifecycleOptions = RenderAppPageLifecycleOptionsBase & ({
|
|
|
150
158
|
* its response policy, such as error boundaries and special errors, get the
|
|
151
159
|
* same header as the normal render.
|
|
152
160
|
*/
|
|
153
|
-
export declare function applyIneligibleRouteCachePolicy(response: Response, options: Pick<RenderAppPageLifecycleOptions, "isDraftMode" | "isDynamicError" | "isForceDynamic" | "isForceStatic" | "isProduction" | "isProgressiveActionRender" | "isRscRequest" | "isStaticEligible" | "middlewareContext" | "peekDynamicUsage" | "revalidateSeconds" | "scriptNonce"
|
|
161
|
+
export declare function applyIneligibleRouteCachePolicy(response: Response, options: Pick<RenderAppPageLifecycleOptions, "dynamicStaleTimeSeconds" | "isDraftMode" | "isDynamicError" | "isForceDynamic" | "isForceStatic" | "isProduction" | "isProgressiveActionRender" | "isRscRequest" | "isStaticEligible" | "middlewareContext" | "peekDynamicUsage" | "revalidateSeconds" | "scriptNonce">, probeCacheLife?: AppPageRequestCacheLife | null): Response;
|
|
154
162
|
export declare function renderAppPageLifecycle(options: RenderAppPageLifecycleOptions): Promise<Response>;
|
|
155
163
|
//#endregion
|