vinext 1.4.1 → 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 +1 -1
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/check.js +1 -1
- package/dist/config/config-matchers.js +2 -2
- package/dist/config/destination-params.js +5 -2
- package/dist/config/next-config.d.ts +16 -1
- package/dist/config/next-config.js +31 -1
- package/dist/entries/app-rsc-entry.js +41 -18
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +2 -0
- package/dist/index.js +15 -12
- 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/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 +3 -5
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +21 -4
- 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 +38 -16
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -1
- package/dist/server/app-page-dispatch.js +73 -30
- package/dist/server/app-page-element-builder.d.ts +9 -20
- package/dist/server/app-page-element-builder.js +37 -49
- 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 -2
- package/dist/server/app-page-render.js +17 -3
- package/dist/server/app-page-request.d.ts +24 -2
- package/dist/server/app-page-request.js +10 -10
- package/dist/server/app-page-route-wiring.d.ts +10 -0
- package/dist/server/app-page-route-wiring.js +16 -12
- 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-response-stage-entry.js +1 -0
- 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-rsc-handler.d.ts +10 -1
- package/dist/server/app-rsc-handler.js +75 -43
- 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-route-matching.d.ts +14 -0
- package/dist/server/app-rsc-route-matching.js +42 -18
- package/dist/server/app-server-action-execution.js +16 -10
- package/dist/server/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -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 +23 -22
- package/dist/server/image-optimization.js +12 -2
- package/dist/server/metadata-route-response.js +24 -6
- 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-request-pipeline.js +6 -5
- package/dist/server/pages-request-stage-entry.js +1 -0
- package/dist/server/prod-server.js +17 -9
- package/dist/server/request-pipeline.d.ts +5 -0
- package/dist/server/request-pipeline.js +11 -1
- package/dist/server/response-stage-cacheability.d.ts +7 -0
- package/dist/server/response-stage-cacheability.js +6 -5
- 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/image-props.d.ts +6 -0
- package/dist/shims/image-props.js +39 -3
- package/dist/shims/image.js +3 -3
- package/dist/shims/slot.d.ts +3 -0
- package/dist/shims/slot.js +67 -9
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- package/package.json +1 -1
|
@@ -101,6 +101,7 @@ type AppRscRouteForMatching = {
|
|
|
101
101
|
pattern: string;
|
|
102
102
|
patternParts: string[];
|
|
103
103
|
routeHandler?: unknown;
|
|
104
|
+
routeSegments?: readonly string[] | null;
|
|
104
105
|
slots?: Record<string, AppRscSlotForMatching>;
|
|
105
106
|
siblingIntercepts?: AppRscSiblingInterceptForMatching[];
|
|
106
107
|
};
|
|
@@ -108,6 +109,14 @@ type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
|
|
|
108
109
|
matchedParams: AppRscRouteParams;
|
|
109
110
|
sourceRouteIsConcrete: boolean;
|
|
110
111
|
sourceMatchedParams: AppRscRouteParams;
|
|
112
|
+
/**
|
|
113
|
+
* The source route and params the request's interception context names,
|
|
114
|
+
* which the request handler authorizes. They differ from the rendered
|
|
115
|
+
* `sourceRouteIndex` only for a sibling intercept opened from a deeper
|
|
116
|
+
* source, which renders the intercepting route itself.
|
|
117
|
+
*/
|
|
118
|
+
authorizationSourceRouteIndex: number;
|
|
119
|
+
authorizationSourceParams: AppRscRouteParams;
|
|
111
120
|
};
|
|
112
121
|
type AppRscInterceptLoadState = {
|
|
113
122
|
page: unknown;
|
|
@@ -120,6 +129,11 @@ type AppRscInterceptLookupEntry = {
|
|
|
120
129
|
interceptionId: string | null;
|
|
121
130
|
interceptionGraphId: string | null;
|
|
122
131
|
sourceRouteIndex: number;
|
|
132
|
+
/**
|
|
133
|
+
* A sibling intercept whose owner route's page sits in the directory of the
|
|
134
|
+
* interception marker, so the owner is the intercepting route itself.
|
|
135
|
+
*/
|
|
136
|
+
ownerIsInterceptingRoute: boolean;
|
|
123
137
|
slotKey: string;
|
|
124
138
|
targetPattern: string;
|
|
125
139
|
targetPatternParts: string[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { decodeMatchedParams, decodeRouteSegment, splitPathSegments
|
|
1
|
+
import { decodeMatchedParams, decodeRouteSegment, splitPathSegments } from "../routing/utils.js";
|
|
2
2
|
import { createAppRouteGraphInterceptionId } from "../routing/app-route-ids.js";
|
|
3
3
|
import { buildRouteTrie, trieMatchRaw } from "../routing/route-trie.js";
|
|
4
4
|
import { extractRawRoutePatternParams, matchRoutePatternPrefix, matchRoutePatternRaw } from "../routing/route-pattern.js";
|
|
5
|
+
import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
|
|
5
6
|
import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
|
|
6
7
|
//#region src/server/app-rsc-route-matching.ts
|
|
7
8
|
/**
|
|
@@ -13,10 +14,19 @@ const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
|
|
|
13
14
|
function createRouteParams() {
|
|
14
15
|
return Object.create(null);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Whether each encoded raw segment is exactly the spelling the URL parser, as
|
|
19
|
+
* a browser runs it, gives its decoded form: `caf%C3%A9` for `café`, but `q&a`
|
|
20
|
+
* for `q&a`. `%61dmin`, `caf%c3%a9`, `%63af%C3%A9` and `q%26a` are other
|
|
21
|
+
* spellings of a page, which middleware never compared as its pathname.
|
|
22
|
+
*/
|
|
23
|
+
function isCanonicalPathEncoding(rawParts, decodedParts) {
|
|
24
|
+
return rawParts.every((rawPart, index) => !rawPart.includes("%") || rawPart === new URL(`/${decodedParts[index]}`, "http://n").pathname.slice(1));
|
|
25
|
+
}
|
|
26
|
+
function appRscPathnameParts(pathname) {
|
|
17
27
|
const pathOnly = pathname.split("?")[0];
|
|
18
28
|
const normalizedPathname = pathOnly === "/" ? "/" : pathOnly.replace(/\/$/, "");
|
|
19
|
-
return
|
|
29
|
+
return splitPathSegments(normalizedPathname);
|
|
20
30
|
}
|
|
21
31
|
function isAppRouteHandlerRoute(route) {
|
|
22
32
|
return route.routeHandler != null || typeof route.__loadRouteHandler === "function";
|
|
@@ -49,8 +59,10 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
49
59
|
const result = matchRawRequestParts(rawParts);
|
|
50
60
|
if (!result) return null;
|
|
51
61
|
if (decodedPathnameRoutes.size > 0 && result.route.patternParts.some((part) => part.startsWith(":")) && !isAppRouteHandlerRoute(result.route) && rawParts.some((part) => part.includes("%"))) {
|
|
52
|
-
const
|
|
53
|
-
|
|
62
|
+
const decodedParts = rawParts.map(decodeRouteSegment);
|
|
63
|
+
const decodedPathname = decodedParts.join("/");
|
|
64
|
+
const route = decodedPathnameRoutes.get(decodedPathname);
|
|
65
|
+
if (route && route !== result.route && (isCanonicalPathEncoding(rawParts, decodedParts) || getRenderedConcreteUrlPathsForRoute(route.pattern)?.has(`/${decodedPathname}`) === true)) return {
|
|
54
66
|
route,
|
|
55
67
|
params: createRouteParams()
|
|
56
68
|
};
|
|
@@ -62,23 +74,18 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
62
74
|
return interceptionIds.has(interceptionId);
|
|
63
75
|
},
|
|
64
76
|
matchRoute(url) {
|
|
65
|
-
|
|
66
|
-
const result = trieMatchRaw(routeTrie, appRscPathnameParts(url, false));
|
|
67
|
-
if (!result) return null;
|
|
68
|
-
result.params = extractRawRoutePatternParams(result.route.patternParts, rawParts);
|
|
69
|
-
normalizeMatchedParamsForRoute(result);
|
|
70
|
-
return result;
|
|
77
|
+
return matchRequestParts(appRscPathnameParts(url));
|
|
71
78
|
},
|
|
72
79
|
matchRequestRoute(url) {
|
|
73
|
-
return matchRequestParts(appRscPathnameParts(url
|
|
80
|
+
return matchRequestParts(appRscPathnameParts(url));
|
|
74
81
|
},
|
|
75
82
|
matchRawRequestRoute(url) {
|
|
76
|
-
return matchRawRequestParts(appRscPathnameParts(url
|
|
83
|
+
return matchRawRequestParts(appRscPathnameParts(url));
|
|
77
84
|
},
|
|
78
85
|
findIntercept(pathname, sourcePathname = null, interceptionId = null) {
|
|
79
86
|
if (sourcePathname === null) return null;
|
|
80
|
-
const urlParts = appRscPathnameParts(pathname
|
|
81
|
-
const sourceParts = appRscPathnameParts(sourcePathname
|
|
87
|
+
const urlParts = appRscPathnameParts(pathname);
|
|
88
|
+
const sourceParts = appRscPathnameParts(sourcePathname);
|
|
82
89
|
const matchedSourceRoute = matchRawRequestParts(sourceParts);
|
|
83
90
|
for (const entry of interceptLookup) {
|
|
84
91
|
if (interceptionId !== null && entry.interceptionId !== interceptionId) continue;
|
|
@@ -92,15 +99,21 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
92
99
|
if (sourceRoute && isAppRouteHandlerRoute(sourceRoute)) continue;
|
|
93
100
|
const matchedSourceParams = concreteSourceRoute ? concreteSourceRoute.params : sourceRoute ? matchSlotOwnerSourceParams(sourceParts, sourceRoute.patternParts, entry.sourceMatchPatternParts !== null) : null;
|
|
94
101
|
if (!concreteSourceRoute && matchedSourceParams) canonicalizeAppPageParams(matchedSourceParams);
|
|
102
|
+
const ownerRoute = routes[entry.sourceRouteIndex];
|
|
103
|
+
const rendersOwnerRoute = entry.ownerIsInterceptingRoute && concreteSourceRouteIndex !== entry.sourceRouteIndex && ownerRoute !== void 0;
|
|
104
|
+
const renderedSourceParams = rendersOwnerRoute ? matchSlotOwnerSourceParams(sourceParts, ownerRoute.patternParts, true) : matchedSourceParams;
|
|
105
|
+
if (rendersOwnerRoute && renderedSourceParams) canonicalizeAppPageParams(renderedSourceParams);
|
|
95
106
|
if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
|
|
96
|
-
const sourceParams =
|
|
107
|
+
const sourceParams = renderedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(renderedSourceParams, entry.sourceMatchPatternParts) : renderedSourceParams ?? createRouteParams();
|
|
97
108
|
return {
|
|
98
109
|
...entry,
|
|
99
110
|
page: entry.__loadState.page,
|
|
100
111
|
sourceRouteIsConcrete: concreteSourceRoute !== null,
|
|
101
|
-
sourceRouteIndex: concreteSourceRouteIndex,
|
|
112
|
+
sourceRouteIndex: rendersOwnerRoute ? entry.sourceRouteIndex : concreteSourceRouteIndex,
|
|
102
113
|
matchedParams: mergeMatchedParams(sourceParams, params),
|
|
103
|
-
sourceMatchedParams:
|
|
114
|
+
sourceMatchedParams: renderedSourceParams ?? createRouteParams(),
|
|
115
|
+
authorizationSourceRouteIndex: concreteSourceRouteIndex,
|
|
116
|
+
authorizationSourceParams: matchedSourceParams ?? createRouteParams()
|
|
104
117
|
};
|
|
105
118
|
}
|
|
106
119
|
return null;
|
|
@@ -108,6 +121,15 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
108
121
|
};
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
124
|
+
* Whether a sibling intercept's owner route is the intercepting route: its
|
|
125
|
+
* page lives in the directory that holds the interception marker. An owner
|
|
126
|
+
* found elsewhere stands in for a marker directory without a page.
|
|
127
|
+
*/
|
|
128
|
+
function isInterceptingRoute(routeSegments, sourcePageSegments) {
|
|
129
|
+
if (!routeSegments || !sourcePageSegments) return false;
|
|
130
|
+
return sourcePageSegments.findIndex((segment) => /^\(\.+\)/.test(segment)) === routeSegments.length && routeSegments.every((segment, index) => segment === sourcePageSegments[index]);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
111
133
|
* Params for the slot owner when interception falls back to it instead of a
|
|
112
134
|
* concrete descendant source route. The owner is what renders, and
|
|
113
135
|
* `matchInterceptRoute` reads the promoted route's params solely from these,
|
|
@@ -195,6 +217,7 @@ function createInterceptLookup(routes) {
|
|
|
195
217
|
interceptionId: typeof slotModule.id === "string" && sourceMatchPattern !== null ? createAppRouteGraphInterceptionId(slotModule.id, sourceMatchPattern, intercept.targetPattern) : null,
|
|
196
218
|
interceptionGraphId: null,
|
|
197
219
|
sourceRouteIndex: ownerRouteIndex,
|
|
220
|
+
ownerIsInterceptingRoute: false,
|
|
198
221
|
slotKey,
|
|
199
222
|
slotId: typeof slotModule.id === "string" ? slotModule.id : null,
|
|
200
223
|
targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
|
|
@@ -246,6 +269,7 @@ function createInterceptLookup(routes) {
|
|
|
246
269
|
interceptionId: typeof intercept.id === "string" ? intercept.id : null,
|
|
247
270
|
interceptionGraphId: typeof intercept.id === "string" ? intercept.id : null,
|
|
248
271
|
sourceRouteIndex: routeIndex,
|
|
272
|
+
ownerIsInterceptingRoute: !isAppRouteHandlerRoute(route) && isInterceptingRoute(route.routeSegments, intercept.sourcePageSegments),
|
|
249
273
|
slotKey: SIBLING_PAGE_INTERCEPT_SLOT_KEY,
|
|
250
274
|
slotId: typeof intercept.slotId === "string" ? intercept.slotId : null,
|
|
251
275
|
targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
|
|
@@ -12,6 +12,7 @@ import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayloa
|
|
|
12
12
|
import { splitPathSegments } from "../routing/utils.js";
|
|
13
13
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
14
14
|
import { logRequestError } from "./request-error-log.js";
|
|
15
|
+
import { SERVER_ACTION_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
|
|
15
16
|
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
16
17
|
import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
|
|
17
18
|
import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
|
|
@@ -21,17 +22,18 @@ import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
|
|
|
21
22
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
22
23
|
import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, createRscRequestUrl, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
|
|
23
24
|
import { getSetCookieName } from "./cookie-utils.js";
|
|
25
|
+
import { markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse } from "./app-rsc-response-finalizer.js";
|
|
24
26
|
import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
|
|
25
27
|
import { applyEdgeRuntimeHeader } from "./app-page-response.js";
|
|
26
28
|
import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
|
|
27
|
-
import {
|
|
28
|
-
import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
|
|
29
|
+
import { resolveAppPageNavigationParams } from "./app-page-navigation-params.js";
|
|
29
30
|
import "./app-page-stream.js";
|
|
31
|
+
import { hasDigest } from "./app-rsc-errors.js";
|
|
30
32
|
import { buildAppPageTags } from "./implicit-tags.js";
|
|
33
|
+
import { resolveAppPageInterceptSegmentConfig } from "./app-page-intercept-segment-config.js";
|
|
31
34
|
import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
|
|
32
35
|
import { createRootParamsUsageController, runWithRootParamsUsage } from "../shims/root-params.js";
|
|
33
36
|
import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
|
|
34
|
-
import { markAppRscResponseConfigHeadersApplied } from "./app-rsc-response-finalizer.js";
|
|
35
37
|
//#region src/server/app-server-action-execution.ts
|
|
36
38
|
/**
|
|
37
39
|
* Run a server-reference load or decode outside every request scope.
|
|
@@ -659,7 +661,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
659
661
|
options.clearRequestContext();
|
|
660
662
|
const headers = new Headers();
|
|
661
663
|
mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
|
|
662
|
-
headers.set("Cache-Control",
|
|
664
|
+
headers.set("Cache-Control", SERVER_ACTION_CACHE_CONTROL);
|
|
663
665
|
for (const cookie of actionPendingCookies) headers.append("Set-Cookie", cookie);
|
|
664
666
|
if (actionDraftCookie) headers.append("Set-Cookie", actionDraftCookie);
|
|
665
667
|
return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action failed: " + getServerActionFailureMessage(actionError), { headers });
|
|
@@ -879,6 +881,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
879
881
|
applyEdgeRuntimeHeader(redirectHeaders, options.isEdgeRuntime);
|
|
880
882
|
mergeActionRedirectSourceHeaders(redirectHeaders, options.sourceConfigHeaders ?? null);
|
|
881
883
|
mergeActionRedirectSourceHeaders(redirectHeaders, options.middlewareHeaders);
|
|
884
|
+
applyCdnResponseHeaders(redirectHeaders, { cacheControl: SERVER_ACTION_CACHE_CONTROL });
|
|
882
885
|
applyRscCompatibilityIdHeader(redirectHeaders);
|
|
883
886
|
const actionRedirectUrl = applyActionRedirectBasePath(actionRedirect.url, options.basePath ?? "");
|
|
884
887
|
redirectHeaders.set(ACTION_REDIRECT_HEADER, actionRedirectUrl);
|
|
@@ -938,10 +941,10 @@ async function handleServerActionRscRequest(options) {
|
|
|
938
941
|
targetPathname,
|
|
939
942
|
targetRoute: targetMatch?.route ?? null
|
|
940
943
|
}) ? 200 : 303;
|
|
941
|
-
return markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
|
|
944
|
+
return markStreamedActionRedirectResponse(markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
|
|
942
945
|
status: redirectResponseStatus,
|
|
943
946
|
headers: redirectHeaders
|
|
944
|
-
}, options.clearRequestContext));
|
|
947
|
+
}, options.clearRequestContext)));
|
|
945
948
|
}
|
|
946
949
|
const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
|
|
947
950
|
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
@@ -976,6 +979,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
976
979
|
let element;
|
|
977
980
|
let errorPattern = match ? match.route.pattern : options.cleanPathname;
|
|
978
981
|
let renderedPathAndSearch = null;
|
|
982
|
+
let actionRerenderIsEdgeRuntime = options.isEdgeRuntime;
|
|
979
983
|
const actionRerenderIsRscRequest = true;
|
|
980
984
|
if (match) {
|
|
981
985
|
const { route: actionRoute, params: actionParams } = match;
|
|
@@ -991,7 +995,9 @@ async function handleServerActionRscRequest(options) {
|
|
|
991
995
|
});
|
|
992
996
|
const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
|
|
993
997
|
await options.ensureRouteLoaded?.(actionRerenderTarget.route);
|
|
994
|
-
const
|
|
998
|
+
const actionInterceptConfig = actionRerenderTarget.interceptOpts ? resolveAppPageInterceptSegmentConfig(actionRerenderTarget.route, actionRerenderTarget.interceptOpts) : null;
|
|
999
|
+
if (actionInterceptConfig) actionRerenderIsEdgeRuntime = actionInterceptConfig.isEdgeRuntime;
|
|
1000
|
+
const actionRerenderDynamicConfig = actionInterceptConfig ? actionInterceptConfig.dynamicConfig : options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
|
|
995
1001
|
const actionRerenderSearchParams = prepareActionPageRerenderContext({
|
|
996
1002
|
draftModeCookie: actionDraftCookie,
|
|
997
1003
|
draftModeSecret: options.draftModeSecret,
|
|
@@ -1007,8 +1013,8 @@ async function handleServerActionRscRequest(options) {
|
|
|
1007
1013
|
});
|
|
1008
1014
|
const renderedSearch = options.searchParams.toString();
|
|
1009
1015
|
renderedPathAndSearch = renderedSearch ? `${options.cleanPathname}?${renderedSearch}` : options.cleanPathname;
|
|
1010
|
-
setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
|
|
1011
|
-
setCurrentFetchRevalidate(options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
|
|
1016
|
+
setCurrentFetchCacheMode(actionInterceptConfig ? actionInterceptConfig.fetchCacheMode : options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
|
|
1017
|
+
setCurrentFetchRevalidate(actionInterceptConfig ? actionInterceptConfig.revalidateSeconds : options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
|
|
1012
1018
|
setCurrentForceDynamicFetchDefault(actionRerenderDynamicConfig === "force-dynamic");
|
|
1013
1019
|
setCurrentFetchSoftTags(buildServerActionPageTags(actionRerenderTarget.route, options.cleanPathname));
|
|
1014
1020
|
const buildActionRerenderElement = () => options.buildPageElement({
|
|
@@ -1047,7 +1053,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
1047
1053
|
"Content-Type": VINEXT_RSC_CONTENT_TYPE,
|
|
1048
1054
|
Vary: VINEXT_RSC_VARY_HEADER
|
|
1049
1055
|
});
|
|
1050
|
-
applyEdgeRuntimeHeader(actionHeaders,
|
|
1056
|
+
applyEdgeRuntimeHeader(actionHeaders, actionRerenderIsEdgeRuntime);
|
|
1051
1057
|
mergeMiddlewareResponseHeaders(actionHeaders, options.middlewareHeaders);
|
|
1052
1058
|
applyRscCompatibilityIdHeader(actionHeaders);
|
|
1053
1059
|
setActionRevalidatedHeader(actionHeaders, actionRevalidationKind);
|
|
@@ -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 };
|
|
@@ -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.
|
|
@@ -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
|
}
|
|
@@ -317,7 +327,7 @@ function createPassthroughImageResponse(source, config) {
|
|
|
317
327
|
* cache headers.
|
|
318
328
|
*/
|
|
319
329
|
async function handleImageOptimization(request, handlers, allowedWidths, imageConfig) {
|
|
320
|
-
const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities);
|
|
330
|
+
const params = parseImageParams(new URL(request.url), allowedWidths, imageConfig?.qualities, { localPatterns: imageConfig?.localPatterns });
|
|
321
331
|
if (!params) return badRequestResponse();
|
|
322
332
|
const { imageUrl, width, quality } = params;
|
|
323
333
|
const source = await handlers.fetchAsset(imageUrl, request);
|
|
@@ -402,4 +412,4 @@ function handleConfiguredImageOptimization(request, fetchAsset, allowedWidths, i
|
|
|
402
412
|
}, allowedWidths, imageConfig);
|
|
403
413
|
}
|
|
404
414
|
//#endregion
|
|
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 };
|
|
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 };
|
|
@@ -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
|
}
|
|
@@ -53,17 +53,26 @@ function toH3Pathname(pathname) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
+
* The key Nitro's static handler looks a pathname up under, following
|
|
57
|
+
* `runtime/internal/static.mjs`: without one trailing slash and decoded
|
|
58
|
+
* except for `%2F`.
|
|
59
|
+
*/
|
|
60
|
+
function toAssetId(pathname) {
|
|
61
|
+
const id = pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
62
|
+
try {
|
|
63
|
+
return decodeURIComponent(id.replace(/%2f/gi, "%252F"));
|
|
64
|
+
} catch {
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
56
69
|
* Whether Nitro's static handler would answer a request for a pathname with
|
|
57
|
-
* a public file
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* extension of a compressed copy for every encoding the request accepts.
|
|
70
|
+
* a public file. It looks h3's pathname up under its key, and then as a
|
|
71
|
+
* directory with an `index.html`, each first with the extension of a
|
|
72
|
+
* compressed copy for every encoding the request accepts.
|
|
61
73
|
*/
|
|
62
74
|
function isPublicFilePathname(pathname, extensions, publicAssets) {
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
id = decodeURIComponent(id.replace(/%2f/gi, "%252F"));
|
|
66
|
-
} catch {}
|
|
75
|
+
const id = toAssetId(pathname);
|
|
67
76
|
const index = id.endsWith("/") ? `${id}index.html` : `${id}/index.html`;
|
|
68
77
|
return extensions.some((extension) => publicAssets.has(id + extension) || publicAssets.has(index + extension));
|
|
69
78
|
}
|
|
@@ -86,6 +95,9 @@ function createNitroPublicFilesPlugin(options) {
|
|
|
86
95
|
const token = crypto.randomUUID();
|
|
87
96
|
Reflect.set(globalThis, Symbol.for(NITRO_ASSETS_KEY), {
|
|
88
97
|
async fetch(request) {
|
|
98
|
+
const { pathname } = new URL(request.url);
|
|
99
|
+
const h3Pathname = toH3Pathname(pathname);
|
|
100
|
+
if (h3Pathname === void 0 || toAssetId(h3Pathname) !== toAssetId(pathname)) return new Response(null, { status: 404 });
|
|
89
101
|
const headers = new Headers(request.headers);
|
|
90
102
|
headers.set(ASSET_REQUEST_HEADER, token);
|
|
91
103
|
return appFetch(new Request(request, { headers }));
|
|
@@ -59,8 +59,12 @@ export declare function encodeUrlParserIgnoredCharacters(pathname: string): stri
|
|
|
59
59
|
* `/_next/data/<buildId>/en.json` → `/en`
|
|
60
60
|
* `/_next/data/<wrong-id>/about.json` → null
|
|
61
61
|
* `/_next/data/<buildId>/about` → null (missing .json suffix)
|
|
62
|
+
* `/_next/data/<buildId>/blog/...json` → null (strips to a dot segment)
|
|
63
|
+
*
|
|
64
|
+
* `decoded` marks a percent-decoded copy of a pathname whose raw form already
|
|
65
|
+
* parsed: a `%2e` left in it was `%252e` on the wire, an ordinary character.
|
|
62
66
|
*/
|
|
63
|
-
export declare function parseNextDataPathname(pathname: string, buildId: string): NextDataMatch | null;
|
|
67
|
+
export declare function parseNextDataPathname(pathname: string, buildId: string, decoded?: boolean): NextDataMatch | null;
|
|
64
68
|
/**
|
|
65
69
|
* The page pathname a `_next/data` URL addresses, whatever its buildId. Next.js
|
|
66
70
|
* compiles every middleware matcher with an optional `/_next/data/<buildId>`
|