vinext 1.4.0 → 1.4.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 +21 -21
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/build/prerender.js +3 -2
- package/dist/check.js +4 -7
- 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 +4 -2
- package/dist/config/destination-params.d.ts +2 -1
- package/dist/config/destination-params.js +55 -2
- package/dist/config/next-config.d.ts +22 -1
- package/dist/config/next-config.js +49 -4
- package/dist/entries/app-rsc-entry.js +41 -19
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +5 -1
- package/dist/index.js +44 -32
- package/dist/init-cloudflare.js +18 -2
- package/dist/plugins/css-target.d.ts +18 -0
- package/dist/plugins/css-target.js +93 -0
- package/dist/plugins/strip-server-exports.js +2 -1
- package/dist/routing/app-route-graph.d.ts +10 -0
- package/dist/routing/app-route-graph.js +15 -1
- package/dist/server/api-handler.d.ts +2 -0
- package/dist/server/api-handler.js +5 -6
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +28 -7
- 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-elements.d.ts +3 -0
- package/dist/server/app-elements.js +14 -1
- package/dist/server/app-fallback-renderer.js +2 -2
- package/dist/server/app-optimistic-routing.d.ts +12 -1
- package/dist/server/app-optimistic-routing.js +40 -19
- package/dist/server/app-page-boundary-render.js +7 -12
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -2
- package/dist/server/app-page-dispatch.js +73 -31
- package/dist/server/app-page-element-builder.d.ts +9 -20
- package/dist/server/app-page-element-builder.js +40 -52
- package/dist/server/app-page-execution.js +9 -6
- package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
- package/dist/server/app-page-intercept-segment-config.js +90 -0
- package/dist/server/app-page-navigation-params.d.ts +23 -0
- package/dist/server/app-page-navigation-params.js +49 -0
- package/dist/server/app-page-render.d.ts +5 -3
- package/dist/server/app-page-render.js +31 -17
- package/dist/server/app-page-request.d.ts +24 -2
- package/dist/server/app-page-request.js +10 -10
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +10 -0
- package/dist/server/app-page-route-wiring.js +34 -13
- package/dist/server/app-page-segment-plan.js +9 -1
- package/dist/server/app-page-segment-state.d.ts +9 -0
- package/dist/server/app-page-segment-state.js +54 -1
- 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-response-stage-entry.js +1 -0
- 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-route-module-loader.d.ts +2 -0
- package/dist/server/app-route-module-loader.js +1 -0
- 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.d.ts +10 -1
- package/dist/server/app-rsc-handler.js +126 -103
- 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-finalizer.d.ts +3 -0
- package/dist/server/app-rsc-response-finalizer.js +11 -1
- package/dist/server/app-rsc-response-stage.js +4 -12
- package/dist/server/app-rsc-route-matching.d.ts +14 -0
- package/dist/server/app-rsc-route-matching.js +42 -18
- package/dist/server/app-server-action-execution.js +24 -14
- 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/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -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-local-patterns.d.ts +21 -0
- package/dist/server/image-local-patterns.js +59 -0
- package/dist/server/image-optimization.d.ts +28 -22
- package/dist/server/image-optimization.js +17 -2
- package/dist/server/isr-cache.js +4 -1
- package/dist/server/metadata-route-response.js +25 -7
- 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/nitro-public-files-plugin.js +20 -8
- package/dist/server/pages-data-route.d.ts +5 -1
- package/dist/server/pages-data-route.js +14 -6
- 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-pipeline.js +6 -5
- package/dist/server/pages-request-stage-entry.js +25 -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 +59 -17
- 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 +14 -4
- package/dist/server/request-pipeline.js +33 -15
- package/dist/server/response-stage-cacheability.d.ts +7 -0
- package/dist/server/response-stage-cacheability.js +6 -5
- 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/server/worker-image-optimization.d.ts +2 -2
- package/dist/server/worker-image-optimization.js +18 -3
- package/dist/server/worker-utils.d.ts +8 -0
- package/dist/server/worker-utils.js +19 -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/image-props.d.ts +6 -0
- package/dist/shims/image-props.js +39 -3
- package/dist/shims/image.js +3 -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 +9 -2
- package/dist/shims/slot.js +76 -11
- package/dist/shims/unified-request-context.d.ts +6 -0
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- 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
|
@@ -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 };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CdnCacheableHeaderInput, isNonCacheableCacheControl } from "../shims/cdn-cache.js";
|
|
2
2
|
//#region src/server/cache-control.d.ts
|
|
3
3
|
export declare const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
4
|
+
/** The policy Next.js sends on every Server Action response. */
|
|
5
|
+
export declare const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
|
|
4
6
|
export declare const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
|
|
5
7
|
export declare const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
|
|
6
8
|
export declare const NO_STORE_CACHE_CONTROL = "no-store, must-revalidate";
|
|
@@ -2,6 +2,8 @@ import { getCdnCacheAdapter, isNonCacheableCacheControl } from "../shims/cdn-cac
|
|
|
2
2
|
import { recordRouteCacheabilityCdnTags } from "../shims/cacheability-classification.js";
|
|
3
3
|
//#region src/server/cache-control.ts
|
|
4
4
|
const NEVER_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
5
|
+
/** The policy Next.js sends on every Server Action response. */
|
|
6
|
+
const SERVER_ACTION_CACHE_CONTROL = "no-cache, no-store, max-age=0, must-revalidate";
|
|
5
7
|
const BROWSER_REVALIDATE_CACHE_CONTROL = "public, max-age=0, must-revalidate";
|
|
6
8
|
const STATIC_CACHE_CONTROL = "s-maxage=31536000, stale-while-revalidate";
|
|
7
9
|
const STALE_REVALIDATE_CACHE_CONTROL = "s-maxage=0, stale-while-revalidate";
|
|
@@ -167,4 +169,4 @@ function buildCachedRevalidateCacheControl(cacheState, revalidateSeconds, expire
|
|
|
167
169
|
return buildRevalidateCacheControl(revalidateSeconds, expireSeconds);
|
|
168
170
|
}
|
|
169
171
|
//#endregion
|
|
170
|
-
export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
|
|
172
|
+
export { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, SERVER_ACTION_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseBuildIdentityHeaders, applyCdnResponseHeaders, applyCdnResponseIdentityHeaders, buildCachedRevalidateCacheControl, buildRevalidateCacheControl, captureCdnResponsePolicyHeaders, captureCdnResponsePolicyOverrides, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, isNonCacheableCacheControl, isNonCacheableCdnResponsePolicy, readCdnResponseCacheControl, readCdnResponsePolicyHeaderName, reconcileCdnResponseHeadersAfterOuterPolicy, shouldUseNextDeployCacheControl, validateCdnRequest };
|
|
@@ -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",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/server/image-local-patterns.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `images.localPatterns` matching for the image optimization endpoint, kept
|
|
4
|
+
* apart from the remote-pattern shim so the request path doesn't load its IP
|
|
5
|
+
* parsing.
|
|
6
|
+
*/
|
|
7
|
+
export type LocalPattern = {
|
|
8
|
+
pathname?: string;
|
|
9
|
+
search?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Check whether a local URL matches a single local pattern.
|
|
13
|
+
* Follows the same semantics as Next.js's matchLocalPattern().
|
|
14
|
+
*/
|
|
15
|
+
export declare function matchLocalPattern(pattern: LocalPattern, url: URL): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Check whether a local URL matches any configured local pattern. Like
|
|
18
|
+
* Next.js's hasLocalMatch(), an unset list allows every local URL.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hasLocalMatch(localPatterns: LocalPattern[] | undefined, url: URL): boolean;
|
|
21
|
+
//#endregion
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/server/image-local-patterns.ts
|
|
2
|
+
const localPathnameRegexCache = /* @__PURE__ */ new Map();
|
|
3
|
+
/**
|
|
4
|
+
* Convert a local pathname glob to a RegExp, reading `*` and `**` like Next.js's
|
|
5
|
+
* picomatch (`makeRe(pathname, { dot: true })`):
|
|
6
|
+
* - a `**` segment matches zero or more whole segments, but needs its own
|
|
7
|
+
* separator right after a leading `/`, and at the end after a segment
|
|
8
|
+
* that ends in `*`;
|
|
9
|
+
* - any other `*` run matches characters within one segment;
|
|
10
|
+
* - a last segment ending in `*` also matches one trailing slash, and a
|
|
11
|
+
* whole last `*` matches one character or more (that slash included).
|
|
12
|
+
* Other picomatch syntax (braces, `?`, classes) is matched literally.
|
|
13
|
+
*/
|
|
14
|
+
function localPathnameGlobToRegex(pattern) {
|
|
15
|
+
const cached = localPathnameRegexCache.get(pattern);
|
|
16
|
+
if (cached !== void 0) return cached;
|
|
17
|
+
const segments = pattern.split("/").filter((segment, i, all) => !(segment === "**" && all[i - 1] === "**"));
|
|
18
|
+
let regexStr = "^";
|
|
19
|
+
for (let i = 0; i < segments.length; i++) {
|
|
20
|
+
const segment = segments[i];
|
|
21
|
+
const previous = segments[i - 1];
|
|
22
|
+
const isLast = i === segments.length - 1;
|
|
23
|
+
if (segment === "**") {
|
|
24
|
+
if (i === 0) regexStr += isLast ? ".*" : "(?:.*/)?";
|
|
25
|
+
else if (previous === "" || isLast && previous.endsWith("*")) regexStr += "/.*";
|
|
26
|
+
else regexStr += "(?:/.*)?";
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (i > 0 && !(i === 1 && previous === "**")) regexStr += "/";
|
|
30
|
+
if (segment === "*" && isLast) {
|
|
31
|
+
regexStr += "(?:[^/]+/?|/)";
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
regexStr += segment.split(/\*+/).map((literal) => literal.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
|
|
35
|
+
if (isLast && segment.endsWith("*")) regexStr += "/?";
|
|
36
|
+
}
|
|
37
|
+
regexStr += "$";
|
|
38
|
+
const re = new RegExp(regexStr);
|
|
39
|
+
localPathnameRegexCache.set(pattern, re);
|
|
40
|
+
return re;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check whether a local URL matches a single local pattern.
|
|
44
|
+
* Follows the same semantics as Next.js's matchLocalPattern().
|
|
45
|
+
*/
|
|
46
|
+
function matchLocalPattern(pattern, url) {
|
|
47
|
+
if (pattern.search !== void 0 && pattern.search !== url.search) return false;
|
|
48
|
+
return localPathnameGlobToRegex(pattern.pathname ?? "**").test(url.pathname);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check whether a local URL matches any configured local pattern. Like
|
|
52
|
+
* Next.js's hasLocalMatch(), an unset list allows every local URL.
|
|
53
|
+
*/
|
|
54
|
+
function hasLocalMatch(localPatterns, url) {
|
|
55
|
+
if (!localPatterns) return true;
|
|
56
|
+
return localPatterns.some((p) => matchLocalPattern(p, url));
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { hasLocalMatch, matchLocalPattern };
|
|
@@ -1,22 +1,5 @@
|
|
|
1
|
+
import { LocalPattern } from "./image-local-patterns.js";
|
|
1
2
|
//#region src/server/image-optimization.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Image optimization request handler.
|
|
4
|
-
*
|
|
5
|
-
* Handles `/_next/image?url=...&w=...&q=...` requests. In production
|
|
6
|
-
* on Cloudflare Workers, uses the Images binding (`env.IMAGES`) to
|
|
7
|
-
* resize and transcode on the fly. On other runtimes (Node.js dev/prod
|
|
8
|
-
* server), serves the original file as a passthrough with appropriate
|
|
9
|
-
* Cache-Control headers.
|
|
10
|
-
*
|
|
11
|
-
* Format negotiation: inspects the `Accept` header and serves AVIF, WebP,
|
|
12
|
-
* or JPEG depending on client support.
|
|
13
|
-
*
|
|
14
|
-
* Security: All image responses include Content-Security-Policy and
|
|
15
|
-
* X-Content-Type-Options headers to prevent XSS via SVG or Content-Type
|
|
16
|
-
* spoofing. SVG content is blocked by default (following Next.js behavior).
|
|
17
|
-
* When `dangerouslyAllowSVG` is enabled in next.config.js, SVGs are served
|
|
18
|
-
* as-is (no transformation) with security headers applied.
|
|
19
|
-
*/
|
|
20
3
|
/** The pathname that triggers image optimization (matches Next.js). */
|
|
21
4
|
export declare const IMAGE_OPTIMIZATION_PATH = "/_next/image";
|
|
22
5
|
/**
|
|
@@ -49,11 +32,22 @@ export type ImageConfig = {
|
|
|
49
32
|
/** Allowed fixed-image widths. Defaults to Next.js image sizes. */
|
|
50
33
|
imageSizes?: number[];
|
|
51
34
|
/**
|
|
52
|
-
* Allowed output qualities. When
|
|
53
|
-
*
|
|
54
|
-
*
|
|
35
|
+
* Allowed output qualities. When set, only the listed qualities are
|
|
36
|
+
* accepted, as in Next.js. When unset, any quality from 1-100 is accepted.
|
|
37
|
+
*
|
|
38
|
+
* Intentional divergence from Next.js 16, which defaults this to `[75]`:
|
|
39
|
+
* vinext keeps the Next.js 14/15 behaviour for an unset list so apps that
|
|
40
|
+
* adopt vinext from those versions and pass `quality={N}` keep working
|
|
41
|
+
* instead of getting 400s. Apps opt into Next.js 16's strict list by
|
|
42
|
+
* configuring `images.qualities`.
|
|
55
43
|
*/
|
|
56
44
|
qualities?: number[];
|
|
45
|
+
/**
|
|
46
|
+
* Local sources the endpoint may serve, resolved like Next.js: an app list
|
|
47
|
+
* gains the static-import pattern, and an unset list becomes
|
|
48
|
+
* DEFAULT_LOCAL_PATTERNS when checked.
|
|
49
|
+
*/
|
|
50
|
+
localPatterns?: LocalPattern[];
|
|
57
51
|
/** Allow SVG through the image optimization endpoint. Default: false. */
|
|
58
52
|
dangerouslyAllowSVG?: boolean;
|
|
59
53
|
/**
|
|
@@ -80,10 +74,12 @@ export declare const DEFAULT_DEVICE_SIZES: number[];
|
|
|
80
74
|
export declare const DEFAULT_IMAGE_SIZES: number[];
|
|
81
75
|
export type ParseImageParamsOptions = {
|
|
82
76
|
isDev?: boolean;
|
|
77
|
+
/** `images.localPatterns`; DEFAULT_LOCAL_PATTERNS when unset. */
|
|
78
|
+
localPatterns?: LocalPattern[];
|
|
83
79
|
};
|
|
84
80
|
export declare function resolveDevImageRedirect(requestUrl: URL, allowedWidths?: number[], allowedQualities?: number[], options?: ParseImageParamsOptions): string | null;
|
|
85
81
|
export type ImageOptimizationSignal = {
|
|
86
|
-
/**
|
|
82
|
+
/** next.config and middleware response headers to send with the image. */
|
|
87
83
|
middlewareHeaders: Headers;
|
|
88
84
|
/** The request headers after middleware's overrides, when it set any. */
|
|
89
85
|
requestHeaders: Headers | null;
|
|
@@ -120,6 +116,11 @@ export declare function isImageSourceBodyStatus(status: number): boolean;
|
|
|
120
116
|
*/
|
|
121
117
|
export declare const INVALID_IMAGE_SOURCE_MESSAGE = "The requested resource isn't a valid image.";
|
|
122
118
|
export declare function invalidImageSourceResponse(): Response;
|
|
119
|
+
/**
|
|
120
|
+
* Next.js applies this when `images.localPatterns` is unset: every local path,
|
|
121
|
+
* but no query string.
|
|
122
|
+
*/
|
|
123
|
+
export declare const DEFAULT_LOCAL_PATTERNS: LocalPattern[];
|
|
123
124
|
/**
|
|
124
125
|
* Parse and validate image optimization query parameters.
|
|
125
126
|
* Returns null if the request is malformed.
|
|
@@ -143,6 +144,11 @@ export declare function negotiateImageFormat(acceptHeader: string | null): strin
|
|
|
143
144
|
* Optimized images are immutable because the URL encodes the transform params.
|
|
144
145
|
*/
|
|
145
146
|
export declare const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
147
|
+
/**
|
|
148
|
+
* Next.js's Cache-Control for an image whose source can change at its URL
|
|
149
|
+
* (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
|
|
150
|
+
*/
|
|
151
|
+
export declare const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
|
|
146
152
|
/**
|
|
147
153
|
* Content-Security-Policy for image optimization responses.
|
|
148
154
|
* Blocks script execution and framing to prevent XSS via SVG or other
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { badRequestResponse } from "./http-error-responses.js";
|
|
2
2
|
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
3
|
+
import { hasLocalMatch } from "./image-local-patterns.js";
|
|
3
4
|
//#region src/server/image-optimization.ts
|
|
4
5
|
/**
|
|
5
6
|
* Image optimization request handler.
|
|
@@ -166,6 +167,14 @@ function invalidImageSourceResponse() {
|
|
|
166
167
|
}
|
|
167
168
|
const RECURSIVE_IMAGE_URL_RE = /\/_(?:next|vinext)\/image(?:$|\/)/;
|
|
168
169
|
/**
|
|
170
|
+
* Next.js applies this when `images.localPatterns` is unset: every local path,
|
|
171
|
+
* but no query string.
|
|
172
|
+
*/
|
|
173
|
+
const DEFAULT_LOCAL_PATTERNS = [{
|
|
174
|
+
pathname: "**",
|
|
175
|
+
search: ""
|
|
176
|
+
}];
|
|
177
|
+
/**
|
|
169
178
|
* Parse and validate image optimization query parameters.
|
|
170
179
|
* Returns null if the request is malformed.
|
|
171
180
|
*
|
|
@@ -204,6 +213,7 @@ function parseImageParams(url, allowedWidths = [...DEFAULT_DEVICE_SIZES, ...DEFA
|
|
|
204
213
|
if (resolved.origin !== base) return null;
|
|
205
214
|
if (resolved.pathname.startsWith("//")) return null;
|
|
206
215
|
if (RECURSIVE_IMAGE_URL_RE.test(decodeURIComponent(resolved.pathname))) return null;
|
|
216
|
+
if (!options.isDev && !hasLocalMatch(options.localPatterns ?? DEFAULT_LOCAL_PATTERNS, resolved)) return null;
|
|
207
217
|
} catch {
|
|
208
218
|
return null;
|
|
209
219
|
}
|
|
@@ -229,6 +239,11 @@ function negotiateImageFormat(acceptHeader) {
|
|
|
229
239
|
*/
|
|
230
240
|
const IMAGE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
231
241
|
/**
|
|
242
|
+
* Next.js's Cache-Control for an image whose source can change at its URL
|
|
243
|
+
* (anything but a hashed build asset), at the default `images.minimumCacheTTL`.
|
|
244
|
+
*/
|
|
245
|
+
const MUTABLE_IMAGE_SOURCE_CACHE_CONTROL = "public, max-age=14400, must-revalidate";
|
|
246
|
+
/**
|
|
232
247
|
* Content-Security-Policy for image optimization responses.
|
|
233
248
|
* Blocks script execution and framing to prevent XSS via SVG or other
|
|
234
249
|
* active content that might be served through the image endpoint.
|
|
@@ -312,7 +327,7 @@ function createPassthroughImageResponse(source, config) {
|
|
|
312
327
|
* cache headers.
|
|
313
328
|
*/
|
|
314
329
|
async function handleImageOptimization(request, handlers, allowedWidths, imageConfig) {
|
|
315
|
-
const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities);
|
|
330
|
+
const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
|
|
316
331
|
if (!params) return badRequestResponse();
|
|
317
332
|
const { imageUrl, width, quality } = params;
|
|
318
333
|
const source = await handlers.fetchAsset(imageUrl, request);
|
|
@@ -397,4 +412,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
|
|
|
397
412
|
}, allowedWidths, imageConfig);
|
|
398
413
|
}
|
|
399
414
|
//#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 };
|
|
415
|
+
export { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, DEFAULT_LOCAL_PATTERNS, 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";
|
|
@@ -504,20 +504,34 @@ async function writeMetadataRouteMiss(options, route, rendered, cachedEntry) {
|
|
|
504
504
|
async function handleMetadataRouteRequest(options) {
|
|
505
505
|
let urlParts;
|
|
506
506
|
const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
|
|
507
|
+
const routePathname = options.routePathname ?? options.cleanPathname;
|
|
508
|
+
let routeUrlParts;
|
|
509
|
+
const getRouteUrlParts = () => routeUrlParts ??= routePathname.split("/").filter(Boolean);
|
|
510
|
+
let cacheOptions = options;
|
|
511
|
+
const skipDecodedOwner = () => {
|
|
512
|
+
cacheOptions = {
|
|
513
|
+
...options,
|
|
514
|
+
isrGet: void 0,
|
|
515
|
+
isrRouteKey: void 0,
|
|
516
|
+
isrSet: void 0
|
|
517
|
+
};
|
|
518
|
+
};
|
|
507
519
|
for (const route of options.metadataRoutes) {
|
|
508
520
|
const functions = getMetadataRouteFunctions(route);
|
|
509
521
|
if (route.type === "sitemap" && route.isDynamic) {
|
|
510
522
|
if (functions.generateSitemaps) {
|
|
511
|
-
if (isGeneratedSitemapPath(route, options.cleanPathname))
|
|
523
|
+
if (!isGeneratedSitemapPath(route, options.cleanPathname)) continue;
|
|
524
|
+
if (!isGeneratedSitemapPath(route, routePathname)) skipDecodedOwner();
|
|
525
|
+
else {
|
|
512
526
|
beginMetadataRouteCacheability(route);
|
|
513
527
|
const render = async () => {
|
|
514
528
|
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
515
529
|
const response = await handleGeneratedSitemap(route, options.cleanPathname, functions);
|
|
516
530
|
return response ? captureRenderedMetadataRoute(response, route, options.cleanPathname) : null;
|
|
517
531
|
};
|
|
518
|
-
const cached = await readMatchedPrerenderedMetadataRouteResponse(
|
|
532
|
+
const cached = await readMatchedPrerenderedMetadataRouteResponse(cacheOptions, route, render);
|
|
519
533
|
if (cached.response) return cached.response;
|
|
520
|
-
const response = await renderMetadataRouteMiss(
|
|
534
|
+
const response = await renderMetadataRouteMiss(cacheOptions, route, render, cached.cachedEntry);
|
|
521
535
|
if (response) return response;
|
|
522
536
|
}
|
|
523
537
|
continue;
|
|
@@ -525,8 +539,12 @@ async function handleMetadataRouteRequest(options) {
|
|
|
525
539
|
}
|
|
526
540
|
const match = matchMetadataRoute(route, options.cleanPathname, functions, getUrlParts);
|
|
527
541
|
if (!match) continue;
|
|
542
|
+
if (!matchMetadataRoute(route, routePathname, functions, getRouteUrlParts)) {
|
|
543
|
+
skipDecodedOwner();
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
528
546
|
beginMetadataRouteCacheability(route);
|
|
529
|
-
const rawParams = route.patternParts ? metadataRouteRawParams(route, match,
|
|
547
|
+
const rawParams = route.patternParts ? metadataRouteRawParams(route, match, routePathname) : Object.create(null);
|
|
530
548
|
if (route.patternParts) {
|
|
531
549
|
const runtimeParams = { ...rawParams };
|
|
532
550
|
decodeMatchedParams(runtimeParams);
|
|
@@ -536,7 +554,7 @@ async function handleMetadataRouteRequest(options) {
|
|
|
536
554
|
setCurrentFetchSoftTags(buildMetadataRouteTags(route, options.cleanPathname, []));
|
|
537
555
|
return captureRenderedMetadataRoute(route.isDynamic ? await callDynamicMetadataRoute(route, match, options.makeThenableParams, functions) : serveStaticMetadataRoute(route), route, options.cleanPathname);
|
|
538
556
|
};
|
|
539
|
-
const cached = await readMatchedPrerenderedMetadataRouteResponse(
|
|
557
|
+
const cached = await readMatchedPrerenderedMetadataRouteResponse(cacheOptions, route, render);
|
|
540
558
|
if (cached.response) return cached.response;
|
|
541
559
|
if (route.isDynamic && isImageMetadataRoute(route) && functions.dynamicParams === false) {
|
|
542
560
|
const validationParams = { ...rawParams };
|
|
@@ -552,7 +570,7 @@ async function handleMetadataRouteRequest(options) {
|
|
|
552
570
|
});
|
|
553
571
|
if (dynamicParamsResponse) return dynamicParamsResponse;
|
|
554
572
|
}
|
|
555
|
-
return renderMetadataRouteMiss(
|
|
573
|
+
return renderMetadataRouteMiss(cacheOptions, route, render, cached.cachedEntry);
|
|
556
574
|
}
|
|
557
575
|
return null;
|
|
558
576
|
}
|
|
@@ -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;
|