vinext 0.1.1 → 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/client-build-config.d.ts +7 -1
- package/dist/build/client-build-config.js +9 -1
- package/dist/check.js +4 -3
- package/dist/client/navigation-runtime.d.ts +3 -2
- package/dist/client/window-next.d.ts +6 -4
- package/dist/config/config-matchers.d.ts +11 -4
- package/dist/config/config-matchers.js +15 -2
- package/dist/config/next-config.d.ts +13 -0
- package/dist/config/next-config.js +2 -0
- package/dist/deploy.js +9 -2
- package/dist/entries/app-rsc-entry.js +7 -1
- package/dist/entries/pages-client-entry.js +1 -1
- package/dist/entries/pages-server-entry.js +7 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +86 -78
- 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/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 +23 -1
- package/dist/routing/app-route-graph.js +47 -8
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/app-browser-entry.js +108 -213
- 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-navigation-controller.d.ts +3 -2
- package/dist/server/app-browser-navigation-controller.js +10 -7
- 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.js +4 -7
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +3 -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 +4 -2
- package/dist/server/app-page-cache.js +9 -7
- package/dist/server/app-page-dispatch.d.ts +8 -0
- package/dist/server/app-page-dispatch.js +18 -5
- package/dist/server/app-page-element-builder.d.ts +22 -2
- package/dist/server/app-page-element-builder.js +37 -8
- 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 +1 -1
- package/dist/server/app-page-render.js +7 -14
- package/dist/server/app-page-request.d.ts +1 -0
- package/dist/server/app-page-request.js +3 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +8 -7
- package/dist/server/app-page-stream.d.ts +1 -6
- package/dist/server/app-page-stream.js +1 -4
- package/dist/server/app-route-handler-response.js +11 -10
- package/dist/server/app-route-handler-runtime.js +12 -1
- package/dist/server/app-rsc-handler.js +1 -1
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-server-action-execution.d.ts +11 -0
- package/dist/server/app-server-action-execution.js +5 -2
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/app-ssr-stream.js +9 -1
- package/dist/server/dev-lockfile.js +2 -1
- package/dist/server/dev-server.js +43 -12
- 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-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 +188 -0
- package/dist/server/navigation-trace.d.ts +11 -1
- 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 +3 -11
- package/dist/server/pages-node-compat.js +174 -121
- package/dist/server/pages-page-data.d.ts +28 -0
- package/dist/server/pages-page-data.js +61 -17
- package/dist/server/pages-page-handler.d.ts +1 -0
- package/dist/server/pages-page-handler.js +22 -6
- package/dist/server/pages-page-response.d.ts +45 -1
- package/dist/server/pages-page-response.js +66 -5
- package/dist/server/pages-readiness.d.ts +1 -1
- package/dist/server/pages-request-pipeline.d.ts +15 -1
- package/dist/server/pages-request-pipeline.js +23 -2
- package/dist/server/prod-server.d.ts +39 -1
- package/dist/server/prod-server.js +98 -34
- package/dist/shims/cache-runtime.js +9 -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 +4 -4
- package/dist/shims/error.js +37 -11
- package/dist/shims/fetch-cache.d.ts +9 -1
- package/dist/shims/fetch-cache.js +11 -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/metadata.d.ts +6 -2
- package/dist/shims/metadata.js +32 -14
- package/dist/shims/navigation.d.ts +7 -16
- package/dist/shims/navigation.js +33 -16
- package/dist/shims/router.js +28 -1
- 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.js +15 -5
- 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 +2 -2
|
@@ -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 { stripBasePath } from "../utils/base-path.js";
|
|
2
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,
|
|
@@ -495,6 +681,8 @@ function planNavigation(input) {
|
|
|
495
681
|
}
|
|
496
682
|
}
|
|
497
683
|
const navigationPlanner = {
|
|
684
|
+
classifyEarlyNavigationIntent,
|
|
685
|
+
classifyRscFetchResult,
|
|
498
686
|
classifyRootBoundaryTransition,
|
|
499
687
|
plan: planNavigation,
|
|
500
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<{
|
|
@@ -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 };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { workUnitAsyncStorage } from "../shims/internal/work-unit-async-storage.js";
|
|
2
|
+
//#region src/server/otel-tracer-extension.ts
|
|
3
|
+
/**
|
|
4
|
+
* OpenTelemetry tracer provider extension for Cache Components.
|
|
5
|
+
*
|
|
6
|
+
* When `cacheComponents: true` is enabled in next.config, component renders
|
|
7
|
+
* go through multiple phases (warmup → resume). During these phases, the
|
|
8
|
+
* `workUnitAsyncStorage` carries a prerender or cache store. Without this
|
|
9
|
+
* extension, calls to `tracer.startSpan()` / `tracer.startActiveSpan()` from
|
|
10
|
+
* inside user RSC code would inherit that prerender context, causing:
|
|
11
|
+
*
|
|
12
|
+
* 1. Spans to reuse the same trace ID across requests (the frozen prerender
|
|
13
|
+
* context bleeds into the runtime resume render).
|
|
14
|
+
* 2. Spans not being created at all during fallback resume (the work unit
|
|
15
|
+
* context gates span creation in some OTel SDK implementations).
|
|
16
|
+
*
|
|
17
|
+
* The fix mirrors Next.js's `instrumentation-node-extensions.ts`:
|
|
18
|
+
* - Wrap `tracer.startSpan` to exit `workUnitAsyncStorage` before creating
|
|
19
|
+
* the span, ensuring a clean context for span ID generation.
|
|
20
|
+
* - Wrap `tracer.startActiveSpan` similarly and re-enter the work unit store
|
|
21
|
+
* for the callback, so that the callback runs with the correct request
|
|
22
|
+
* context restored.
|
|
23
|
+
*
|
|
24
|
+
* This extension is intentionally a no-op when:
|
|
25
|
+
* - `@opentelemetry/api` is not installed (graceful degradation).
|
|
26
|
+
* - No OTel tracer provider has been registered (provider is the noop provider).
|
|
27
|
+
* - `workUnitAsyncStorage` has no active store (non-render contexts).
|
|
28
|
+
*
|
|
29
|
+
* References:
|
|
30
|
+
* - packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
|
|
31
|
+
* - https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/instrumentation-node-extensions.ts
|
|
32
|
+
*/
|
|
33
|
+
const extendedProviders = /* @__PURE__ */ new WeakSet();
|
|
34
|
+
const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
|
|
35
|
+
function isUseCacheFn(fn) {
|
|
36
|
+
return typeof fn === "function" && fn[USE_CACHE_FUNCTION_SYMBOL] === true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extend the registered OTel tracer provider so that `startSpan` and
|
|
40
|
+
* `startActiveSpan` exit the `workUnitAsyncStorage` context before creating
|
|
41
|
+
* spans. This prevents the prerender/cache work unit store from leaking into
|
|
42
|
+
* span ID generation during Cache Component fallback resumes.
|
|
43
|
+
*
|
|
44
|
+
* Safe to call multiple times — subsequent calls are no-ops once the provider
|
|
45
|
+
* has been wrapped.
|
|
46
|
+
*
|
|
47
|
+
* Must only be called in Node.js environments (not Edge runtime).
|
|
48
|
+
*/
|
|
49
|
+
function extendTracerProviderForCacheComponents() {
|
|
50
|
+
let api;
|
|
51
|
+
try {
|
|
52
|
+
const req = globalThis.require;
|
|
53
|
+
if (typeof req === "function") api = req("@opentelemetry/api");
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!api) return;
|
|
58
|
+
const provider = api.trace.getTracerProvider();
|
|
59
|
+
if (!provider || typeof provider.getTracer !== "function") return;
|
|
60
|
+
if (extendedProviders.has(provider)) return;
|
|
61
|
+
extendedProviders.add(provider);
|
|
62
|
+
const originalGetTracer = provider.getTracer.bind(provider);
|
|
63
|
+
const wrappedTracers = /* @__PURE__ */ new WeakSet();
|
|
64
|
+
provider.getTracer = (...args) => {
|
|
65
|
+
const tracer = originalGetTracer(...args);
|
|
66
|
+
if (!tracer || wrappedTracers.has(tracer)) return tracer;
|
|
67
|
+
const originalStartSpan = tracer.startSpan;
|
|
68
|
+
if (typeof originalStartSpan === "function") tracer.startSpan = (...startSpanArgs) => workUnitAsyncStorage.exit(() => originalStartSpan.apply(tracer, startSpanArgs));
|
|
69
|
+
const originalStartActiveSpan = tracer.startActiveSpan;
|
|
70
|
+
if (typeof originalStartActiveSpan === "function") tracer.startActiveSpan = (...startActiveSpanArgs) => {
|
|
71
|
+
const workUnitStore = workUnitAsyncStorage.getStore();
|
|
72
|
+
if (!workUnitStore) return originalStartActiveSpan.apply(tracer, startActiveSpanArgs);
|
|
73
|
+
let fnIdx = 0;
|
|
74
|
+
if (startActiveSpanArgs.length === 2 && typeof startActiveSpanArgs[1] === "function") fnIdx = 1;
|
|
75
|
+
else if (startActiveSpanArgs.length === 3 && typeof startActiveSpanArgs[2] === "function") fnIdx = 2;
|
|
76
|
+
else if (startActiveSpanArgs.length > 3 && typeof startActiveSpanArgs[3] === "function") fnIdx = 3;
|
|
77
|
+
if (fnIdx > 0) {
|
|
78
|
+
const originalFn = startActiveSpanArgs[fnIdx];
|
|
79
|
+
if (isUseCacheFn(originalFn)) console.error("A Cache Function (`use cache`) was passed to startActiveSpan which means it will receive a Span argument with a possibly random ID on every invocation leading to cache misses. Provide a wrapping function around the Cache Function that does not forward the Span argument to avoid this issue.");
|
|
80
|
+
startActiveSpanArgs[fnIdx] = (...cbArgs) => workUnitAsyncStorage.run(workUnitStore, originalFn, ...cbArgs);
|
|
81
|
+
}
|
|
82
|
+
return workUnitAsyncStorage.exit(() => originalStartActiveSpan.apply(tracer, startActiveSpanArgs));
|
|
83
|
+
};
|
|
84
|
+
wrappedTracers.add(tracer);
|
|
85
|
+
return tracer;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { extendTracerProviderForCacheComponents };
|
|
@@ -12,8 +12,7 @@ type PagesApiRouteConfig = {
|
|
|
12
12
|
* `bodyParser: false` is critical for webhook handlers (Stripe, GitHub,
|
|
13
13
|
* Slack, etc.) that need to read the raw bytes to verify an HMAC
|
|
14
14
|
* signature. With it set, `req.body` is left undefined and the raw stream
|
|
15
|
-
*
|
|
16
|
-
* code can consume it.
|
|
15
|
+
* remains available through the Node-readable request object.
|
|
17
16
|
*
|
|
18
17
|
* @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config
|
|
19
18
|
*/
|
|
@@ -22,9 +21,21 @@ type PagesApiRouteConfig = {
|
|
|
22
21
|
sizeLimit?: string | number;
|
|
23
22
|
};
|
|
24
23
|
responseLimit?: boolean | string | number;
|
|
24
|
+
/**
|
|
25
|
+
* `externalResolver: true` declares that the response is sent by an
|
|
26
|
+
* external resolver (e.g. express/connect proxy middleware) that may
|
|
27
|
+
* complete after the handler's promise settles. Next.js uses it to
|
|
28
|
+
* suppress the "API resolved without sending a response" dev warning;
|
|
29
|
+
* vinext additionally uses it to suppress the auto-`end()` safety net,
|
|
30
|
+
* which would otherwise resolve an empty response before the external
|
|
31
|
+
* resolver writes.
|
|
32
|
+
*
|
|
33
|
+
* @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config
|
|
34
|
+
*/
|
|
35
|
+
externalResolver?: boolean;
|
|
25
36
|
};
|
|
26
37
|
};
|
|
27
|
-
type PagesNodeApiRouteHandler = (req: PagesReqResRequest, res: PagesReqResResponse) =>
|
|
38
|
+
type PagesNodeApiRouteHandler = (req: PagesReqResRequest, res: PagesReqResResponse) => unknown;
|
|
28
39
|
type PagesEdgeApiRouteHandler = (request: Request) => Response | Promise<Response>;
|
|
29
40
|
type PagesApiRouteModule = {
|
|
30
41
|
/**
|
|
@@ -43,8 +43,13 @@ async function _handlePagesApiRoute(options) {
|
|
|
43
43
|
request: options.request,
|
|
44
44
|
url: options.url
|
|
45
45
|
});
|
|
46
|
+
let resWasPiped = false;
|
|
47
|
+
res.once("pipe", () => {
|
|
48
|
+
resWasPiped = true;
|
|
49
|
+
});
|
|
50
|
+
const externalResolver = route.module.config?.api?.externalResolver || false;
|
|
46
51
|
await route.module.default(req, res);
|
|
47
|
-
res.end();
|
|
52
|
+
if (!externalResolver && !resWasPiped && !res.headersSent) res.end();
|
|
48
53
|
return await responsePromise;
|
|
49
54
|
} catch (error) {
|
|
50
55
|
if (error instanceof PagesBodyParseError) return new Response(error.message, {
|
|
@@ -23,11 +23,14 @@ declare function resolveSsrManifest(manifest: Record<string, string[]> | null |
|
|
|
23
23
|
*/
|
|
24
24
|
declare function getManifestFilesForModule(manifest: Record<string, string[]> | null | undefined, moduleId: string | null | undefined): string[] | null;
|
|
25
25
|
/**
|
|
26
|
-
* Find the first `.js` file in the manifest for `moduleId` and return
|
|
27
|
-
*
|
|
28
|
-
* the matched page or the `_app` module
|
|
26
|
+
* Find the first `.js` file in the manifest for `moduleId` and return the URL it
|
|
27
|
+
* is actually SERVED from. Used to resolve the client-navigation / hydration URL
|
|
28
|
+
* for the matched page or the `_app` module (it is `import()`ed on the client),
|
|
29
|
+
* so it must point at the served location: `assetPrefix` replaces `basePath` for
|
|
30
|
+
* asset URLs. SSR-manifest values are base-anchored; re-anchor under any
|
|
31
|
+
* configured `assetPrefix` (default `""` keeps the legacy `"/" + file`).
|
|
29
32
|
*/
|
|
30
|
-
declare function resolveClientModuleUrl(manifest: Record<string, string[]> | null | undefined, moduleId: string | null | undefined): string | undefined;
|
|
33
|
+
declare function resolveClientModuleUrl(manifest: Record<string, string[]> | null | undefined, moduleId: string | null | undefined, basePath?: string, assetPrefix?: string): string | undefined;
|
|
31
34
|
type CollectAssetTagsOptions = {
|
|
32
35
|
/**
|
|
33
36
|
* SSR manifest mapping module file paths to their associated asset list.
|
|
@@ -46,6 +49,14 @@ type CollectAssetTagsOptions = {
|
|
|
46
49
|
* default.
|
|
47
50
|
*/
|
|
48
51
|
disableOptimizedLoading: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Configured `basePath` / `assetPrefix`. SSR-manifest values are base-anchored
|
|
54
|
+
* (needed for the lazy-chunk membership test), but the EMITTED href must point
|
|
55
|
+
* where the asset is actually served — `assetPrefix` replaces `basePath` for
|
|
56
|
+
* asset URLs. Default `""` (both unset) keeps the legacy `"/" + value` href.
|
|
57
|
+
*/
|
|
58
|
+
basePath?: string;
|
|
59
|
+
assetPrefix?: string;
|
|
49
60
|
};
|
|
50
61
|
/**
|
|
51
62
|
* Build the HTML `<link>` and `<script>` tag string for the SSR response.
|