vinext 0.1.0 → 0.1.2
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 +2 -5
- package/dist/build/assets-ignore.d.ts +32 -0
- package/dist/build/assets-ignore.js +48 -0
- package/dist/build/client-build-config.d.ts +33 -1
- package/dist/build/client-build-config.js +66 -1
- package/dist/check.js +4 -3
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +11 -2
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/client/window-next.d.ts +6 -4
- package/dist/config/config-matchers.d.ts +31 -5
- package/dist/config/config-matchers.js +50 -3
- package/dist/config/next-config.d.ts +29 -3
- package/dist/config/next-config.js +32 -2
- package/dist/deploy.js +47 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +61 -5
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-client-entry.js +1 -1
- package/dist/entries/pages-server-entry.js +16 -7
- package/dist/index.d.ts +0 -2
- package/dist/index.js +233 -280
- package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
- package/dist/plugins/dynamic-preload-metadata.js +415 -0
- package/dist/plugins/og-assets.js +2 -2
- package/dist/plugins/optimize-imports.d.ts +8 -4
- package/dist/plugins/optimize-imports.js +16 -12
- package/dist/plugins/postcss.js +18 -14
- package/dist/plugins/require-context.d.ts +6 -0
- package/dist/plugins/require-context.js +184 -0
- package/dist/plugins/sass.d.ts +53 -24
- package/dist/plugins/sass.js +249 -1
- package/dist/plugins/wasm-module-import.d.ts +15 -0
- package/dist/plugins/wasm-module-import.js +50 -0
- package/dist/routing/app-route-graph.d.ts +35 -2
- package/dist/routing/app-route-graph.js +179 -8
- package/dist/routing/file-matcher.js +1 -1
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +16 -1
- package/dist/server/api-handler.js +4 -0
- package/dist/server/app-browser-entry.js +155 -215
- package/dist/server/app-browser-error.d.ts +4 -1
- package/dist/server/app-browser-error.js +7 -1
- package/dist/server/app-browser-history-controller.d.ts +104 -0
- package/dist/server/app-browser-history-controller.js +210 -0
- package/dist/server/app-browser-interception-context.d.ts +2 -1
- package/dist/server/app-browser-interception-context.js +15 -2
- package/dist/server/app-browser-navigation-controller.d.ts +13 -2
- package/dist/server/app-browser-navigation-controller.js +83 -4
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
- package/dist/server/app-browser-rsc-redirect.js +30 -8
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +10 -10
- package/dist/server/app-browser-visible-commit.js +10 -8
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +3 -1
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-optimistic-routing.js +22 -1
- package/dist/server/app-page-boundary-render.d.ts +2 -1
- package/dist/server/app-page-boundary-render.js +45 -21
- package/dist/server/app-page-cache.js +9 -7
- package/dist/server/app-page-dispatch.d.ts +14 -0
- package/dist/server/app-page-dispatch.js +21 -6
- package/dist/server/app-page-element-builder.d.ts +23 -2
- package/dist/server/app-page-element-builder.js +58 -17
- package/dist/server/app-page-execution.d.ts +1 -1
- package/dist/server/app-page-execution.js +32 -17
- package/dist/server/app-page-render.d.ts +7 -1
- package/dist/server/app-page-render.js +11 -16
- package/dist/server/app-page-request.d.ts +9 -6
- package/dist/server/app-page-request.js +14 -10
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +2 -2
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +10 -8
- package/dist/server/app-page-stream.d.ts +37 -7
- package/dist/server/app-page-stream.js +36 -6
- package/dist/server/app-pages-bridge.d.ts +16 -0
- package/dist/server/app-pages-bridge.js +23 -3
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +2 -0
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-response.js +11 -10
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +15 -3
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +5 -4
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-rsc-route-matching.d.ts +20 -1
- package/dist/server/app-rsc-route-matching.js +29 -4
- package/dist/server/app-server-action-execution.d.ts +22 -1
- package/dist/server/app-server-action-execution.js +73 -12
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +19 -3
- package/dist/server/app-ssr-stream.js +9 -1
- package/dist/server/dev-lockfile.js +2 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +97 -43
- package/dist/server/headers.d.ts +8 -1
- package/dist/server/headers.js +8 -1
- package/dist/server/instrumentation-runtime.d.ts +6 -0
- package/dist/server/instrumentation-runtime.js +8 -0
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/isr-decision.d.ts +79 -0
- package/dist/server/isr-decision.js +70 -0
- package/dist/server/metadata-route-response.js +5 -3
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +11 -7
- package/dist/server/middleware.js +1 -0
- package/dist/server/navigation-planner.d.ts +62 -1
- package/dist/server/navigation-planner.js +193 -3
- package/dist/server/navigation-trace.d.ts +12 -2
- package/dist/server/navigation-trace.js +11 -1
- package/dist/server/normalize-path.d.ts +0 -8
- package/dist/server/normalize-path.js +3 -1
- package/dist/server/otel-tracer-extension.d.ts +45 -0
- package/dist/server/otel-tracer-extension.js +89 -0
- package/dist/server/pages-api-route.d.ts +14 -3
- package/dist/server/pages-api-route.js +6 -1
- package/dist/server/pages-asset-tags.d.ts +15 -4
- package/dist/server/pages-asset-tags.js +18 -12
- package/dist/server/pages-data-route.js +5 -1
- package/dist/server/pages-node-compat.d.ts +5 -11
- package/dist/server/pages-node-compat.js +175 -118
- package/dist/server/pages-page-data.d.ts +38 -7
- package/dist/server/pages-page-data.js +64 -18
- package/dist/server/pages-page-handler.d.ts +10 -2
- package/dist/server/pages-page-handler.js +49 -20
- package/dist/server/pages-page-response.d.ts +55 -2
- package/dist/server/pages-page-response.js +74 -6
- package/dist/server/pages-readiness.d.ts +36 -0
- package/dist/server/pages-readiness.js +21 -0
- package/dist/server/pages-request-pipeline.d.ts +113 -0
- package/dist/server/pages-request-pipeline.js +230 -0
- package/dist/server/pages-revalidate.d.ts +15 -0
- package/dist/server/pages-revalidate.js +19 -0
- package/dist/server/prod-server.d.ts +45 -3
- package/dist/server/prod-server.js +182 -234
- package/dist/server/socket-error-backstop.d.ts +19 -1
- package/dist/server/socket-error-backstop.js +77 -4
- package/dist/shims/app-router-scroll.js +22 -4
- package/dist/shims/cache-runtime.js +39 -2
- package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
- package/dist/shims/dynamic-preload-chunks.js +77 -0
- package/dist/shims/dynamic.d.ts +4 -0
- package/dist/shims/dynamic.js +4 -2
- package/dist/shims/error-boundary.d.ts +17 -7
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/error.js +37 -11
- package/dist/shims/fetch-cache.d.ts +22 -1
- package/dist/shims/fetch-cache.js +28 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- package/dist/shims/head.js +6 -1
- package/dist/shims/headers.d.ts +16 -2
- package/dist/shims/headers.js +37 -1
- package/dist/shims/image-config.js +7 -1
- package/dist/shims/internal/app-route-detection.d.ts +6 -3
- package/dist/shims/internal/app-route-detection.js +10 -6
- package/dist/shims/internal/app-router-context.d.ts +5 -0
- package/dist/shims/internal/link-status-registry.d.ts +43 -0
- package/dist/shims/internal/link-status-registry.js +42 -0
- package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
- package/dist/shims/internal/route-pattern-for-warning.js +40 -0
- package/dist/shims/internal/utils.d.ts +1 -0
- package/dist/shims/link.js +20 -6
- package/dist/shims/metadata.d.ts +6 -2
- package/dist/shims/metadata.js +32 -14
- package/dist/shims/navigation.d.ts +9 -18
- package/dist/shims/navigation.js +96 -23
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js +2 -0
- package/dist/shims/router.d.ts +6 -3
- package/dist/shims/router.js +156 -22
- package/dist/shims/script-nonce-context.d.ts +1 -1
- package/dist/shims/script-nonce-context.js +11 -3
- package/dist/shims/server.d.ts +17 -1
- package/dist/shims/server.js +31 -6
- package/dist/shims/slot.js +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -0
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +41 -6
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- package/dist/utils/client-runtime-metadata.d.ts +45 -0
- package/dist/utils/client-runtime-metadata.js +63 -0
- package/dist/utils/hash.d.ts +17 -1
- package/dist/utils/hash.js +36 -1
- package/dist/utils/lazy-chunks.d.ts +27 -1
- package/dist/utils/lazy-chunks.js +65 -1
- package/dist/utils/manifest-paths.d.ts +20 -2
- package/dist/utils/manifest-paths.js +38 -3
- package/dist/utils/path.d.ts +2 -1
- package/dist/utils/path.js +5 -1
- package/package.json +6 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildCachedRevalidateCacheControl, buildRevalidateCacheControl } from "./cache-control.js";
|
|
2
|
+
//#region src/server/isr-decision.ts
|
|
3
|
+
/** Resolve effective revalidate/expire, preferring per-entry metadata. */
|
|
4
|
+
function resolveRevalidate(options) {
|
|
5
|
+
return {
|
|
6
|
+
effectiveRevalidate: options.cacheControlMeta?.revalidate ?? options.revalidateSeconds,
|
|
7
|
+
effectiveExpire: options.cacheControlMeta === void 0 ? void 0 : options.cacheControlMeta.expire ?? options.expireSeconds
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function buildCacheControl(disposition, kind, revalidate, expire) {
|
|
11
|
+
if (kind === "app-route") {
|
|
12
|
+
if (revalidate === 0) return NEVER_CACHE_CONTROL;
|
|
13
|
+
if (revalidate === Infinity) return STATIC_CACHE_CONTROL;
|
|
14
|
+
}
|
|
15
|
+
return buildCachedRevalidateCacheControl(disposition, revalidate, expire);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Derive the `Cache-Control` string for an ISR response.
|
|
19
|
+
*
|
|
20
|
+
* Content guards (kind mismatch, query-variant-unproven, empty body) are the
|
|
21
|
+
* caller's responsibility and must happen *before* this call. `cacheState`
|
|
22
|
+
* must only be `"HIT"` or `"STALE"` when those guards have already passed.
|
|
23
|
+
*/
|
|
24
|
+
function decideIsr(options) {
|
|
25
|
+
if (options.cacheState === "MISS") return {
|
|
26
|
+
disposition: "MISS",
|
|
27
|
+
scheduleRegeneration: false,
|
|
28
|
+
cacheControl: ""
|
|
29
|
+
};
|
|
30
|
+
const { effectiveRevalidate, effectiveExpire } = resolveRevalidate(options);
|
|
31
|
+
if (options.cacheState === "HIT") return {
|
|
32
|
+
disposition: "HIT",
|
|
33
|
+
scheduleRegeneration: false,
|
|
34
|
+
cacheControl: buildCacheControl("HIT", options.kind, effectiveRevalidate, effectiveExpire)
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
disposition: "STALE",
|
|
38
|
+
scheduleRegeneration: true,
|
|
39
|
+
cacheControl: buildCacheControl("STALE", options.kind, effectiveRevalidate, effectiveExpire)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the `Cache-Control` string for a fresh MISS response whose ISR policy
|
|
44
|
+
* is known (i.e. revalidate is set and > 0). Uses the unbounded SWR form when
|
|
45
|
+
* no expire ceiling is available, exactly as `buildRevalidateCacheControl` does.
|
|
46
|
+
*
|
|
47
|
+
* Separate from `decideIsr` because a MISS doesn't read a cache entry and
|
|
48
|
+
* therefore never has `cacheControlMeta`. `expireSeconds` here is the route
|
|
49
|
+
* config ceiling passed directly from the caller (not a per-entry fallback).
|
|
50
|
+
*/
|
|
51
|
+
function buildMissIsrCacheControl(revalidateSeconds, expireSeconds) {
|
|
52
|
+
return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the `Cache-Control` string for a fresh (MISS) app-route response.
|
|
56
|
+
*
|
|
57
|
+
* Applies the same `revalidateSeconds=0`→NEVER and `Infinity`→STATIC gates
|
|
58
|
+
* that `decideIsr` uses for app-route cached responses. `expireSeconds` is
|
|
59
|
+
* the route config ceiling passed directly (not per-entry metadata fallback).
|
|
60
|
+
*
|
|
61
|
+
* Used by `applyRouteHandlerRevalidateHeader` which operates on a fresh
|
|
62
|
+
* response that has no per-entry cache metadata.
|
|
63
|
+
*/
|
|
64
|
+
function buildAppRouteMissIsrCacheControl(revalidateSeconds, expireSeconds) {
|
|
65
|
+
if (revalidateSeconds === 0) return NEVER_CACHE_CONTROL;
|
|
66
|
+
if (revalidateSeconds === Infinity) return STATIC_CACHE_CONTROL;
|
|
67
|
+
return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { NEVER_CACHE_CONTROL as ISR_NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL as ISR_NO_STORE_CACHE_CONTROL, buildAppRouteMissIsrCacheControl, buildMissIsrCacheControl, decideIsr };
|
|
@@ -53,9 +53,9 @@ function getMetadataRouteFunctions(route) {
|
|
|
53
53
|
routeFunctionCache.set(route, functions);
|
|
54
54
|
return functions;
|
|
55
55
|
}
|
|
56
|
-
function matchMetadataRoute(route, cleanPathname, functions) {
|
|
56
|
+
function matchMetadataRoute(route, cleanPathname, functions, getUrlParts) {
|
|
57
57
|
if (route.patternParts) {
|
|
58
|
-
const urlParts =
|
|
58
|
+
const urlParts = getUrlParts();
|
|
59
59
|
if (functions.hasGeneratedImageMetadata && urlParts.length > 0) {
|
|
60
60
|
const params = matchMetadataRoutePattern(urlParts.slice(0, -1), route.patternParts);
|
|
61
61
|
if (params) return {
|
|
@@ -184,6 +184,8 @@ function serveStaticMetadataRoute(route) {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
async function handleMetadataRouteRequest(options) {
|
|
187
|
+
let urlParts;
|
|
188
|
+
const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
|
|
187
189
|
for (const route of options.metadataRoutes) {
|
|
188
190
|
const functions = getMetadataRouteFunctions(route);
|
|
189
191
|
if (route.type === "sitemap" && route.isDynamic) {
|
|
@@ -193,7 +195,7 @@ async function handleMetadataRouteRequest(options) {
|
|
|
193
195
|
continue;
|
|
194
196
|
}
|
|
195
197
|
}
|
|
196
|
-
const match = matchMetadataRoute(route, options.cleanPathname, functions);
|
|
198
|
+
const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
|
|
197
199
|
if (!match) continue;
|
|
198
200
|
return route.isDynamic ? callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route);
|
|
199
201
|
}
|
|
@@ -19,6 +19,19 @@ type MiddlewareHandler = (request: NextRequest, event: NextFetchEvent) => Respon
|
|
|
19
19
|
type ExecuteMiddlewareOptions = {
|
|
20
20
|
basePath?: string;
|
|
21
21
|
filePath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the incoming request was inside the configured basePath. Drives
|
|
24
|
+
* the `nextUrl.basePath` the middleware observes: in-basePath requests are
|
|
25
|
+
* re-prefixed so NextURL reports the configured basePath, while
|
|
26
|
+
* out-of-basePath ("absolute path") requests stay un-prefixed so middleware
|
|
27
|
+
* sees `nextUrl.basePath === ""` (Next.js `getNextPathnameInfo` semantics —
|
|
28
|
+
* see test/e2e/middleware-base-path "should execute from absolute paths").
|
|
29
|
+
* When omitted it is derived from the request URL, which is correct for the
|
|
30
|
+
* Pages prod/deploy adapters because they pass the original (un-stripped)
|
|
31
|
+
* URL. Callers that pass an already-stripped URL (dev server, App Router)
|
|
32
|
+
* must set this explicitly.
|
|
33
|
+
*/
|
|
34
|
+
hadBasePath?: boolean;
|
|
22
35
|
i18nConfig?: NextI18nConfig | null;
|
|
23
36
|
includeErrorDetails?: boolean;
|
|
24
37
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
2
|
+
import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
2
3
|
import "./server-globals.js";
|
|
3
4
|
import { getRequestExecutionContext, runWithExecutionContext } from "../shims/request-context.js";
|
|
4
5
|
import { MIDDLEWARE_REWRITE_HEADER } from "./headers.js";
|
|
@@ -99,12 +100,13 @@ function resolveMiddlewarePathname(request) {
|
|
|
99
100
|
return badRequestResponse();
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
|
-
function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash) {
|
|
103
|
+
function createNextRequest(request, normalizedPathname, i18nConfig, basePath, trailingSlash, hadBasePath) {
|
|
103
104
|
const url = new URL(request.url);
|
|
104
105
|
let mwRequest = request.body && !request.bodyUsed ? request.clone() : request;
|
|
105
|
-
|
|
106
|
+
const mwPathname = basePath && hadBasePath ? addBasePathToPathname(normalizedPathname, basePath) : normalizedPathname;
|
|
107
|
+
if (mwPathname !== url.pathname) {
|
|
106
108
|
const mwUrl = new URL(url);
|
|
107
|
-
mwUrl.pathname =
|
|
109
|
+
mwUrl.pathname = mwPathname;
|
|
108
110
|
mwRequest = new Request(mwUrl, mwRequest);
|
|
109
111
|
}
|
|
110
112
|
const nextConfig = basePath || i18nConfig || trailingSlash ? {
|
|
@@ -124,9 +126,11 @@ async function executeMiddleware(options) {
|
|
|
124
126
|
continue: false,
|
|
125
127
|
response: normalizedPathname
|
|
126
128
|
};
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
129
|
+
const hadBasePath = options.hadBasePath ?? (!options.basePath || hasBasePath(new URL(options.request.url).pathname, options.basePath));
|
|
130
|
+
const matchPathname = options.basePath ? stripBasePath(normalizedPathname, options.basePath) : normalizedPathname;
|
|
131
|
+
if (!matchesMiddleware(matchPathname, middlewareMatcher(options.module), options.request, options.i18nConfig)) return { continue: true };
|
|
132
|
+
const nextRequest = createNextRequest(options.request, normalizedPathname, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath);
|
|
133
|
+
const fetchEvent = new NextFetchEvent({ page: matchPathname });
|
|
130
134
|
let response;
|
|
131
135
|
try {
|
|
132
136
|
response = await middlewareFn(nextRequest, fetchEvent);
|
|
@@ -195,7 +199,7 @@ async function executeMiddleware(options) {
|
|
|
195
199
|
try {
|
|
196
200
|
const rewriteParsed = new URL(rewriteUrl, options.request.url);
|
|
197
201
|
const requestOrigin = new URL(options.request.url).origin;
|
|
198
|
-
if (rewriteParsed.origin === requestOrigin) rewritePath = rewriteParsed.pathname + rewriteParsed.search;
|
|
202
|
+
if (rewriteParsed.origin === requestOrigin) rewritePath = (options.basePath ? stripBasePath(rewriteParsed.pathname, options.basePath) : rewriteParsed.pathname) + rewriteParsed.search;
|
|
199
203
|
else rewritePath = rewriteParsed.href;
|
|
200
204
|
} catch {
|
|
201
205
|
rewritePath = rewriteUrl;
|
|
@@ -85,6 +85,7 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
|
|
|
85
85
|
return runGeneratedMiddleware({
|
|
86
86
|
basePath,
|
|
87
87
|
filePath: middlewarePath,
|
|
88
|
+
hadBasePath: true,
|
|
88
89
|
i18nConfig,
|
|
89
90
|
includeErrorDetails: process.env.NODE_ENV !== "production",
|
|
90
91
|
isDataRequest,
|
|
@@ -114,6 +114,63 @@ type FlightResultV0 = {
|
|
|
114
114
|
href: string;
|
|
115
115
|
targetSnapshot: RouteSnapshotV0;
|
|
116
116
|
};
|
|
117
|
+
type RscFetchResultSource = "cached" | "live";
|
|
118
|
+
type RscFetchResultFactsV0 = {
|
|
119
|
+
source: RscFetchResultSource;
|
|
120
|
+
currentHref: string;
|
|
121
|
+
origin: string;
|
|
122
|
+
effectiveHistoryUpdateMode: "push" | "replace";
|
|
123
|
+
redirectDepth: number;
|
|
124
|
+
requestPreviousNextUrl: string | null;
|
|
125
|
+
clientCompatibilityId: string | null;
|
|
126
|
+
responseOk: boolean;
|
|
127
|
+
isRscContentType: boolean;
|
|
128
|
+
hasBody: boolean;
|
|
129
|
+
compatibilityIdHeader: string | null;
|
|
130
|
+
responseUrl: string | null;
|
|
131
|
+
streamedRedirectTarget: string | null;
|
|
132
|
+
};
|
|
133
|
+
type RscRedirectFollowV0 = {
|
|
134
|
+
href: string;
|
|
135
|
+
historyUpdateMode: "push" | "replace";
|
|
136
|
+
previousNextUrl: string | null;
|
|
137
|
+
redirectDepth: number;
|
|
138
|
+
};
|
|
139
|
+
type RscFetchResultHardNavReason = "invalidRscPayload" | "rscCompatibilityMismatch" | "externalRedirectTarget" | "redirectDepthExhausted" | "streamedRedirectLoop";
|
|
140
|
+
type RscFetchResultDecisionV0 = {
|
|
141
|
+
kind: "proceedToCommit";
|
|
142
|
+
discardBody: false;
|
|
143
|
+
trace: NavigationTrace;
|
|
144
|
+
} | {
|
|
145
|
+
kind: "followRedirect";
|
|
146
|
+
discardBody: boolean;
|
|
147
|
+
redirect: RscRedirectFollowV0;
|
|
148
|
+
trace: NavigationTrace;
|
|
149
|
+
} | {
|
|
150
|
+
kind: "hardNavigate";
|
|
151
|
+
discardBody: boolean;
|
|
152
|
+
url: string;
|
|
153
|
+
reason: RscFetchResultHardNavReason;
|
|
154
|
+
trace: NavigationTrace;
|
|
155
|
+
};
|
|
156
|
+
type EarlyNavigationIntentFactsV0 = {
|
|
157
|
+
basePath: string;
|
|
158
|
+
currentHref: string;
|
|
159
|
+
mode: "push" | "replace";
|
|
160
|
+
scroll: boolean;
|
|
161
|
+
targetHref: string;
|
|
162
|
+
};
|
|
163
|
+
type EarlyNavigationIntentDecisionV0 = {
|
|
164
|
+
kind: "sameDocumentScroll";
|
|
165
|
+
hash: string;
|
|
166
|
+
mode: "push" | "replace";
|
|
167
|
+
scroll: boolean;
|
|
168
|
+
trace: NavigationTrace;
|
|
169
|
+
} | {
|
|
170
|
+
kind: "flightNavigation";
|
|
171
|
+
bypassNavigationCache: boolean;
|
|
172
|
+
trace: NavigationTrace;
|
|
173
|
+
};
|
|
117
174
|
type NavigationPlannerInput = {
|
|
118
175
|
routeManifest: RouteManifest | null;
|
|
119
176
|
state: NavigationPlannerStateV0;
|
|
@@ -122,6 +179,8 @@ type NavigationPlannerInput = {
|
|
|
122
179
|
type AcceptedCacheEntryReuseDecision = Extract<CacheEntryReuseDecision, {
|
|
123
180
|
canReuse: true;
|
|
124
181
|
}>;
|
|
182
|
+
declare function classifyRscFetchResult(facts: RscFetchResultFactsV0): RscFetchResultDecisionV0;
|
|
183
|
+
declare function classifyEarlyNavigationIntent(facts: EarlyNavigationIntentFactsV0): EarlyNavigationIntentDecisionV0;
|
|
125
184
|
declare function classifyRootBoundaryTransition(currentRootBoundaryId: string | null, nextRootBoundaryId: string | null): RootBoundaryTransition;
|
|
126
185
|
declare function resolveSameLayoutAncestorPersistence(currentSnapshot: RouteSnapshotV0, targetSnapshot: RouteSnapshotV0): readonly string[];
|
|
127
186
|
declare function resolveMountedParallelSlotPersistence(currentSnapshot: RouteSnapshotV0, targetSnapshot: RouteSnapshotV0): readonly string[];
|
|
@@ -143,6 +202,8 @@ declare function resolveDefaultOrUnmatchedSlotPersistenceForLayouts(options: {
|
|
|
143
202
|
}): readonly string[];
|
|
144
203
|
declare function planNavigation(input: NavigationPlannerInput): NavigationDecisionV0;
|
|
145
204
|
declare const navigationPlanner: {
|
|
205
|
+
classifyEarlyNavigationIntent: typeof classifyEarlyNavigationIntent;
|
|
206
|
+
classifyRscFetchResult: typeof classifyRscFetchResult;
|
|
146
207
|
classifyRootBoundaryTransition: typeof classifyRootBoundaryTransition;
|
|
147
208
|
plan: typeof planNavigation;
|
|
148
209
|
resolveCurrentRootBoundaryElementPersistence: typeof resolveCurrentRootBoundaryElementPersistence;
|
|
@@ -150,4 +211,4 @@ declare const navigationPlanner: {
|
|
|
150
211
|
resolveSameLayoutAncestorPersistence: typeof resolveSameLayoutAncestorPersistence;
|
|
151
212
|
};
|
|
152
213
|
//#endregion
|
|
153
|
-
export { FlightResultV0, InterceptionSnapshotV0, MountedParallelSlotSnapshotV0, NavigationDecisionV0, NavigationEvent, NavigationPlannerInput, NavigationPlannerStateV0, OperationLane, OperationToken, ParallelSlotBindingSnapshotV0, RefreshScope, RootBoundaryTransition, RouteSnapshotV0, TraverseDirection, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
|
|
214
|
+
export { EarlyNavigationIntentDecisionV0, EarlyNavigationIntentFactsV0, FlightResultV0, InterceptionSnapshotV0, MountedParallelSlotSnapshotV0, NavigationDecisionV0, NavigationEvent, NavigationPlannerInput, NavigationPlannerStateV0, OperationLane, OperationToken, ParallelSlotBindingSnapshotV0, RefreshScope, RootBoundaryTransition, RouteSnapshotV0, RscFetchResultDecisionV0, RscFetchResultFactsV0, TraverseDirection, navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts };
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
|
-
import {
|
|
2
|
+
import { stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
3
4
|
import { compareAppElementsSlotIds } from "./app-elements-wire.js";
|
|
4
5
|
import "./app-elements.js";
|
|
6
|
+
import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
|
|
7
|
+
import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
|
|
5
8
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
6
9
|
//#region src/server/navigation-planner.ts
|
|
7
10
|
const ROUTE_INTERCEPTION_CONTEXT_SEPARATOR = "\0";
|
|
@@ -27,6 +30,189 @@ function getRequestedWorkTargetHref(work) {
|
|
|
27
30
|
default: throw new Error("[vinext] Unknown requested navigation work: " + String(work));
|
|
28
31
|
}
|
|
29
32
|
}
|
|
33
|
+
function createRscFetchResultTraceFields(facts, fields = {}) {
|
|
34
|
+
return {
|
|
35
|
+
fetchResultSource: facts.source,
|
|
36
|
+
...fields
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function createRscFetchResultHardNavigationDecision(options) {
|
|
40
|
+
return {
|
|
41
|
+
discardBody: options.discardBody,
|
|
42
|
+
kind: "hardNavigate",
|
|
43
|
+
reason: options.reason,
|
|
44
|
+
trace: createNavigationTrace(options.reasonCode, createRscFetchResultTraceFields(options.facts, {
|
|
45
|
+
...options.redirectSignal !== void 0 ? { redirectSignal: options.redirectSignal } : {},
|
|
46
|
+
redirectDepth: options.facts.redirectDepth,
|
|
47
|
+
targetHref: options.url
|
|
48
|
+
})),
|
|
49
|
+
url: options.url
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createRscFetchResultFollowRedirectDecision(options) {
|
|
53
|
+
return {
|
|
54
|
+
discardBody: options.discardBody,
|
|
55
|
+
kind: "followRedirect",
|
|
56
|
+
redirect: options.redirect,
|
|
57
|
+
trace: createNavigationTrace(NavigationTraceReasonCodes.redirectFollow, createRscFetchResultTraceFields(options.facts, {
|
|
58
|
+
redirectDepth: options.redirect.redirectDepth,
|
|
59
|
+
redirectSignal: options.redirectSignal,
|
|
60
|
+
targetHref: options.redirect.href
|
|
61
|
+
}))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function mapRscRedirectTerminalReason(reason) {
|
|
65
|
+
switch (reason) {
|
|
66
|
+
case "externalRedirect": return {
|
|
67
|
+
hardNavigationReason: "externalRedirectTarget",
|
|
68
|
+
traceReasonCode: NavigationTraceReasonCodes.redirectTerminalExternal
|
|
69
|
+
};
|
|
70
|
+
case "maxRedirectsExceeded": return {
|
|
71
|
+
hardNavigationReason: "redirectDepthExhausted",
|
|
72
|
+
traceReasonCode: NavigationTraceReasonCodes.redirectTerminalDepth
|
|
73
|
+
};
|
|
74
|
+
default: throw new Error("[vinext] Unknown RSC redirect terminal reason: " + String(reason));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function classifyRscFetchResult(facts) {
|
|
78
|
+
if (!facts.responseOk || !facts.isRscContentType || !facts.hasBody) {
|
|
79
|
+
const url = resolveHardNavigationTargetFromRscResponse(facts.responseUrl, facts.currentHref, facts.origin);
|
|
80
|
+
return createRscFetchResultHardNavigationDecision({
|
|
81
|
+
discardBody: false,
|
|
82
|
+
facts,
|
|
83
|
+
reason: "invalidRscPayload",
|
|
84
|
+
reasonCode: NavigationTraceReasonCodes.invalidRscPayload,
|
|
85
|
+
url
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const compatibilityDecision = resolveRscCompatibilityNavigationDecision({
|
|
89
|
+
clientCompatibilityId: facts.clientCompatibilityId,
|
|
90
|
+
currentHref: facts.currentHref,
|
|
91
|
+
origin: facts.origin,
|
|
92
|
+
responseCompatibilityId: facts.compatibilityIdHeader,
|
|
93
|
+
responseUrl: facts.responseUrl
|
|
94
|
+
});
|
|
95
|
+
if (compatibilityDecision.kind === "hard-navigate") return createRscFetchResultHardNavigationDecision({
|
|
96
|
+
discardBody: false,
|
|
97
|
+
facts,
|
|
98
|
+
reason: "rscCompatibilityMismatch",
|
|
99
|
+
reasonCode: NavigationTraceReasonCodes.rscCompatibilityMismatch,
|
|
100
|
+
url: compatibilityDecision.hardNavigationTarget
|
|
101
|
+
});
|
|
102
|
+
if (facts.responseUrl !== null) {
|
|
103
|
+
const redirectDecision = resolveRscRedirectLifecycleHop({
|
|
104
|
+
currentHref: facts.currentHref,
|
|
105
|
+
historyUpdateMode: facts.effectiveHistoryUpdateMode,
|
|
106
|
+
origin: facts.origin,
|
|
107
|
+
redirectDepth: facts.redirectDepth,
|
|
108
|
+
requestPreviousNextUrl: facts.requestPreviousNextUrl,
|
|
109
|
+
responseUrl: facts.responseUrl
|
|
110
|
+
});
|
|
111
|
+
if (redirectDecision.kind === "terminal-hard-navigation") {
|
|
112
|
+
const terminalReason = mapRscRedirectTerminalReason(redirectDecision.reason);
|
|
113
|
+
return createRscFetchResultHardNavigationDecision({
|
|
114
|
+
discardBody: false,
|
|
115
|
+
facts,
|
|
116
|
+
reason: terminalReason.hardNavigationReason,
|
|
117
|
+
reasonCode: terminalReason.traceReasonCode,
|
|
118
|
+
redirectSignal: "response-url",
|
|
119
|
+
url: redirectDecision.href
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (redirectDecision.kind === "follow") return createRscFetchResultFollowRedirectDecision({
|
|
123
|
+
discardBody: false,
|
|
124
|
+
facts,
|
|
125
|
+
redirect: {
|
|
126
|
+
href: redirectDecision.href,
|
|
127
|
+
historyUpdateMode: facts.effectiveHistoryUpdateMode,
|
|
128
|
+
previousNextUrl: redirectDecision.previousNextUrl,
|
|
129
|
+
redirectDepth: redirectDecision.redirectDepth
|
|
130
|
+
},
|
|
131
|
+
redirectSignal: "response-url"
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (facts.streamedRedirectTarget !== null) {
|
|
135
|
+
const redirectDecision = resolveStreamedRscRedirectLifecycleHop({
|
|
136
|
+
currentHref: facts.currentHref,
|
|
137
|
+
historyUpdateMode: facts.effectiveHistoryUpdateMode,
|
|
138
|
+
origin: facts.origin,
|
|
139
|
+
redirectDepth: facts.redirectDepth,
|
|
140
|
+
requestPreviousNextUrl: facts.requestPreviousNextUrl,
|
|
141
|
+
streamedRedirectTarget: facts.streamedRedirectTarget
|
|
142
|
+
});
|
|
143
|
+
if (redirectDecision.kind === "terminal-hard-navigation") {
|
|
144
|
+
const terminalReason = mapRscRedirectTerminalReason(redirectDecision.reason);
|
|
145
|
+
return createRscFetchResultHardNavigationDecision({
|
|
146
|
+
discardBody: true,
|
|
147
|
+
facts,
|
|
148
|
+
reason: terminalReason.hardNavigationReason,
|
|
149
|
+
reasonCode: terminalReason.traceReasonCode,
|
|
150
|
+
redirectSignal: "streamed-header",
|
|
151
|
+
url: redirectDecision.href
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (redirectDecision.kind === "follow") return createRscFetchResultFollowRedirectDecision({
|
|
155
|
+
discardBody: true,
|
|
156
|
+
facts,
|
|
157
|
+
redirect: {
|
|
158
|
+
href: redirectDecision.href,
|
|
159
|
+
historyUpdateMode: facts.effectiveHistoryUpdateMode,
|
|
160
|
+
previousNextUrl: redirectDecision.previousNextUrl,
|
|
161
|
+
redirectDepth: redirectDecision.redirectDepth
|
|
162
|
+
},
|
|
163
|
+
redirectSignal: "streamed-header"
|
|
164
|
+
});
|
|
165
|
+
return createRscFetchResultHardNavigationDecision({
|
|
166
|
+
discardBody: true,
|
|
167
|
+
facts,
|
|
168
|
+
reason: "streamedRedirectLoop",
|
|
169
|
+
reasonCode: NavigationTraceReasonCodes.streamedRedirectLoop,
|
|
170
|
+
redirectSignal: "streamed-header",
|
|
171
|
+
url: redirectDecision.href
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
discardBody: false,
|
|
176
|
+
kind: "proceedToCommit",
|
|
177
|
+
trace: createNavigationTrace(NavigationTraceReasonCodes.proceedToCommit, createRscFetchResultTraceFields(facts))
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function createEarlyNavigationIntentTrace(reasonCode, facts) {
|
|
181
|
+
return createNavigationTrace(reasonCode, { targetHref: facts.targetHref });
|
|
182
|
+
}
|
|
183
|
+
function classifyEarlyNavigationIntent(facts) {
|
|
184
|
+
let current;
|
|
185
|
+
let next;
|
|
186
|
+
try {
|
|
187
|
+
current = new URL(facts.currentHref);
|
|
188
|
+
next = new URL(facts.targetHref, facts.currentHref);
|
|
189
|
+
} catch {
|
|
190
|
+
return {
|
|
191
|
+
bypassNavigationCache: false,
|
|
192
|
+
kind: "flightNavigation",
|
|
193
|
+
trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.crossDocumentFlight, facts)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const samePathname = current.origin === next.origin && stripBasePath(current.pathname, facts.basePath) === stripBasePath(next.pathname, facts.basePath);
|
|
197
|
+
const sameSearch = current.searchParams.toString() === next.searchParams.toString();
|
|
198
|
+
if (samePathname && sameSearch && next.hash !== "") return {
|
|
199
|
+
hash: next.hash,
|
|
200
|
+
kind: "sameDocumentScroll",
|
|
201
|
+
mode: facts.mode,
|
|
202
|
+
scroll: facts.scroll,
|
|
203
|
+
trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.sameDocumentScroll, facts)
|
|
204
|
+
};
|
|
205
|
+
if (samePathname && !sameSearch) return {
|
|
206
|
+
bypassNavigationCache: true,
|
|
207
|
+
kind: "flightNavigation",
|
|
208
|
+
trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.samePageSearch, facts)
|
|
209
|
+
};
|
|
210
|
+
return {
|
|
211
|
+
bypassNavigationCache: false,
|
|
212
|
+
kind: "flightNavigation",
|
|
213
|
+
trace: createEarlyNavigationIntentTrace(NavigationTraceReasonCodes.crossDocumentFlight, facts)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
30
216
|
function createSnapshotRouteTopology(snapshot) {
|
|
31
217
|
return {
|
|
32
218
|
layoutIds: snapshot.layoutIds,
|
|
@@ -119,8 +305,10 @@ function findRouteManifestInterceptionForProof(routeManifest, proof) {
|
|
|
119
305
|
const candidateInterceptions = routeManifest.segmentGraph.interceptionsBySlotId.get(proof.slotId) ?? [];
|
|
120
306
|
for (const interception of candidateInterceptions) {
|
|
121
307
|
if (!matchRoutePatternPrefix(sourceParts, interception.sourcePatternParts)) continue;
|
|
122
|
-
|
|
123
|
-
|
|
308
|
+
const exactTargetParams = matchRoutePattern(targetParts, interception.targetPatternParts);
|
|
309
|
+
const allowsMiddlewareRewriteTarget = exactTargetParams === null && matchRoutePatternWithOptionalDynamicSegments(targetParts, interception.targetPatternParts);
|
|
310
|
+
if (exactTargetParams === null && !allowsMiddlewareRewriteTarget) continue;
|
|
311
|
+
if (!allowsMiddlewareRewriteTarget && interception.targetRouteId !== null && targetRoute?.id !== interception.targetRouteId) continue;
|
|
124
312
|
return interception;
|
|
125
313
|
}
|
|
126
314
|
return null;
|
|
@@ -493,6 +681,8 @@ function planNavigation(input) {
|
|
|
493
681
|
}
|
|
494
682
|
}
|
|
495
683
|
const navigationPlanner = {
|
|
684
|
+
classifyEarlyNavigationIntent,
|
|
685
|
+
classifyRscFetchResult,
|
|
496
686
|
classifyRootBoundaryTransition,
|
|
497
687
|
plan: planNavigation,
|
|
498
688
|
resolveCurrentRootBoundaryElementPersistence,
|
|
@@ -4,6 +4,8 @@ type NavigationTraceSchemaVersion = 0;
|
|
|
4
4
|
declare const NavigationTraceReasonCodes: {
|
|
5
5
|
cacheProofRejected: "NC_CACHE_REJECT";
|
|
6
6
|
commitCurrent: "NC_COMMIT";
|
|
7
|
+
crossDocumentFlight: "NC_CROSS_DOC_FLIGHT";
|
|
8
|
+
invalidRscPayload: "NC_RSC_INVALID";
|
|
7
9
|
interceptedCommitCurrent: "NC_INTERCEPT_COMMIT";
|
|
8
10
|
interceptedRejectedIncompatibleRoot: "NC_INTERCEPT_REJECT_ROOT";
|
|
9
11
|
interceptedRejectedMissingProof: "NC_INTERCEPT_REJECT_MISSING_PROOF";
|
|
@@ -12,10 +14,18 @@ declare const NavigationTraceReasonCodes: {
|
|
|
12
14
|
interceptedRejectedUndeclaredTopology: "NC_INTERCEPT_REJECT_GRAPH";
|
|
13
15
|
interceptedRejectedUnknownSource: "NC_INTERCEPT_REJECT_SOURCE";
|
|
14
16
|
prefetchOnly: "NC_PREFETCH_ONLY";
|
|
17
|
+
proceedToCommit: "NC_RSC_PROCEED";
|
|
18
|
+
redirectFollow: "NC_RSC_REDIRECT_FOLLOW";
|
|
19
|
+
redirectTerminalDepth: "NC_RSC_REDIRECT_DEPTH";
|
|
20
|
+
redirectTerminalExternal: "NC_RSC_REDIRECT_EXTERNAL";
|
|
15
21
|
requestWork: "NC_REQUEST";
|
|
16
22
|
rootBoundaryChanged: "NC_ROOT";
|
|
17
23
|
rootBoundaryUnknown: "NC_ROOT_UNKNOWN";
|
|
24
|
+
rscCompatibilityMismatch: "NC_RSC_COMPAT_MISMATCH";
|
|
25
|
+
sameDocumentScroll: "NC_SAME_DOC_SCROLL";
|
|
26
|
+
samePageSearch: "NC_SAME_PAGE_SEARCH";
|
|
18
27
|
staleOperation: "NC_STALE";
|
|
28
|
+
streamedRedirectLoop: "NC_RSC_STREAMED_REDIRECT_LOOP";
|
|
19
29
|
};
|
|
20
30
|
declare const NavigationTraceTransactionCodes: {
|
|
21
31
|
hardNavigate: "NT_HARD_NAVIGATE";
|
|
@@ -25,7 +35,7 @@ declare const NavigationTraceTransactionCodes: {
|
|
|
25
35
|
type NavigationTraceReasonCode = (typeof NavigationTraceReasonCodes)[keyof typeof NavigationTraceReasonCodes];
|
|
26
36
|
type NavigationTraceTransactionCode = (typeof NavigationTraceTransactionCodes)[keyof typeof NavigationTraceTransactionCodes];
|
|
27
37
|
type NavigationTraceCode = NavigationTraceReasonCode | NavigationTraceTransactionCode;
|
|
28
|
-
type NavigationTraceFieldName = "activeNavigationId" | "cacheProofCode" | "cacheProofMode" | "cacheProofReuseClass" | "cacheProofScope" | "currentRootLayoutTreePath" | "currentVisibleCommitVersion" | "nextRootLayoutTreePath" | "eventKind" | "operationLane" | "pendingOperationId" | "startedVisibleCommitVersion" | "startedNavigationId" | "targetHref" | "traverseDirection";
|
|
38
|
+
type NavigationTraceFieldName = "activeNavigationId" | "cacheProofCode" | "cacheProofMode" | "cacheProofReuseClass" | "cacheProofScope" | "currentRootLayoutTreePath" | "currentVisibleCommitVersion" | "nextRootLayoutTreePath" | "eventKind" | "fetchResultSource" | "operationLane" | "pendingOperationId" | "redirectDepth" | "redirectSignal" | "startedVisibleCommitVersion" | "startedNavigationId" | "targetHref" | "traverseDirection";
|
|
29
39
|
type NavigationTraceFieldValue = string | number | boolean | null;
|
|
30
40
|
type NavigationTraceFields = Readonly<Partial<Record<NavigationTraceFieldName, NavigationTraceFieldValue>>>;
|
|
31
41
|
type NavigationTraceEntry = Readonly<{
|
|
@@ -47,4 +57,4 @@ declare function createNavigationLifecycleTraceFields(options: {
|
|
|
47
57
|
declare function createNavigationTrace(code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
|
|
48
58
|
declare function prependNavigationTraceEntry(trace: NavigationTrace, code: NavigationTraceCode, fields?: NavigationTraceFields): NavigationTrace;
|
|
49
59
|
//#endregion
|
|
50
|
-
export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace,
|
|
60
|
+
export { NAVIGATION_TRACE_SCHEMA_VERSION, NavigationTrace, NavigationTraceFields, NavigationTraceReasonCode, NavigationTraceReasonCodes, NavigationTraceTransactionCode, NavigationTraceTransactionCodes, createNavigationLifecycleTraceFields, createNavigationTrace, prependNavigationTraceEntry };
|
|
@@ -3,6 +3,8 @@ const NAVIGATION_TRACE_SCHEMA_VERSION = 0;
|
|
|
3
3
|
const NavigationTraceReasonCodes = {
|
|
4
4
|
cacheProofRejected: "NC_CACHE_REJECT",
|
|
5
5
|
commitCurrent: "NC_COMMIT",
|
|
6
|
+
crossDocumentFlight: "NC_CROSS_DOC_FLIGHT",
|
|
7
|
+
invalidRscPayload: "NC_RSC_INVALID",
|
|
6
8
|
interceptedCommitCurrent: "NC_INTERCEPT_COMMIT",
|
|
7
9
|
interceptedRejectedIncompatibleRoot: "NC_INTERCEPT_REJECT_ROOT",
|
|
8
10
|
interceptedRejectedMissingProof: "NC_INTERCEPT_REJECT_MISSING_PROOF",
|
|
@@ -11,10 +13,18 @@ const NavigationTraceReasonCodes = {
|
|
|
11
13
|
interceptedRejectedUndeclaredTopology: "NC_INTERCEPT_REJECT_GRAPH",
|
|
12
14
|
interceptedRejectedUnknownSource: "NC_INTERCEPT_REJECT_SOURCE",
|
|
13
15
|
prefetchOnly: "NC_PREFETCH_ONLY",
|
|
16
|
+
proceedToCommit: "NC_RSC_PROCEED",
|
|
17
|
+
redirectFollow: "NC_RSC_REDIRECT_FOLLOW",
|
|
18
|
+
redirectTerminalDepth: "NC_RSC_REDIRECT_DEPTH",
|
|
19
|
+
redirectTerminalExternal: "NC_RSC_REDIRECT_EXTERNAL",
|
|
14
20
|
requestWork: "NC_REQUEST",
|
|
15
21
|
rootBoundaryChanged: "NC_ROOT",
|
|
16
22
|
rootBoundaryUnknown: "NC_ROOT_UNKNOWN",
|
|
17
|
-
|
|
23
|
+
rscCompatibilityMismatch: "NC_RSC_COMPAT_MISMATCH",
|
|
24
|
+
sameDocumentScroll: "NC_SAME_DOC_SCROLL",
|
|
25
|
+
samePageSearch: "NC_SAME_PAGE_SEARCH",
|
|
26
|
+
staleOperation: "NC_STALE",
|
|
27
|
+
streamedRedirectLoop: "NC_RSC_STREAMED_REDIRECT_LOOP"
|
|
18
28
|
};
|
|
19
29
|
const NavigationTraceTransactionCodes = {
|
|
20
30
|
hardNavigate: "NT_HARD_NAVIGATE",
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
//#region src/server/normalize-path.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Re-encode path delimiter characters that were decoded by decodeURIComponent.
|
|
4
|
-
* After decoding a URL segment, characters like / # ? \ need to be re-encoded
|
|
5
|
-
* so they don't change the path structure.
|
|
6
|
-
*
|
|
7
|
-
* Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
|
|
8
|
-
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
|
|
9
|
-
*/
|
|
10
2
|
declare function escapePathDelimiters(segment: string, escapeEncoded?: boolean): string;
|
|
11
3
|
/**
|
|
12
4
|
* Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* Ported from Next.js: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
|
|
8
8
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
|
|
9
9
|
*/
|
|
10
|
+
const PATH_DELIMITERS_RE = /([/#?])/gi;
|
|
11
|
+
const PATH_DELIMITERS_ENCODED_RE = /([/#?]|%(2f|23|3f|5c))/gi;
|
|
10
12
|
function escapePathDelimiters(segment, escapeEncoded) {
|
|
11
|
-
return segment.replace(
|
|
13
|
+
return segment.replace(escapeEncoded ? PATH_DELIMITERS_ENCODED_RE : PATH_DELIMITERS_RE, (char) => encodeURIComponent(char));
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* Decode a URL pathname segment-by-segment, preserving encoded path delimiters.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/server/otel-tracer-extension.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry tracer provider extension for Cache Components.
|
|
4
|
+
*
|
|
5
|
+
* When `cacheComponents: true` is enabled in next.config, component renders
|
|
6
|
+
* go through multiple phases (warmup → resume). During these phases, the
|
|
7
|
+
* `workUnitAsyncStorage` carries a prerender or cache store. Without this
|
|
8
|
+
* extension, calls to `tracer.startSpan()` / `tracer.startActiveSpan()` from
|
|
9
|
+
* inside user RSC code would inherit that prerender context, causing:
|
|
10
|
+
*
|
|
11
|
+
* 1. Spans to reuse the same trace ID across requests (the frozen prerender
|
|
12
|
+
* context bleeds into the runtime resume render).
|
|
13
|
+
* 2. Spans not being created at all during fallback resume (the work unit
|
|
14
|
+
* context gates span creation in some OTel SDK implementations).
|
|
15
|
+
*
|
|
16
|
+
* The fix mirrors Next.js's `instrumentation-node-extensions.ts`:
|
|
17
|
+
* - Wrap `tracer.startSpan` to exit `workUnitAsyncStorage` before creating
|
|
18
|
+
* the span, ensuring a clean context for span ID generation.
|
|
19
|
+
* - Wrap `tracer.startActiveSpan` similarly and re-enter the work unit store
|
|
20
|
+
* for the callback, so that the callback runs with the correct request
|
|
21
|
+
* context restored.
|
|
22
|
+
*
|
|
23
|
+
* This extension is intentionally a no-op when:
|
|
24
|
+
* - `@opentelemetry/api` is not installed (graceful degradation).
|
|
25
|
+
* - No OTel tracer provider has been registered (provider is the noop provider).
|
|
26
|
+
* - `workUnitAsyncStorage` has no active store (non-render contexts).
|
|
27
|
+
*
|
|
28
|
+
* References:
|
|
29
|
+
* - packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
|
|
30
|
+
* - https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Extend the registered OTel tracer provider so that `startSpan` and
|
|
34
|
+
* `startActiveSpan` exit the `workUnitAsyncStorage` context before creating
|
|
35
|
+
* spans. This prevents the prerender/cache work unit store from leaking into
|
|
36
|
+
* span ID generation during Cache Component fallback resumes.
|
|
37
|
+
*
|
|
38
|
+
* Safe to call multiple times — subsequent calls are no-ops once the provider
|
|
39
|
+
* has been wrapped.
|
|
40
|
+
*
|
|
41
|
+
* Must only be called in Node.js environments (not Edge runtime).
|
|
42
|
+
*/
|
|
43
|
+
declare function extendTracerProviderForCacheComponents(): void;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { extendTracerProviderForCacheComponents };
|