vinext 1.4.0 → 1.4.1
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 +20 -20
- package/dist/build/prerender.js +3 -2
- package/dist/check.js +3 -6
- package/dist/client/dev-error-overlay-store.d.ts +1 -0
- package/dist/client/dev-error-overlay.d.ts +2 -0
- package/dist/client/dev-error-overlay.js +19 -3
- package/dist/config/config-matchers.js +2 -0
- package/dist/config/destination-params.d.ts +2 -1
- package/dist/config/destination-params.js +52 -2
- package/dist/config/next-config.d.ts +6 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.js +0 -1
- package/dist/entries/pages-server-entry.js +3 -1
- package/dist/index.js +29 -20
- package/dist/plugins/strip-server-exports.js +2 -1
- package/dist/server/api-handler.js +2 -1
- package/dist/server/app-browser-entry.js +7 -3
- package/dist/server/app-browser-rsc-redirect.js +3 -3
- package/dist/server/app-browser-server-action-client.js +2 -2
- package/dist/server/app-browser-visible-commit.js +2 -2
- package/dist/server/app-optimistic-routing.js +2 -3
- package/dist/server/app-page-boundary-render.js +5 -10
- package/dist/server/app-page-dispatch.d.ts +0 -1
- package/dist/server/app-page-dispatch.js +0 -1
- package/dist/server/app-page-element-builder.js +3 -3
- package/dist/server/app-page-execution.js +9 -6
- package/dist/server/app-page-render.d.ts +0 -1
- package/dist/server/app-page-render.js +14 -14
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.js +22 -5
- package/dist/server/app-page-stream.d.ts +10 -8
- package/dist/server/app-page-stream.js +2 -1
- package/dist/server/app-request-stage-independent-entry.js +1 -1
- package/dist/server/app-route-handler-execution.js +3 -2
- package/dist/server/app-route-handler-runtime.d.ts +1 -1
- package/dist/server/app-route-handler-runtime.js +11 -7
- package/dist/server/app-router-entry.js +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +10 -10
- package/dist/server/app-rsc-cache-busting.js +27 -68
- package/dist/server/app-rsc-error-handler.js +1 -1
- package/dist/server/app-rsc-errors.js +4 -1
- package/dist/server/app-rsc-handler.js +53 -62
- package/dist/server/app-rsc-request-normalization.d.ts +10 -12
- package/dist/server/app-rsc-request-normalization.js +11 -14
- package/dist/server/app-rsc-response-stage.js +4 -12
- package/dist/server/app-server-action-execution.js +8 -4
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +13 -8
- package/dist/server/app-worker-stages.d.ts +12 -3
- package/dist/server/app-worker-stages.js +37 -4
- package/dist/server/bfcache-identity.d.ts +7 -0
- package/dist/server/bfcache-identity.js +10 -1
- package/dist/server/cacheability-manifest.js +1 -2
- package/dist/server/dev-origin-check.d.ts +2 -1
- package/dist/server/dev-origin-check.js +2 -1
- package/dist/server/dev-server.js +2 -1
- package/dist/server/image-optimization.d.ts +5 -0
- package/dist/server/image-optimization.js +6 -1
- package/dist/server/isr-cache.js +4 -1
- package/dist/server/metadata-route-response.js +1 -1
- package/dist/server/middleware-matcher.js +19 -6
- package/dist/server/middleware-runtime.js +6 -2
- package/dist/server/multi-stage.d.ts +7 -3
- package/dist/server/pages-node-compat.d.ts +1 -1
- package/dist/server/pages-node-compat.js +2 -0
- package/dist/server/pages-page-data.js +2 -1
- package/dist/server/pages-page-handler.js +17 -6
- package/dist/server/pages-page-response.d.ts +9 -0
- package/dist/server/pages-page-response.js +18 -4
- package/dist/server/pages-request-stage-entry.js +24 -4
- package/dist/server/pages-router-entry.js +3 -2
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +42 -8
- package/dist/server/request-error-log.d.ts +9 -0
- package/dist/server/request-error-log.js +13 -0
- package/dist/server/request-pipeline.d.ts +9 -4
- package/dist/server/request-pipeline.js +23 -15
- package/dist/server/rsc-completion-metadata.js +3 -0
- package/dist/server/static-file-cache.d.ts +9 -0
- package/dist/server/static-file-cache.js +13 -2
- package/dist/shims/cache-request-state.d.ts +6 -6
- package/dist/shims/cache-request-state.js +6 -36
- package/dist/shims/cache-runtime.d.ts +3 -1
- package/dist/shims/cache-runtime.js +77 -47
- package/dist/shims/cache.d.ts +10 -5
- package/dist/shims/cache.js +35 -22
- package/dist/shims/client-hook-error.d.ts +2 -0
- package/dist/shims/client-hook-error.js +16 -1
- package/dist/shims/fetch-cache.js +6 -2
- package/dist/shims/headers.d.ts +2 -1
- package/dist/shims/headers.js +5 -3
- package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
- package/dist/shims/link.js +5 -10
- package/dist/shims/navigation.js +2 -3
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/slot.d.ts +6 -2
- package/dist/shims/slot.js +10 -3
- package/dist/shims/unified-request-context.d.ts +6 -0
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/cache-life-profiles.d.ts +33 -0
- package/dist/utils/cache-life-profiles.js +111 -0
- package/dist/utils/middleware-request-headers.d.ts +14 -3
- package/dist/utils/middleware-request-headers.js +54 -7
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
|
8
8
|
import { normalizePath } from "./normalize-path.js";
|
|
9
9
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
10
10
|
import { APP_RSC_RENDER_MODE_NAVIGATION, APP_RSC_RENDER_MODE_PREFETCH_EMPTY, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL, parseAppRscRenderMode } from "./app-rsc-render-mode.js";
|
|
11
|
-
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM
|
|
11
|
+
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM } from "./app-rsc-cache-busting.js";
|
|
12
12
|
import { normalizeInterceptionContextHeader } from "./app-interception-context-header.js";
|
|
13
13
|
//#region src/server/app-rsc-request-normalization.ts
|
|
14
14
|
function extractFlightRouterStatePath(value, depth = 0) {
|
|
@@ -78,13 +78,13 @@ function tryNormalizeComparablePathAndSearch(value, basePath, baseUrl) {
|
|
|
78
78
|
* 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
|
|
79
79
|
* 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
|
|
80
80
|
* `/__vinext/` bypasses this for internal prerender endpoints.
|
|
81
|
-
* 6. RSC detection:
|
|
82
|
-
* `_rsc` cache-busting query is
|
|
83
|
-
* responses do not share the
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
81
|
+
* 6. RSC detection: `RSC: 1`, as in Next.js. A `.rsc` suffix is an ordinary
|
|
82
|
+
* part of the pathname. The internal `_rsc` cache-busting query is
|
|
83
|
+
* validated separately so full-route Flight responses do not share the
|
|
84
|
+
* canonical HTML URL in caches that ignore Vary.
|
|
85
|
+
* 7. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
|
|
86
|
+
* 8. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
|
|
87
|
+
* 9. Read semantic render mode for refresh/action payload rendering
|
|
88
88
|
*
|
|
89
89
|
* @returns A 400 or 404 Response for invalid or out-of-scope inputs,
|
|
90
90
|
* or a NormalizedRscRequest for valid requests.
|
|
@@ -110,9 +110,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
|
|
|
110
110
|
requestPathname = stripBasePath(requestPathname, basePath);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
const isRscRequest =
|
|
114
|
-
const cleanPathname = stripRscSuffix(pathname);
|
|
115
|
-
const requestCleanPathname = stripRscSuffix(requestPathname);
|
|
113
|
+
const isRscRequest = request.headers.get("RSC") === "1";
|
|
116
114
|
const interceptionContextHeader = normalizeInterceptionContextHeader(request.headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER));
|
|
117
115
|
const rawInterceptionId = request.headers.get(VINEXT_INTERCEPTION_ID_HEADER);
|
|
118
116
|
if (rawInterceptionId !== null && (rawInterceptionId.length > 4096 || !rawInterceptionId.startsWith("interception:"))) return badRequestResponse();
|
|
@@ -124,7 +122,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
|
|
|
124
122
|
const routerState = parsePrefetchRouterState(request.headers.get(NEXT_ROUTER_STATE_TREE_HEADER));
|
|
125
123
|
if (nextUrl && routerState) {
|
|
126
124
|
const targetUrl = new URL(url);
|
|
127
|
-
targetUrl.pathname =
|
|
125
|
+
targetUrl.pathname = pathname;
|
|
128
126
|
targetUrl.searchParams.delete(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);
|
|
129
127
|
const routerPathAndSearch = tryNormalizeComparablePathAndSearch(routerState.pathAndSearch, basePath, url);
|
|
130
128
|
const nextPathAndSearch = tryNormalizeComparablePathAndSearch(nextUrl, basePath, url);
|
|
@@ -136,8 +134,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
|
|
|
136
134
|
hadBasePath,
|
|
137
135
|
url,
|
|
138
136
|
pathname,
|
|
139
|
-
|
|
140
|
-
requestCleanPathname,
|
|
137
|
+
requestPathname,
|
|
141
138
|
isRscRequest,
|
|
142
139
|
interceptionContextHeader,
|
|
143
140
|
interceptionIdHeader,
|
|
@@ -10,7 +10,7 @@ import { applyEffectiveRequestCookieHeader, getDraftModeCookieHeader, headersCon
|
|
|
10
10
|
import { ensureFetchPatch, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
|
|
11
11
|
import { traceFindPageComponents } from "./pages-execution-tracing.js";
|
|
12
12
|
import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
|
|
13
|
-
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, hasRscCacheBustingSearchParam, stripRscCacheBustingSearchParam
|
|
13
|
+
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, hasRscCacheBustingSearchParam, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
|
|
14
14
|
import { traceAppPageRender } from "./app-page-tracing.js";
|
|
15
15
|
import { buildPageCacheTags } from "./implicit-tags.js";
|
|
16
16
|
import { pickRootParams, setRootParams } from "../shims/root-params.js";
|
|
@@ -48,13 +48,6 @@ function requestWithoutRscCacheBustingSearchParam(request) {
|
|
|
48
48
|
stripRscCacheBustingSearchParam(url);
|
|
49
49
|
return cloneRequestWithUrl(request, url.toString());
|
|
50
50
|
}
|
|
51
|
-
function requestWithoutRscSuffix(request) {
|
|
52
|
-
const url = new URL(request.url);
|
|
53
|
-
const pathname = stripRscSuffix(url.pathname);
|
|
54
|
-
if (pathname === url.pathname) return request;
|
|
55
|
-
url.pathname = pathname;
|
|
56
|
-
return cloneRequestWithUrl(request, url.toString());
|
|
57
|
-
}
|
|
58
51
|
function pathnameForResolvedUrl(resolvedUrl) {
|
|
59
52
|
return resolvedUrl.split("#", 1)[0].split("?", 1)[0];
|
|
60
53
|
}
|
|
@@ -192,9 +185,8 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
|
|
|
192
185
|
setRootParams(rootParams);
|
|
193
186
|
if (props.kind === "app-route-handler") {
|
|
194
187
|
setCurrentFetchSoftTags(buildPageCacheTags(props.cleanPathname, [], [...route.routeSegments], "route"));
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const routeHandlerRequest = isEdgeRouteHandler(route.routeHandler) ? userlandRequest : normalizedUserlandRequest;
|
|
188
|
+
const userlandRequest = requestWithoutRscCacheBustingSearchParam(request);
|
|
189
|
+
const routeHandlerRequest = isEdgeRouteHandler(route.routeHandler) ? userlandRequest : request;
|
|
198
190
|
const routeHandlerUrl = new URL(routeHandlerRequest.url);
|
|
199
191
|
const internalRscValues = isEdgeRouteHandler(route.routeHandler) ? [] : routeHandlerUrl.searchParams.getAll(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);
|
|
200
192
|
routeHandlerUrl.search = searchParams.toString();
|
|
@@ -225,7 +217,7 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
|
|
|
225
217
|
handlerStart: process.env.NODE_ENV !== "production" ? performance.now() : 0,
|
|
226
218
|
interceptionContext: normalized.interceptionContextHeader,
|
|
227
219
|
interceptionId: normalized.interceptionIdHeader,
|
|
228
|
-
interceptionPathname: props.matchKind === "resolved" ? props.cleanPathname : normalized.
|
|
220
|
+
interceptionPathname: props.matchKind === "resolved" ? props.cleanPathname : normalized.requestPathname,
|
|
229
221
|
isProgressiveActionRender: false,
|
|
230
222
|
isRscRequest: normalized.isRscRequest,
|
|
231
223
|
middlewareContext,
|
|
@@ -11,6 +11,7 @@ import { internalServerErrorResponse, payloadTooLargeResponse } from "./http-err
|
|
|
11
11
|
import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayload } from "./request-pipeline.js";
|
|
12
12
|
import { splitPathSegments } from "../routing/utils.js";
|
|
13
13
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
14
|
+
import { logRequestError } from "./request-error-log.js";
|
|
14
15
|
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
15
16
|
import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
|
|
16
17
|
import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
|
|
@@ -509,7 +510,10 @@ function createRejectedActionResult(error) {
|
|
|
509
510
|
* Next.js, a thrown string is not logged in development.
|
|
510
511
|
*/
|
|
511
512
|
function logRejectedActionResult(error) {
|
|
512
|
-
if (process.env.NODE_ENV === "production" || hasDigest(error))
|
|
513
|
+
if (process.env.NODE_ENV === "production" || hasDigest(error)) {
|
|
514
|
+
if (typeof error === "string") console.error("[vinext] Server action error:", error);
|
|
515
|
+
else logRequestError("[vinext] Server action error:", error);
|
|
516
|
+
}
|
|
513
517
|
}
|
|
514
518
|
function getActionHttpFallbackStatus(error) {
|
|
515
519
|
const digest = getNextErrorDigest(error);
|
|
@@ -520,7 +524,7 @@ function getActionHttpFallbackStatus(error) {
|
|
|
520
524
|
}
|
|
521
525
|
function createServerActionErrorResponse(error, options) {
|
|
522
526
|
options.getAndClearPendingCookies();
|
|
523
|
-
|
|
527
|
+
logRequestError("[vinext] Server action error:", error);
|
|
524
528
|
options.reportRequestError(error, {
|
|
525
529
|
path: options.cleanPathname,
|
|
526
530
|
method: options.request.method,
|
|
@@ -627,7 +631,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
627
631
|
actionFailed = true;
|
|
628
632
|
actionFailedWithError = getActionHttpFallbackStatus(error) === null && !isServerActionNotFoundError(error, null);
|
|
629
633
|
if (actionFailedWithError) {
|
|
630
|
-
|
|
634
|
+
logRequestError("[vinext] Server action error:", error);
|
|
631
635
|
options.reportRequestError(error, {
|
|
632
636
|
path: options.cleanPathname,
|
|
633
637
|
method: options.request.method,
|
|
@@ -699,7 +703,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
699
703
|
});
|
|
700
704
|
getAndClearActionRevalidationKind();
|
|
701
705
|
options.getAndClearPendingCookies();
|
|
702
|
-
|
|
706
|
+
logRequestError("[vinext] Server action payload parsing error:", error);
|
|
703
707
|
options.reportRequestError(error, {
|
|
704
708
|
path: options.cleanPathname,
|
|
705
709
|
method: options.request.method,
|
|
@@ -72,6 +72,12 @@ export declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navCont
|
|
|
72
72
|
*/
|
|
73
73
|
isCacheCandidate?: boolean;
|
|
74
74
|
fallbackToErrorDocumentOnShellError?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the RSC render threw the given shell error. As in Next.js, in
|
|
77
|
+
* dev the error document carries only an error that the HTML render alone
|
|
78
|
+
* threw, for the overlay: the browser reports the others itself.
|
|
79
|
+
*/
|
|
80
|
+
isServerComponentError?: (error: unknown) => boolean;
|
|
75
81
|
/**
|
|
76
82
|
* The parallel slot not-found boundaries that catch the error that
|
|
77
83
|
* rejected the shell, or null when none catches it. The document then
|
|
@@ -2,6 +2,7 @@ import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
|
|
|
2
2
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
3
3
|
import { isOpenRedirectShaped, repeatedSlashRedirectResponse } from "./open-redirect.js";
|
|
4
4
|
import "./server-globals.js";
|
|
5
|
+
import { isNavigationSignalError } from "../utils/navigation-signal.js";
|
|
5
6
|
import { isPprFallbackShellAbortError } from "../shims/ppr-fallback-shell.js";
|
|
6
7
|
import { onRenderDynamicLatched } from "../shims/internal/headers-state.js";
|
|
7
8
|
import { AppRouterContext } from "../shims/internal/app-router-context.js";
|
|
@@ -15,6 +16,7 @@ import { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRsc
|
|
|
15
16
|
import { AppElementsWire } from "./app-elements-wire.js";
|
|
16
17
|
import "./app-elements.js";
|
|
17
18
|
import { createInitialBfcacheMaps } from "./app-bfcache-identity.js";
|
|
19
|
+
import { isBailoutToCSRError, isDynamicServerError } from "../shims/navigation-errors.js";
|
|
18
20
|
import { ServerInsertedHTMLContext, clearServerInsertedHTML, getBfcacheIdMapContext, registerServerInsertedHTMLCallback, renderServerInsertedHTML, setNavigationContext } from "../shims/navigation-context-state.js";
|
|
19
21
|
import "../shims/navigation-server.js";
|
|
20
22
|
import { BfcacheIdentityMapContext } from "../shims/slot.js";
|
|
@@ -71,13 +73,14 @@ function createUtf8Stream(html) {
|
|
|
71
73
|
controller.close();
|
|
72
74
|
} });
|
|
73
75
|
}
|
|
74
|
-
function buildBootstrapModuleScript(bootstrapModuleUrl, nonce) {
|
|
76
|
+
function buildBootstrapModuleScript(bootstrapModuleUrl, nonce, crossOrigin) {
|
|
75
77
|
if (!bootstrapModuleUrl) return "";
|
|
76
|
-
|
|
78
|
+
const crossOriginAttribute = crossOrigin === void 0 ? "" : ` crossorigin="${crossOrigin === "use-credentials" ? "use-credentials" : ""}"`;
|
|
79
|
+
return `<script type="module"${createNonceAttribute(nonce)} src="` + escapeHtmlAttr(bootstrapModuleUrl) + `"${crossOriginAttribute} id="_R_" async="" fetchPriority="low"><\/script>`;
|
|
77
80
|
}
|
|
78
|
-
function renderSsrErrorDocumentShell(bootstrapModuleUrl, nonce) {
|
|
79
|
-
const html = renderToStaticMarkup(createElement(DefaultGlobalError, { error: null })).replace("<style>", "<style data-vinext-error-shell-style=\"\">");
|
|
80
|
-
const bootstrapScript = buildBootstrapModuleScript(bootstrapModuleUrl, nonce);
|
|
81
|
+
function renderSsrErrorDocumentShell(bootstrapModuleUrl, nonce, crossOrigin) {
|
|
82
|
+
const html = renderToStaticMarkup(createElement(DefaultGlobalError, { error: null })).replace("<head>", "<head><meta name=\"robots\" content=\"noindex\"/>").replace("<style>", "<style data-vinext-error-shell-style=\"\">");
|
|
83
|
+
const bootstrapScript = buildBootstrapModuleScript(bootstrapModuleUrl, nonce, crossOrigin);
|
|
81
84
|
if (!bootstrapScript) return createUtf8Stream(`<!DOCTYPE html>${html}`);
|
|
82
85
|
const documentClose = "</body></html>";
|
|
83
86
|
if (!html.endsWith(documentClose)) return createUtf8Stream(`<!DOCTYPE html>${html}${bootstrapScript}`);
|
|
@@ -292,6 +295,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
292
295
|
let htmlStream;
|
|
293
296
|
let renderComplete = Promise.resolve();
|
|
294
297
|
let shellErrorRecovered = false;
|
|
298
|
+
let ssrOnlyShellError;
|
|
295
299
|
let caughtShellError;
|
|
296
300
|
let caughtBoundaries = null;
|
|
297
301
|
let shouldDelayInitialHtmlPull = false;
|
|
@@ -355,10 +359,11 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
355
359
|
shouldDelayInitialHtmlPull = options?.waitForAllReady !== true;
|
|
356
360
|
caughtShellError = error;
|
|
357
361
|
htmlStream = caughtHtmlStream;
|
|
358
|
-
} else if (options?.fallbackToErrorDocumentOnShellError !== true ||
|
|
362
|
+
} else if (options?.fallbackToErrorDocumentOnShellError !== true || isNavigationSignalError(error) || isBailoutToCSRError(error) || isDynamicServerError(error)) throw error;
|
|
359
363
|
else {
|
|
360
364
|
shellErrorRecovered = true;
|
|
361
|
-
|
|
365
|
+
if (options?.isServerComponentError?.(error) !== true) ssrOnlyShellError = error;
|
|
366
|
+
htmlStream = renderSsrErrorDocumentShell(bootstrapModuleUrl, options?.scriptNonce, assetCrossOrigin);
|
|
362
367
|
}
|
|
363
368
|
}
|
|
364
369
|
}
|
|
@@ -385,7 +390,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
385
390
|
const getInsertedHTML = () => {
|
|
386
391
|
const insertedHTML = renderInsertedHtml(renderServerInsertedHTML());
|
|
387
392
|
const errorMetaHTML = errorMetaRenderer.flush();
|
|
388
|
-
const initialDevServerErrorHTML = createInitialDevServerErrorScript(options?.initialDevServerError, options?.scriptNonce);
|
|
393
|
+
const initialDevServerErrorHTML = createInitialDevServerErrorScript(options?.initialDevServerError ?? ssrOnlyShellError, options?.scriptNonce);
|
|
389
394
|
if (didInjectHeadHTML) return insertedHTML + errorMetaHTML + getRenderedSearchHTML();
|
|
390
395
|
didInjectHeadHTML = true;
|
|
391
396
|
const hidesQuery = searchParamsGate !== null && searchParamsGate.gate.decision !== "real";
|
|
@@ -107,11 +107,20 @@ export type SharedAppCacheIdentityProps = Exclude<AppWorkerResponseStageProps, {
|
|
|
107
107
|
export declare function createSharedAppCacheIdentity(request: Request, props: SharedAppCacheIdentityProps): SharedAppCacheIdentity;
|
|
108
108
|
/**
|
|
109
109
|
* Build the query-free cache identity of a shared App page dispatch. The user
|
|
110
|
-
* query leaves both the URL and `resolvedUrl`;
|
|
111
|
-
*
|
|
112
|
-
* representation. HTML identities also drop the RSC selector headers.
|
|
110
|
+
* query leaves both the URL and `resolvedUrl`; an RSC request's validated
|
|
111
|
+
* `_rsc` and the render mode stay because they select the representation. HTML identities also drop the RSC selector headers.
|
|
113
112
|
*/
|
|
114
113
|
export declare function createSharedAppPageCacheIdentity(request: Request, props: AppMatchedWorkerResponseStageProps): SharedAppCacheIdentity;
|
|
114
|
+
/**
|
|
115
|
+
* Build the query-free cache identity of a shared Route Handler or metadata
|
|
116
|
+
* route dispatch. Next.js keys a route handler by its pathname: the query and
|
|
117
|
+
* the RSC selectors pick no representation of a handler's response, and a
|
|
118
|
+
* handler that reads the request's URL or headers is dynamic, so admission
|
|
119
|
+
* refuses its response. Keeping them in the identity would only let a client
|
|
120
|
+
* store one entry per query or selector value. The method, path and route
|
|
121
|
+
* props stay; the selector headers and the props derived from them leave.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createSharedAppRouteCacheIdentity(request: Request, props: AppMatchedWorkerResponseStageProps | AppMetadataWorkerResponseStageProps): SharedAppCacheIdentity;
|
|
115
124
|
/**
|
|
116
125
|
* Build the cache identity of a shared hybrid Pages page dispatch: the
|
|
117
126
|
* dispatched request and props under `origin`, so every host that names it
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VINEXT_RSC_VARY_HEADER } from "./app-rsc-vary.js";
|
|
2
|
+
import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
|
|
2
3
|
import { cacheabilityManifestPageState, cacheabilityRequestIdentity, cacheabilityRoutePathname } from "./cacheability-manifest.js";
|
|
3
4
|
import { keepOnlyValidatedRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
|
|
4
5
|
import { isTrustedPrerenderState } from "./prerender-route-params.js";
|
|
@@ -32,9 +33,8 @@ function createSharedAppCacheIdentity(request, props) {
|
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* Build the query-free cache identity of a shared App page dispatch. The user
|
|
35
|
-
* query leaves both the URL and `resolvedUrl`;
|
|
36
|
-
*
|
|
37
|
-
* representation. HTML identities also drop the RSC selector headers.
|
|
36
|
+
* query leaves both the URL and `resolvedUrl`; an RSC request's validated
|
|
37
|
+
* `_rsc` and the render mode stay because they select the representation. HTML identities also drop the RSC selector headers.
|
|
38
38
|
*/
|
|
39
39
|
function createSharedAppPageCacheIdentity(request, props) {
|
|
40
40
|
const queryFree = withoutAppPageDispatchQuery(request.url, props);
|
|
@@ -49,6 +49,39 @@ function createSharedAppPageCacheIdentity(request, props) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
+
* Build the query-free cache identity of a shared Route Handler or metadata
|
|
53
|
+
* route dispatch. Next.js keys a route handler by its pathname: the query and
|
|
54
|
+
* the RSC selectors pick no representation of a handler's response, and a
|
|
55
|
+
* handler that reads the request's URL or headers is dynamic, so admission
|
|
56
|
+
* refuses its response. Keeping them in the identity would only let a client
|
|
57
|
+
* store one entry per query or selector value. The method, path and route
|
|
58
|
+
* props stay; the selector headers and the props derived from them leave.
|
|
59
|
+
*/
|
|
60
|
+
function createSharedAppRouteCacheIdentity(request, props) {
|
|
61
|
+
const url = new URL(request.url);
|
|
62
|
+
url.search = "";
|
|
63
|
+
const headers = new Headers(request.headers);
|
|
64
|
+
for (const name of VINEXT_RSC_VARY_HEADER.split(",")) headers.delete(name.trim());
|
|
65
|
+
const searchIndex = props.resolvedUrl.indexOf("?");
|
|
66
|
+
return {
|
|
67
|
+
...sharedAppCacheIdentity(url.toString(), {
|
|
68
|
+
headers,
|
|
69
|
+
method: request.method
|
|
70
|
+
}, {
|
|
71
|
+
...props,
|
|
72
|
+
...props.kind === "app-metadata" ? {} : {
|
|
73
|
+
interceptionContext: null,
|
|
74
|
+
interceptionId: null
|
|
75
|
+
},
|
|
76
|
+
isRscRequest: false,
|
|
77
|
+
mountedSlotsHeader: null,
|
|
78
|
+
renderMode: APP_RSC_RENDER_MODE_NAVIGATION,
|
|
79
|
+
resolvedUrl: searchIndex === -1 ? props.resolvedUrl : props.resolvedUrl.slice(0, searchIndex)
|
|
80
|
+
}),
|
|
81
|
+
queryFree: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
52
85
|
* Build the cache identity of a shared hybrid Pages page dispatch: the
|
|
53
86
|
* dispatched request and props under `origin`, so every host that names it
|
|
54
87
|
* shares one entry.
|
|
@@ -141,4 +174,4 @@ function isResponseStageCacheability(value) {
|
|
|
141
174
|
return (cacheability.probeMode === null || cacheability.probeMode === "probe" || cacheability.probeMode === "identity") && (cacheability.policyHeaders === null || Array.isArray(cacheability.policyHeaders) && cacheability.policyHeaders.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string")) && (cacheability.representation === void 0 || cacheability.representation === "app-route" || cacheability.representation === "html" || cacheability.representation === "pages-data" || cacheability.representation === "rsc-full" || cacheability.representation === "rsc-loading-shell") && typeof cacheability.resolvedRoutePathname === "string" && cacheability.resolvedRoutePathname.startsWith("/");
|
|
142
175
|
}
|
|
143
176
|
//#endregion
|
|
144
|
-
export { APP_METADATA_RESPONSE_STAGE_NO_MATCH_HEADER, APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION, createSharedAppCacheIdentity, createSharedAppPageCacheIdentity, createSharedHybridPagesCacheIdentity, isAppWorkerResponseStageProps, isStaticCandidateAppPageDispatch, prepareSharedAppPageDispatch, withoutAppPageDispatchQuery };
|
|
177
|
+
export { APP_METADATA_RESPONSE_STAGE_NO_MATCH_HEADER, APP_WORKER_RESPONSE_STAGE_PROTOCOL_VERSION, createSharedAppCacheIdentity, createSharedAppPageCacheIdentity, createSharedAppRouteCacheIdentity, createSharedHybridPagesCacheIdentity, isAppWorkerResponseStageProps, isStaticCandidateAppPageDispatch, prepareSharedAppPageDispatch, withoutAppPageDispatchQuery };
|
|
@@ -3,6 +3,13 @@ type ParallelSlotBindingState = "active" | "default" | "unmatched";
|
|
|
3
3
|
export declare function createNestedBfcacheSlotSegmentId(parentSlotId: string, level: number): string;
|
|
4
4
|
export declare function isNestedBfcacheSlotSegmentId(id: string): boolean;
|
|
5
5
|
export declare function isNestedBfcacheSlotSegmentIdFor(id: string, parentSlotId: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Activity key for a route segment whose directory has no layout of its own.
|
|
8
|
+
* Next.js keeps a layout-router Activity list at every segment level, implicit
|
|
9
|
+
* layouts included, keyed by the child segment. The tag keeps these keys apart
|
|
10
|
+
* from identity strings, whose kinds never use it, and from BFCache ids.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createBfcacheChildSegmentStateKey(segmentPathKey: string): string;
|
|
6
13
|
export type BfcacheSegmentIdentity = string;
|
|
7
14
|
export type BfcacheSegmentDescriptor = {
|
|
8
15
|
kind: "page";
|
|
@@ -13,6 +13,15 @@ function isNestedBfcacheSlotSegmentId(id) {
|
|
|
13
13
|
function isNestedBfcacheSlotSegmentIdFor(id, parentSlotId) {
|
|
14
14
|
return isNestedBfcacheSlotSegmentId(id) && id.startsWith(createNestedBfcacheSlotParentPrefix(parentSlotId));
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Activity key for a route segment whose directory has no layout of its own.
|
|
18
|
+
* Next.js keeps a layout-router Activity list at every segment level, implicit
|
|
19
|
+
* layouts included, keyed by the child segment. The tag keeps these keys apart
|
|
20
|
+
* from identity strings, whose kinds never use it, and from BFCache ids.
|
|
21
|
+
*/
|
|
22
|
+
function createBfcacheChildSegmentStateKey(segmentPathKey) {
|
|
23
|
+
return JSON.stringify(["segment", segmentPathKey]);
|
|
24
|
+
}
|
|
16
25
|
function deriveBfcacheSegmentIdentity(descriptor) {
|
|
17
26
|
switch (descriptor.kind) {
|
|
18
27
|
case "page":
|
|
@@ -49,4 +58,4 @@ function deriveBfcacheSegmentIdentity(descriptor) {
|
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
//#endregion
|
|
52
|
-
export { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId, isNestedBfcacheSlotSegmentIdFor };
|
|
61
|
+
export { createBfcacheChildSegmentStateKey, createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity, isNestedBfcacheSlotSegmentId, isNestedBfcacheSlotSegmentIdFor };
|
|
@@ -118,7 +118,7 @@ function cacheabilityRequestIdentity(request, trustedRepresentation) {
|
|
|
118
118
|
representation: "pages-data",
|
|
119
119
|
requestKey
|
|
120
120
|
};
|
|
121
|
-
if (!(request.headers.get("RSC") === "1"
|
|
121
|
+
if (!(request.headers.get("RSC") === "1")) return (request.headers.get("Accept")?.toLowerCase() ?? "").includes("text/html") ? {
|
|
122
122
|
representation: "html",
|
|
123
123
|
requestKey
|
|
124
124
|
} : {
|
|
@@ -146,7 +146,6 @@ function normalizeCacheabilityRoutePathname(pathname) {
|
|
|
146
146
|
}
|
|
147
147
|
function cacheabilityRoutePathname(pathname, representation) {
|
|
148
148
|
const normalized = new URL(pathname, "http://vinext.local").pathname;
|
|
149
|
-
if (representation === "rsc-full" || representation === "rsc-loading-shell") return normalizeCacheabilityRoutePathname(normalized.endsWith(".rsc") ? normalized.slice(0, -4) : normalized);
|
|
150
149
|
if (representation !== "pages-data") return normalizeCacheabilityRoutePathname(normalized);
|
|
151
150
|
const marker = normalized.indexOf("/_next/data/");
|
|
152
151
|
const restWithBuildId = marker === -1 ? "" : normalized.slice(marker + 12);
|
|
@@ -41,7 +41,8 @@ export declare function isCrossSiteNoCorsRequest(secFetchSite: string | null | u
|
|
|
41
41
|
* Validate a dev server request from a Node.js IncomingMessage.
|
|
42
42
|
*
|
|
43
43
|
* Returns null if the request is allowed, or a reason string if it should be blocked.
|
|
44
|
-
* This is used by the
|
|
44
|
+
* This is used by the vinext:dev-origin-check connect middleware and the Pages
|
|
45
|
+
* Router handler.
|
|
45
46
|
*/
|
|
46
47
|
export declare function validateDevRequest(headers: {
|
|
47
48
|
origin?: string;
|
|
@@ -74,7 +74,8 @@ function isCrossSiteNoCorsRequest(secFetchSite, secFetchMode) {
|
|
|
74
74
|
* Validate a dev server request from a Node.js IncomingMessage.
|
|
75
75
|
*
|
|
76
76
|
* Returns null if the request is allowed, or a reason string if it should be blocked.
|
|
77
|
-
* This is used by the
|
|
77
|
+
* This is used by the vinext:dev-origin-check connect middleware and the Pages
|
|
78
|
+
* Router handler.
|
|
78
79
|
*/
|
|
79
80
|
function validateDevRequest(headers, allowedDevOrigins) {
|
|
80
81
|
if (isCrossSiteNoCorsRequest(headers["sec-fetch-site"], headers["sec-fetch-mode"])) return `cross-site no-cors request blocked`;
|
|
@@ -9,6 +9,7 @@ import { matchRoute } from "../routing/pages-router.js";
|
|
|
9
9
|
import { isDangerousScheme } from "../shims/url-safety.js";
|
|
10
10
|
import { extractLocaleFromUrl as extractLocaleFromUrl$1, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
11
11
|
import { importModule, reportRequestError } from "./instrumentation.js";
|
|
12
|
+
import { logRequestError } from "./request-error-log.js";
|
|
12
13
|
import { NEVER_CACHE_CONTROL } from "./cache-control.js";
|
|
13
14
|
import { buildMissIsrCacheControl } from "./isr-decision.js";
|
|
14
15
|
import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
|
|
@@ -918,7 +919,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
|
|
|
918
919
|
_renderEnd = now();
|
|
919
920
|
if (typeof routerShim.setSSRContext === "function") routerShim.setSSRContext(null);
|
|
920
921
|
} catch (e) {
|
|
921
|
-
|
|
922
|
+
logRequestError(e);
|
|
922
923
|
await reportRequestError(e, {
|
|
923
924
|
path: url,
|
|
924
925
|
method: req.method ?? "GET",
|
|
@@ -143,6 +143,11 @@ export declare function negotiateImageFormat(acceptHeader: string | null): strin
|
|
|
143
143
|
* Optimized images are immutable because the URL encodes the transform params.
|
|
144
144
|
*/
|
|
145
145
|
export declare const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
146
|
+
/**
|
|
147
|
+
* Next.js's Cache-Control for an image whose source can change at its URL
|
|
148
|
+
* (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
|
|
149
|
+
*/
|
|
150
|
+
export declare const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
|
|
146
151
|
/**
|
|
147
152
|
* Content-Security-Policy for image optimization responses.
|
|
148
153
|
* Blocks script execution and framing to prevent XSS via SVG or other
|
|
@@ -229,6 +229,11 @@ function negotiateImageFormat(acceptHeader) {
|
|
|
229
229
|
*/
|
|
230
230
|
const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
231
231
|
/**
|
|
232
|
+
* Next.js's Cache-Control for an image whose source can change at its URL
|
|
233
|
+
* (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
|
|
234
|
+
*/
|
|
235
|
+
const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
|
|
236
|
+
/**
|
|
232
237
|
* Content-Security-Policy for image optimization responses.
|
|
233
238
|
* Blocks script execution and framing to prevent XSS via SVG or other
|
|
234
239
|
* active content that might be served through the image endpoint.
|
|
@@ -397,4 +402,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
|
|
|
397
402
|
}, allowedWidths, imageConfig);
|
|
398
403
|
}
|
|
399
404
|
//#endregion
|
|
400
|
-
export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
|
|
405
|
+
export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, IMAGE_CACHE_CONTROL, IMAGE_CONTENT_SECURITY_POLICY, IMAGE_OPTIMIZATION_PATH, IMAGE_RESPONSE_CACHE_STATE, INVALID_IMAGE_SOURCE_MESSAGE, MUTABLE_IMAGE_SOURCE_CACHE_CONTROL, VINEXT_IMAGE_OPTIMIZATION_PATH, getImageOptimizer, handleConfiguredImageOptimization, handleImageOptimization, invalidImageSourceResponse, isImageOptimizationPath, isImageSourceBodyStatus, isSafeImageContentType, markImageOptimizationSignal, negotiateImageFormat, parseImageParams, readImageOptimizationSignal, resolveDevImageRedirect, restoreImageOptimizationSignalFromTransport, serializeImageOptimizationSignalForTransport, setImageOptimizer };
|
package/dist/server/isr-cache.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalizePathnameForRouteMatch } from "../routing/utils.js";
|
|
|
3
3
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
4
4
|
import { isInterceptionMatchedUrlPath } from "./normalize-path.js";
|
|
5
5
|
import { reportRequestError } from "./instrumentation.js";
|
|
6
|
+
import { logRequestError } from "./request-error-log.js";
|
|
6
7
|
import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
|
|
7
8
|
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
8
9
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
@@ -144,7 +145,9 @@ function triggerBackgroundRegeneration(key, renderFn, errorContext) {
|
|
|
144
145
|
if (!getCdnCacheAdapter().ownsBackgroundRevalidation) return;
|
|
145
146
|
if (pendingRegenerations.has(key)) return;
|
|
146
147
|
const promise = renderFn().catch(async (err) => {
|
|
147
|
-
|
|
148
|
+
const message = `[vinext] ISR background regeneration failed for ${key}:`;
|
|
149
|
+
if (errorContext) logRequestError(message, err);
|
|
150
|
+
else console.error(message, err);
|
|
148
151
|
if (errorContext && (errorContext.shouldReport?.(err) ?? true)) await reportRequestError(err, {
|
|
149
152
|
path: key,
|
|
150
153
|
method: "GET",
|
|
@@ -14,10 +14,10 @@ import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
|
|
|
14
14
|
import { getActiveDraftModeState, getHeadersContext, hasDraftModeCookieHeader, replaceHeadersContext } from "../shims/headers.js";
|
|
15
15
|
import { _consumeRequestScopedCacheLife, _isOnDemandRevalidate } from "../shims/cache-request-state.js";
|
|
16
16
|
import { ensureFetchPatch, getCollectedFetchTags, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
|
|
17
|
+
import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
|
|
17
18
|
import { isValidMetadataImageId, manifestToJson, matchMetadataRoutePattern, robotsToText, sitemapToXml } from "./metadata-routes.js";
|
|
18
19
|
import { buildAppRouteCacheValue, buildRouteHandlerCachedResponse } from "./app-route-handler-response.js";
|
|
19
20
|
import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
|
|
20
|
-
import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
|
|
21
21
|
import { buildPageCacheTags } from "./implicit-tags.js";
|
|
22
22
|
import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
|
|
23
23
|
import { completeAppRouteHandlerResponse } from "./app-route-handler-execution.js";
|
|
@@ -50,17 +50,30 @@ function matchesMiddlewarePathname(pathname, matcher, i18nConfig, localeContext)
|
|
|
50
50
|
}
|
|
51
51
|
function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
|
|
52
52
|
if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, pattern, i18nConfig, true);
|
|
53
|
-
if (!i18nConfig) return
|
|
53
|
+
if (!i18nConfig) return matchRequestPattern(pathname, pattern);
|
|
54
54
|
if (localeContext) {
|
|
55
55
|
if (localeContext.kind === "internal") return false;
|
|
56
|
-
return
|
|
56
|
+
return matchRequestPattern(localeContext.kind === "literal" ? stripFirstPathSegment(pathname) : pathname, pattern);
|
|
57
57
|
}
|
|
58
|
-
return
|
|
58
|
+
return matchRequestPattern(stripLocalePrefix(pathname, i18nConfig) ?? pathname, pattern);
|
|
59
59
|
}
|
|
60
60
|
function matchObjectMatcher(pathname, matcher, i18nConfig, localeContext) {
|
|
61
61
|
if (localeContext?.kind === "next-data") return matchNextDataPattern(pathname, matcher.source, i18nConfig, matcher.locale !== false);
|
|
62
62
|
if (matcher.locale !== false) return matchMatcherPattern(pathname, matcher.source, i18nConfig, localeContext);
|
|
63
|
-
return
|
|
63
|
+
return matchRequestPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname === "/" ? "" : pathname}` : pathname, matcher.source);
|
|
64
|
+
}
|
|
65
|
+
const APP_ROUTE_TRANSPORT_SUFFIX = "\\.rsc|\\.segments/.+\\.segment\\.rsc";
|
|
66
|
+
const APP_ROUTE_TRANSPORT_SUFFIX_RE = /(?:\.rsc|\.segments\/.+\.segment\.rsc)$/;
|
|
67
|
+
/**
|
|
68
|
+
* Next.js compiles every matcher with optional App Router transport suffixes,
|
|
69
|
+
* so a matcher for `/about` runs middleware for `/about.rsc` too. Middleware
|
|
70
|
+
* still sees the path as requested.
|
|
71
|
+
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
|
|
72
|
+
*/
|
|
73
|
+
function matchRequestPattern(pathname, source) {
|
|
74
|
+
if (matchPattern(pathname, source)) return true;
|
|
75
|
+
if (!APP_ROUTE_TRANSPORT_SUFFIX_RE.test(pathname)) return false;
|
|
76
|
+
return matchPattern(pathname, source === "/" ? `/(/?index(?:${APP_ROUTE_TRANSPORT_SUFFIX}))?` : `${source}{(${APP_ROUTE_TRANSPORT_SUFFIX})}?`);
|
|
64
77
|
}
|
|
65
78
|
function matchNextDataPattern(pathname, source, i18nConfig, localized) {
|
|
66
79
|
if (compiledPattern(source) === INVALID_MATCHER_PATTERN) return true;
|
|
@@ -69,8 +82,8 @@ function matchNextDataPattern(pathname, source, i18nConfig, localized) {
|
|
|
69
82
|
/**
|
|
70
83
|
* The source Next.js compiles for a matcher: an optional `_next/data/<buildId>`
|
|
71
84
|
* prefix, an internal locale segment under i18n, and an optional data suffix.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
85
|
+
* Its App Router transport suffixes end in `.rsc`, so they can never match a
|
|
86
|
+
* `.json` data URL.
|
|
74
87
|
* https://github.com/vercel/next.js/blob/v16.2.7/packages/next/src/build/analysis/get-page-static-info.ts
|
|
75
88
|
*/
|
|
76
89
|
function nextDataMatcherSource(source, i18nConfig, localized) {
|
|
@@ -19,6 +19,7 @@ import { nextDataMiddlewareMatchPathname } from "./pages-data-route.js";
|
|
|
19
19
|
import { reportRequestError } from "./instrumentation.js";
|
|
20
20
|
import { isValidNavigationSignalError } from "../utils/navigation-signal.js";
|
|
21
21
|
import { frameworkTracer } from "./tracer.js";
|
|
22
|
+
import { logRequestError } from "./request-error-log.js";
|
|
22
23
|
//#region src/server/middleware-runtime.ts
|
|
23
24
|
function reportProxyError(options, error) {
|
|
24
25
|
const url = new URL(options.request.url);
|
|
@@ -252,8 +253,11 @@ async function executeMiddlewareModule(options) {
|
|
|
252
253
|
} catch (e) {
|
|
253
254
|
const isDevelopmentNavigationSignal = process.env.NODE_ENV !== "production" && isValidNavigationSignalError(e);
|
|
254
255
|
if (isDevelopmentNavigationSignal && e instanceof Error) e.message = `Next.js navigation API is not allowed to be used in ${options.isProxy ? "Proxy" : "Middleware"}.`;
|
|
255
|
-
console.error("[vinext] Middleware error:", e);
|
|
256
|
-
|
|
256
|
+
if (isDevelopmentNavigationSignal) console.error("[vinext] Middleware error:", e);
|
|
257
|
+
else {
|
|
258
|
+
logRequestError("[vinext] Middleware error:", e);
|
|
259
|
+
await reportProxyError(options, e);
|
|
260
|
+
}
|
|
257
261
|
const waitUntilPromises = drainFetchEvent(fetchEvent);
|
|
258
262
|
releaseMiddlewareRequestBody(nextRequest, waitUntilPromises);
|
|
259
263
|
const message = options.includeErrorDetails ? "Middleware Error: " + (e instanceof Error ? e.message : String(e)) : "Internal Server Error";
|
|
@@ -77,14 +77,18 @@ export type VinextResponseStageDispatchOptions = {
|
|
|
77
77
|
*
|
|
78
78
|
* Under `responseStageCacheIdentity: "query-free"`, an App page identity
|
|
79
79
|
* also drops the user query from the URL and `resolvedUrl`, keeping
|
|
80
|
-
* framework representation selectors: the
|
|
81
|
-
*
|
|
80
|
+
* framework representation selectors: the render mode and, for RSC
|
|
81
|
+
* requests only, the one validated `_rsc` value. That is safe
|
|
82
82
|
* only because completed-response admission refuses a cacheable App page
|
|
83
83
|
* response unless its render proved it never read `searchParams`. The
|
|
84
84
|
* dispatched request still carries the real query for the render itself. A
|
|
85
85
|
* page identity keeps the query when a `next.config` public cache policy
|
|
86
86
|
* applies (cached per full URL, as Next.js CDN caching is), and for
|
|
87
|
-
* interception and mounted-slot dispatches.
|
|
87
|
+
* interception and mounted-slot dispatches. A Route Handler or metadata
|
|
88
|
+
* route identity keeps only the method, path (without `.rsc`) and route
|
|
89
|
+
* props, as Next.js keys a route handler by its pathname: the RSC selectors
|
|
90
|
+
* pick no representation of its response, and admission refuses a handler
|
|
91
|
+
* that read the request's URL or headers.
|
|
88
92
|
*/
|
|
89
93
|
cacheIdentity?: {
|
|
90
94
|
props: unknown;
|
|
@@ -7,7 +7,7 @@ export type PagesRequestQuery = Record<string, string | string[]>;
|
|
|
7
7
|
export type PagesReqResRequest = Readable & {
|
|
8
8
|
method: string;
|
|
9
9
|
url: string;
|
|
10
|
-
headers: Record<string, string>;
|
|
10
|
+
headers: Record<string, string | string[] | undefined>;
|
|
11
11
|
query: PagesRequestQuery;
|
|
12
12
|
body: unknown;
|
|
13
13
|
cookies: Record<string, string>;
|
|
@@ -367,6 +367,8 @@ function isVinextStreamedApiResponse(response) {
|
|
|
367
367
|
function createPagesReqRes(options) {
|
|
368
368
|
const headersObj = {};
|
|
369
369
|
for (const [key, value] of options.request.headers) headersObj[key.toLowerCase()] = value;
|
|
370
|
+
const setCookies = options.request.headers.getSetCookie();
|
|
371
|
+
if (setCookies.length > 1) headersObj["set-cookie"] = setCookies;
|
|
370
372
|
const req = Object.assign(createRequestReadable(options.request), {
|
|
371
373
|
method: options.request.method,
|
|
372
374
|
url: options.url,
|
|
@@ -13,7 +13,7 @@ import { normalizeStaticPathname } from "../routing/route-pattern.js";
|
|
|
13
13
|
import { buildCacheStateHeaders } from "./cache-headers.js";
|
|
14
14
|
import { pagesPathCacheTag } from "./pages-cache-tag.js";
|
|
15
15
|
import { isPageNotModified } from "./http-conditional.js";
|
|
16
|
-
import { generatePagesETag, isPagesStreamingBot, rendersPagesNotModified } from "./pages-page-response.js";
|
|
16
|
+
import { applyPagesPrerenderRevalidateHeader, generatePagesETag, isPagesStreamingBot, rendersPagesNotModified } from "./pages-page-response.js";
|
|
17
17
|
import { createPagesGetInitialPropsRouter, hasPagesGetInitialProps, isResponseSent, loadPagesGetInitialProps } from "./pages-get-initial-props.js";
|
|
18
18
|
import { isSerializableProps } from "./pages-serializable-props.js";
|
|
19
19
|
import { tracePagesData, tracePagesDocument } from "./pages-execution-tracing.js";
|
|
@@ -130,6 +130,7 @@ function applyPagesTerminalMissHeaders(response, revalidateSeconds, isrCachePath
|
|
|
130
130
|
tags: [pagesPathCacheTag(isrCachePathname)]
|
|
131
131
|
});
|
|
132
132
|
for (const [name, value] of Object.entries(buildCacheStateHeaders("MISS"))) response.headers.set(name, value);
|
|
133
|
+
applyPagesPrerenderRevalidateHeader(response.headers, revalidateSeconds);
|
|
133
134
|
return response;
|
|
134
135
|
}
|
|
135
136
|
function applyCachedPagesRepresentationHeaders(response, cacheState, entry, options) {
|