vinext 1.4.2 → 1.4.4
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 +4 -1
- package/dist/build/prerender-paths.d.ts +11 -1
- package/dist/build/prerender-paths.js +41 -11
- package/dist/cli.js +1 -1
- package/dist/client/dev-error-overlay.d.ts +2 -1
- package/dist/client/dev-error-overlay.js +8 -3
- package/dist/config/config-matchers.js +6 -2
- package/dist/config/next-config.d.ts +9 -0
- package/dist/entries/app-rsc-entry.js +24 -12
- package/dist/entries/pages-server-entry.js +17 -4
- package/dist/index.js +48 -39
- package/dist/init-platform.js +2 -2
- package/dist/init.js +42 -5
- package/dist/server/api-handler.js +8 -5
- package/dist/server/app-browser-entry.js +26 -47
- package/dist/server/app-browser-error.d.ts +1 -1
- package/dist/server/app-browser-error.js +13 -4
- package/dist/server/app-browser-navigation-controller.js +2 -2
- package/dist/server/app-browser-server-action-client.js +2 -2
- package/dist/server/app-browser-state.js +2 -2
- package/dist/server/app-browser-supplemental-refresh.d.ts +13 -0
- package/dist/server/app-browser-supplemental-refresh.js +36 -4
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-history-state.js +1 -1
- package/dist/server/app-middleware.d.ts +2 -0
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-page-boundary-render.js +4 -2
- package/dist/server/app-page-dispatch.d.ts +1 -0
- package/dist/server/app-page-dispatch.js +5 -1
- package/dist/server/app-page-element-builder.d.ts +2 -0
- package/dist/server/app-page-element-builder.js +6 -5
- package/dist/server/app-page-render.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +7 -5
- package/dist/server/app-page-route-wiring.js +11 -9
- package/dist/server/app-pages-bridge.d.ts +6 -0
- package/dist/server/app-pages-bridge.js +8 -3
- package/dist/server/app-request-context.js +1 -1
- package/dist/server/app-request-stage-independent-entry.js +2 -3
- package/dist/server/app-route-handler-dispatch.js +1 -1
- package/dist/server/app-route-handler-execution.js +1 -1
- package/dist/server/app-route-handler-response.d.ts +1 -0
- package/dist/server/app-route-handler-response.js +27 -2
- package/dist/server/app-router-entry.js +2 -3
- package/dist/server/app-rsc-cache-busting.d.ts +8 -0
- package/dist/server/app-rsc-cache-busting.js +13 -1
- package/dist/server/app-rsc-combined-handler.js +2 -1
- package/dist/server/app-rsc-handler.d.ts +4 -0
- package/dist/server/app-rsc-handler.js +41 -24
- package/dist/server/app-rsc-response-finalizer.d.ts +15 -3
- package/dist/server/app-rsc-response-finalizer.js +51 -7
- package/dist/server/app-rsc-response-stage.js +1 -1
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.js +18 -10
- package/dist/server/config-headers.d.ts +7 -0
- package/dist/server/config-headers.js +20 -1
- package/dist/server/edge-api-runtime.d.ts +6 -2
- package/dist/server/edge-api-runtime.js +21 -4
- package/dist/server/image-animation.d.ts +9 -0
- package/dist/server/image-animation.js +118 -0
- package/dist/server/image-optimization.d.ts +25 -5
- package/dist/server/image-optimization.js +305 -21
- package/dist/server/image-source-reader.d.ts +34 -0
- package/dist/server/image-source-reader.js +126 -0
- package/dist/server/metadata-route-response.d.ts +6 -1
- package/dist/server/metadata-route-response.js +24 -6
- package/dist/server/middleware-matcher.d.ts +1 -0
- package/dist/server/middleware-matcher.js +1 -1
- package/dist/server/middleware-runtime.d.ts +5 -0
- package/dist/server/middleware-runtime.js +48 -15
- package/dist/server/middleware.d.ts +1 -1
- package/dist/server/middleware.js +2 -1
- package/dist/server/navigation-planner.js +14 -6
- package/dist/server/nitro-request-target-plugin.d.ts +17 -0
- package/dist/server/nitro-request-target-plugin.js +22 -0
- package/dist/server/open-redirect.d.ts +2 -1
- package/dist/server/open-redirect.js +2 -1
- package/dist/server/pages-client-assets.d.ts +1 -0
- package/dist/server/pages-i18n.d.ts +7 -0
- package/dist/server/pages-i18n.js +14 -1
- package/dist/server/pages-node-compat.js +7 -3
- package/dist/server/pages-page-handler.js +4 -4
- package/dist/server/pages-request-pipeline.d.ts +9 -0
- package/dist/server/pages-request-pipeline.js +29 -10
- package/dist/server/pages-request-stage-entry.js +7 -2
- package/dist/server/pages-router-entry.js +3 -0
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +48 -54
- package/dist/server/remove-request-headers.d.ts +16 -0
- package/dist/server/remove-request-headers.js +23 -0
- package/dist/server/srvx-middleware-origin.d.ts +13 -0
- package/dist/server/srvx-middleware-origin.js +19 -0
- package/dist/server/srvx-request-target.d.ts +12 -0
- package/dist/server/srvx-request-target.js +20 -0
- package/dist/server/static-file-cache.js +10 -22
- package/dist/server/worker-image-optimization.d.ts +8 -1
- package/dist/server/worker-image-optimization.js +25 -6
- package/dist/server/worker-revalidation-context.js +1 -0
- package/dist/shims/cache-runtime.js +1 -1
- package/dist/shims/cache.js +6 -3
- package/dist/shims/client-page-root.js +2 -2
- package/dist/shims/error-boundary.d.ts +6 -2
- package/dist/shims/error-boundary.js +6 -4
- package/dist/shims/internal/navigation-untracked.js +1 -1
- package/dist/shims/metadata.js +1 -1
- package/dist/shims/navigation-server.js +1 -1
- package/dist/shims/navigation.js +4 -4
- package/dist/shims/navigation.react-server.js +3 -3
- package/dist/shims/request-context.d.ts +3 -1
- package/dist/shims/root-params.js +16 -2
- package/dist/shims/slot.js +3 -3
- package/dist/shims/url-utils.js +6 -1
- package/dist/utils/client-entry-manifest.d.ts +5 -0
- package/dist/utils/client-entry-manifest.js +1 -0
- package/dist/utils/client-runtime-metadata.d.ts +5 -0
- package/dist/utils/client-runtime-metadata.js +33 -7
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//#region src/server/image-source-reader.ts
|
|
2
|
+
/**
|
|
3
|
+
* Streamed reads of an image source for the image optimization endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Animation detection parses a GIF, PNG or WebP source as it streams in rather
|
|
6
|
+
* than buffering it, so a large source cannot exhaust a Worker isolate's
|
|
7
|
+
* memory. Only the source's first bytes are kept, to replay without a refetch.
|
|
8
|
+
*/
|
|
9
|
+
/** Most bytes of a source kept to replay it after animation detection. */
|
|
10
|
+
const HELD_SOURCE_LIMIT = 1048576;
|
|
11
|
+
/** Thrown once a source grows past `images.maximumResponseBody`. */
|
|
12
|
+
var ImageSourceTooLargeError = class extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Image source exceeds images.maximumResponseBody");
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var ImageSourceReader = class {
|
|
18
|
+
#reader;
|
|
19
|
+
#limit;
|
|
20
|
+
#read = 0;
|
|
21
|
+
#done = false;
|
|
22
|
+
#tooLarge = false;
|
|
23
|
+
#window = [];
|
|
24
|
+
#windowStart = 0;
|
|
25
|
+
#windowEnd = 0;
|
|
26
|
+
#held = [];
|
|
27
|
+
#replayed = false;
|
|
28
|
+
constructor(body, limit) {
|
|
29
|
+
this.#reader = body.getReader();
|
|
30
|
+
this.#limit = limit;
|
|
31
|
+
}
|
|
32
|
+
/** Whether the source has grown past the limit. */
|
|
33
|
+
get tooLarge() {
|
|
34
|
+
return this.#tooLarge;
|
|
35
|
+
}
|
|
36
|
+
/** The source's length once it has all been read and held, else null. */
|
|
37
|
+
get completeLength() {
|
|
38
|
+
return this.#done && this.#held ? this.#read : null;
|
|
39
|
+
}
|
|
40
|
+
/** Stop reading the source without waiting on its cancellation. */
|
|
41
|
+
cancel() {
|
|
42
|
+
this.#reader.cancel().catch(() => {});
|
|
43
|
+
}
|
|
44
|
+
/** Up to `length` bytes at `offset`, fewer where the source ends. */
|
|
45
|
+
async bytes(offset, length) {
|
|
46
|
+
const end = offset + length;
|
|
47
|
+
await this.#reach(offset, end);
|
|
48
|
+
const available = Math.min(end, this.#windowEnd);
|
|
49
|
+
const bytes = new Uint8Array(Math.max(0, available - offset));
|
|
50
|
+
let start = this.#windowStart;
|
|
51
|
+
for (const chunk of this.#window) {
|
|
52
|
+
if (start >= available) break;
|
|
53
|
+
const from = Math.max(offset - start, 0);
|
|
54
|
+
const to = Math.min(available - start, chunk.byteLength);
|
|
55
|
+
if (from < to) bytes.set(chunk.subarray(from, to), start + from - offset);
|
|
56
|
+
start += chunk.byteLength;
|
|
57
|
+
}
|
|
58
|
+
return bytes;
|
|
59
|
+
}
|
|
60
|
+
/** The byte at `offset`, or undefined past the source's end. */
|
|
61
|
+
async byte(offset) {
|
|
62
|
+
return (await this.bytes(offset, 1))[0];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The whole source as a stream, replaying the bytes already read, or null
|
|
66
|
+
* once they are no longer all held. An incompletely read source replays
|
|
67
|
+
* once; the rest streams from the source, still within the limit, and is
|
|
68
|
+
* held too while the whole source fits.
|
|
69
|
+
*/
|
|
70
|
+
body() {
|
|
71
|
+
const held = this.#held;
|
|
72
|
+
if (held === null || this.#replayed && !this.#done) return null;
|
|
73
|
+
this.#replayed = true;
|
|
74
|
+
this.#window = [];
|
|
75
|
+
let index = 0;
|
|
76
|
+
return new ReadableStream({
|
|
77
|
+
pull: async (controller) => {
|
|
78
|
+
if (index < held.length) {
|
|
79
|
+
controller.enqueue(held[index++]);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const chunk = await this.#next();
|
|
83
|
+
if (!chunk) return controller.close();
|
|
84
|
+
this.#hold(chunk);
|
|
85
|
+
index = held.length;
|
|
86
|
+
controller.enqueue(chunk);
|
|
87
|
+
},
|
|
88
|
+
cancel: (reason) => this.#reader.cancel(reason)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async #next() {
|
|
92
|
+
if (this.#done) return void 0;
|
|
93
|
+
const { done, value } = await this.#reader.read();
|
|
94
|
+
if (done) {
|
|
95
|
+
this.#done = true;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.#read += value.byteLength;
|
|
99
|
+
if (this.#read > this.#limit) {
|
|
100
|
+
this.#tooLarge = true;
|
|
101
|
+
this.cancel();
|
|
102
|
+
throw new ImageSourceTooLargeError();
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
#hold(chunk) {
|
|
107
|
+
if (!this.#held) return;
|
|
108
|
+
if (this.#read <= HELD_SOURCE_LIMIT) this.#held.push(chunk);
|
|
109
|
+
else this.#held = null;
|
|
110
|
+
}
|
|
111
|
+
/** Read until `end` is in the window, dropping chunks wholly before `offset`. */
|
|
112
|
+
async #reach(offset, end) {
|
|
113
|
+
if (offset < this.#windowStart) throw new Error("Image source reads must not move backwards");
|
|
114
|
+
for (;;) {
|
|
115
|
+
while (this.#window.length > 0 && this.#windowStart + this.#window[0].byteLength <= offset) this.#windowStart += this.#window.shift().byteLength;
|
|
116
|
+
if (this.#windowEnd >= end) return;
|
|
117
|
+
const chunk = await this.#next();
|
|
118
|
+
if (!chunk) return;
|
|
119
|
+
this.#hold(chunk);
|
|
120
|
+
this.#window.push(chunk);
|
|
121
|
+
this.#windowEnd += chunk.byteLength;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
export { ImageSourceReader, ImageSourceTooLargeError };
|
|
@@ -21,6 +21,11 @@ type MetadataRouteRequestOptions = {
|
|
|
21
21
|
isrRouteKey?: (pathname: string) => string;
|
|
22
22
|
isrSet?: MetadataRouteCacheSetter;
|
|
23
23
|
makeThenableParams: MetadataRouteMakeThenableParams;
|
|
24
|
+
/**
|
|
25
|
+
* The pathname params are read from. A direct request passes its raw path
|
|
26
|
+
* here and its decoded path as `cleanPathname`; a rewrite passes its
|
|
27
|
+
* destination, still percent-encoded, as both.
|
|
28
|
+
*/
|
|
24
29
|
routePathname?: string;
|
|
25
30
|
scheduleBackgroundRegeneration?: MetadataRouteBackgroundRegenerator;
|
|
26
31
|
};
|
|
@@ -36,6 +41,6 @@ export type PrerenderableMetadataRoute = {
|
|
|
36
41
|
*/
|
|
37
42
|
export declare function getPrerenderableMetadataRoutePaths(metadataRoutes: readonly MetadataRuntimeRoute[]): Promise<PrerenderableMetadataRoute[]>;
|
|
38
43
|
export declare function isMetadataRouteRequestPath(metadataRoutes: readonly MetadataRuntimeRoute[], cleanPathname: string): boolean;
|
|
39
|
-
export declare function handleMetadataRouteRequest(
|
|
44
|
+
export declare function handleMetadataRouteRequest(requestOptions: MetadataRouteRequestOptions): Promise<Response | null>;
|
|
40
45
|
//#endregion
|
|
41
46
|
export {};
|
|
@@ -2,7 +2,7 @@ import { markFullyBufferedBody } from "./fully-buffered-response.js";
|
|
|
2
2
|
import { closeAfterResponse, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
|
|
3
3
|
import { VINEXT_METADATA_ROUTE_CACHE_HEADER } from "./headers.js";
|
|
4
4
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
5
|
-
import { decodeMatchedParams } from "../routing/utils.js";
|
|
5
|
+
import { decodeMatchedParams, normalizePathnameForRouteMatch } from "../routing/utils.js";
|
|
6
6
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
7
7
|
import { runInWorkStoreScope } from "../shims/internal/work-store-scope.js";
|
|
8
8
|
import { beginRouteCacheability, markRouteCacheabilityExplicitResponsePolicy, markRouteCacheabilityResponseBodyComplete, recordRouteCacheability } from "../shims/cacheability-classification.js";
|
|
@@ -16,7 +16,7 @@ import { _consumeRequestScopedCacheLife, _isOnDemandRevalidate } from "../shims/
|
|
|
16
16
|
import { ensureFetchPatch, getCollectedFetchTags, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
|
|
17
17
|
import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
|
|
18
18
|
import { isValidMetadataImageId, manifestToJson, matchMetadataRoutePattern, robotsToText, sitemapToXml } from "./metadata-routes.js";
|
|
19
|
-
import { buildAppRouteCacheValue, buildRouteHandlerCachedResponse } from "./app-route-handler-response.js";
|
|
19
|
+
import { buildAppRouteCacheValue, buildRouteHandlerCachedResponse, stripRouteHandlerFramingHeaders } from "./app-route-handler-response.js";
|
|
20
20
|
import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
|
|
21
21
|
import { buildPageCacheTags } from "./implicit-tags.js";
|
|
22
22
|
import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
|
|
@@ -89,6 +89,20 @@ function getMetadataRouteFunctions(route) {
|
|
|
89
89
|
routeFunctionCache.set(route, functions);
|
|
90
90
|
return functions;
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Next.js compiles a metadata route into a Route Handler, and under a dynamic
|
|
94
|
+
* segment its build stores it only when the route's own module generates its
|
|
95
|
+
* params or is `force-static`/`error` (collectAppRouteSegments reads no parent
|
|
96
|
+
* layout). Its metadata loader adds generateStaticParams for
|
|
97
|
+
* generateImageMetadata. Probed on next@16.2.7: a parent layout's
|
|
98
|
+
* generateStaticParams, `revalidate` or `dynamicParams` leave the route
|
|
99
|
+
* dynamic. Generated sitemaps are only served below a static prefix.
|
|
100
|
+
*/
|
|
101
|
+
function isMetadataRouteStaticGenEnabled(route) {
|
|
102
|
+
if (!route.isDynamic || !route.patternParts) return true;
|
|
103
|
+
const functions = getMetadataRouteFunctions(route);
|
|
104
|
+
return route.module?.dynamic === "force-static" || route.module?.dynamic === "error" || functions.generateStaticParams !== null || functions.generateImageMetadata !== null;
|
|
105
|
+
}
|
|
92
106
|
function isUseCacheFunction(value) {
|
|
93
107
|
return value !== null && Reflect.get(value, USE_CACHE_FUNCTION_SYMBOL) === true;
|
|
94
108
|
}
|
|
@@ -165,7 +179,7 @@ async function captureRenderedMetadataRoute(response, route, cleanPathname) {
|
|
|
165
179
|
...observedCacheLife,
|
|
166
180
|
revalidate: frameworkRevalidate
|
|
167
181
|
};
|
|
168
|
-
const cacheable = (_isOnDemandRevalidate() || response.status < 400 || response.status === 404) && completed && !isRenderDynamicLatched() && !getActiveDraftModeState() && !hasDraftModeCookieHeader() && route.module?.dynamic !== "force-dynamic" && frameworkRevalidate > 0 && !response.headers.has("set-cookie");
|
|
182
|
+
const cacheable = (_isOnDemandRevalidate() || response.status < 400 || response.status === 404) && completed && !isRenderDynamicLatched() && !getActiveDraftModeState() && !hasDraftModeCookieHeader() && route.module?.dynamic !== "force-dynamic" && isMetadataRouteStaticGenEnabled(route) && frameworkRevalidate > 0 && !response.headers.has("set-cookie");
|
|
169
183
|
const cacheControl = cacheable ? buildAppRouteMissIsrCacheControl(frameworkRevalidate, cacheLife?.expire) : NEVER_CACHE_CONTROL;
|
|
170
184
|
if (isUserResponse && !hasUserPolicy) {
|
|
171
185
|
response = new Response(response.body, {
|
|
@@ -233,7 +247,7 @@ async function runMetadataRouteRegeneration(options, route, render, routeKey, pr
|
|
|
233
247
|
}
|
|
234
248
|
/** The outer ISR key for a matched metadata route, or null if it's never cached. */
|
|
235
249
|
function getOuterMetadataCacheKey(options, route) {
|
|
236
|
-
if (!isOuterMetadataCacheEnabled() || !options.isrRouteKey || getActiveDraftModeState() || hasDraftModeCookieHeader() || route.module?.dynamic === "force-dynamic" || route.module?.revalidate === 0) return null;
|
|
250
|
+
if (!isOuterMetadataCacheEnabled() || !options.isrRouteKey || getActiveDraftModeState() || hasDraftModeCookieHeader() || route.module?.dynamic === "force-dynamic" || route.module?.revalidate === 0 || !isMetadataRouteStaticGenEnabled(route)) return null;
|
|
237
251
|
return options.isrRouteKey(options.cleanPathname);
|
|
238
252
|
}
|
|
239
253
|
/**
|
|
@@ -247,7 +261,7 @@ async function renderMetadataRouteMiss(options, route, render, cachedEntry) {
|
|
|
247
261
|
const rendered = await render();
|
|
248
262
|
if (!rendered) return null;
|
|
249
263
|
await writeMetadataRouteMiss(options, route, rendered, cachedEntry);
|
|
250
|
-
return rendered.response;
|
|
264
|
+
return stripRouteHandlerFramingHeaders(rendered.response);
|
|
251
265
|
};
|
|
252
266
|
const routeKey = _isOnDemandRevalidate() ? getOuterMetadataCacheKey(options, route) : null;
|
|
253
267
|
if (routeKey === null || !options.isrSet) return renderAndStore();
|
|
@@ -501,7 +515,11 @@ async function writeMetadataRouteMiss(options, route, rendered, cachedEntry) {
|
|
|
501
515
|
console.error("[vinext] Metadata route ISR cache write error:", error);
|
|
502
516
|
}
|
|
503
517
|
}
|
|
504
|
-
async function handleMetadataRouteRequest(
|
|
518
|
+
async function handleMetadataRouteRequest(requestOptions) {
|
|
519
|
+
const options = requestOptions.routePathname === requestOptions.cleanPathname ? {
|
|
520
|
+
...requestOptions,
|
|
521
|
+
cleanPathname: normalizePathnameForRouteMatch(requestOptions.cleanPathname)
|
|
522
|
+
} : requestOptions;
|
|
505
523
|
let urlParts;
|
|
506
524
|
const getUrlParts = () => urlParts ??= options.cleanPathname.split("/").filter(Boolean);
|
|
507
525
|
const routePathname = options.routePathname ?? options.cleanPathname;
|
|
@@ -60,7 +60,7 @@ function matchMatcherPattern(pathname, pattern, i18nConfig, localeContext) {
|
|
|
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 matchRequestPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname
|
|
63
|
+
return matchRequestPattern(localeContext?.kind === "defaulted" ? `/${localeContext.defaultLocale}${pathname !== "/" ? pathname : localeContext.restoreRootTrailingSlash ? "/" : ""}` : pathname, matcher.source);
|
|
64
64
|
}
|
|
65
65
|
const APP_ROUTE_TRANSPORT_SUFFIX = "\\.rsc|\\.segments/.+\\.segment\\.rsc";
|
|
66
66
|
const APP_ROUTE_TRANSPORT_SUFFIX_RE = /(?:\.rsc|\.segments\/.+\.segment\.rsc)$/;
|
|
@@ -61,6 +61,11 @@ type ExecuteMiddlewareOptions = {
|
|
|
61
61
|
* second tee whose preserved side would never be consumed.
|
|
62
62
|
*/
|
|
63
63
|
requestBodyAlreadyIsolated?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* `shouldRestoreLocaleRootTrailingSlash` for the raw request pathname. Next.js
|
|
66
|
+
* then tests the as-requested root as `/en/` after default-locale insertion.
|
|
67
|
+
*/
|
|
68
|
+
restoreLocaleRootTrailingSlash?: boolean;
|
|
64
69
|
request: Request;
|
|
65
70
|
/**
|
|
66
71
|
* The user's `trailingSlash` config. Plumbed into the NextRequest's NextURL
|
|
@@ -69,9 +69,26 @@ function middlewareMatcher(mod) {
|
|
|
69
69
|
if (!isMiddlewareConfigExport(config)) return void 0;
|
|
70
70
|
return config.matcher;
|
|
71
71
|
}
|
|
72
|
+
const MIDDLEWARE_MESSAGE_HEADER_NAMES = [
|
|
73
|
+
"accept-encoding",
|
|
74
|
+
"connection",
|
|
75
|
+
"content-encoding",
|
|
76
|
+
"content-length",
|
|
77
|
+
"expect",
|
|
78
|
+
"keep-alive",
|
|
79
|
+
"keepalive",
|
|
80
|
+
"transfer-encoding"
|
|
81
|
+
];
|
|
82
|
+
function deleteMiddlewareMessageHeaders(headers) {
|
|
83
|
+
for (const name of MIDDLEWARE_MESSAGE_HEADER_NAMES) headers.delete(name);
|
|
84
|
+
}
|
|
85
|
+
function processMiddlewareResponseHeaders(headers) {
|
|
86
|
+
processMiddlewareHeaders(headers);
|
|
87
|
+
deleteMiddlewareMessageHeaders(headers);
|
|
88
|
+
}
|
|
72
89
|
function stripMiddlewareHeadersFromResponse(response) {
|
|
73
90
|
const headers = new Headers(response.headers);
|
|
74
|
-
|
|
91
|
+
processMiddlewareResponseHeaders(headers);
|
|
75
92
|
return new Response(response.body, {
|
|
76
93
|
status: response.status,
|
|
77
94
|
statusText: response.statusText,
|
|
@@ -108,7 +125,7 @@ function relativizeLocation(location, requestUrl) {
|
|
|
108
125
|
*/
|
|
109
126
|
function dataRedirectResponse(target, originalResponse) {
|
|
110
127
|
const headers = new Headers(originalResponse.headers);
|
|
111
|
-
|
|
128
|
+
processMiddlewareResponseHeaders(headers);
|
|
112
129
|
headers.delete("Location");
|
|
113
130
|
headers.set("x-nextjs-redirect", target);
|
|
114
131
|
return new Response(null, {
|
|
@@ -120,6 +137,7 @@ function collectMiddlewareHeaders(response) {
|
|
|
120
137
|
const responseHeaders = new Headers();
|
|
121
138
|
for (const [key, value] of response.headers) if (!key.startsWith("x-middleware-") || shouldKeepMiddlewareHeader(key)) responseHeaders.append(key, value);
|
|
122
139
|
dropEmptyMiddlewareSetCookie(responseHeaders);
|
|
140
|
+
deleteMiddlewareMessageHeaders(responseHeaders);
|
|
123
141
|
return responseHeaders;
|
|
124
142
|
}
|
|
125
143
|
function drainFetchEvent(fetchEvent) {
|
|
@@ -209,12 +227,19 @@ async function executeMiddlewareModule(options) {
|
|
|
209
227
|
};
|
|
210
228
|
const isStaleDataRequest = options.isDataRequest === true && options.isStaleDataRequest === true;
|
|
211
229
|
const toPageMatcherPathname = (candidate) => candidate !== null && options.isDataRequest && !isStaleDataRequest ? nextDataMiddlewareMatchPathname(candidate) ?? candidate : candidate;
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
const toMatchPathnames = (candidate) => {
|
|
231
|
+
const encoded = toPageMatcherPathname(prepareMatcherPathname(candidate));
|
|
232
|
+
let decoded = encoded;
|
|
233
|
+
try {
|
|
234
|
+
if (encoded !== null) decoded = decodeURIComponent(encoded);
|
|
235
|
+
else if (!options.i18nConfig) decoded = toPageMatcherPathname(prepareMatcherPathname(decodeURIComponent(candidate)));
|
|
236
|
+
} catch {}
|
|
237
|
+
return {
|
|
238
|
+
decoded,
|
|
239
|
+
encoded
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
const { decoded: decodedMatchPathname, encoded: encodedMatchPathname } = toMatchPathnames(encodedRequestPathname);
|
|
218
243
|
let localeContext;
|
|
219
244
|
if (isStaleDataRequest) localeContext = { kind: "next-data" };
|
|
220
245
|
else if (options.i18nConfig && encodedMatchPathname !== null) {
|
|
@@ -226,12 +251,20 @@ async function executeMiddlewareModule(options) {
|
|
|
226
251
|
kind: "defaulted"
|
|
227
252
|
};
|
|
228
253
|
}
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
254
|
+
const slashedRequestPathname = normalizePath(requestPathname);
|
|
255
|
+
const requestedMatchPathnames = slashedRequestPathname !== encodedRequestPathname ? toMatchPathnames(slashedRequestPathname) : {
|
|
256
|
+
decoded: decodedMatchPathname,
|
|
257
|
+
encoded: encodedMatchPathname
|
|
258
|
+
};
|
|
259
|
+
const candidatePathnames = [];
|
|
260
|
+
if (requestedMatchPathnames.encoded !== null) candidatePathnames.push(requestedMatchPathnames.encoded);
|
|
261
|
+
if (requestedMatchPathnames.decoded !== null && requestedMatchPathnames.decoded !== requestedMatchPathnames.encoded) candidatePathnames.push(requestedMatchPathnames.decoded);
|
|
262
|
+
const requestedLocaleContext = options.restoreLocaleRootTrailingSlash && localeContext?.kind === "defaulted" ? {
|
|
263
|
+
...localeContext,
|
|
264
|
+
restoreRootTrailingSlash: true
|
|
265
|
+
} : localeContext;
|
|
266
|
+
if (candidatePathnames.some((candidate) => matchesMiddlewarePathname(candidate, matcher, options.i18nConfig, requestedLocaleContext))) options.onPathMatch?.();
|
|
267
|
+
if (!candidatePathnames.some((candidate) => matchesMiddleware(candidate, matcher, options.request, options.i18nConfig, requestedLocaleContext))) return { continue: true };
|
|
235
268
|
options.onMatch?.();
|
|
236
269
|
const middlewareUrl = withOrigin(requestUrl, getRequestExecutionContext()?.middlewareOrigin ?? requestUrl.origin);
|
|
237
270
|
const nextRequest = createNextRequest(options.request, middlewareUrl, options.i18nConfig, options.basePath, options.trailingSlash, hadBasePath, options.requestBodyAlreadyIsolated, options.skipTrailingSlashRedirect);
|
|
@@ -305,7 +338,7 @@ async function executeMiddlewareModule(options) {
|
|
|
305
338
|
}
|
|
306
339
|
const responseHeaders = new Headers(response.headers);
|
|
307
340
|
responseHeaders.delete("location");
|
|
308
|
-
|
|
341
|
+
processMiddlewareResponseHeaders(responseHeaders);
|
|
309
342
|
const relativizedResponseHeaders = new Headers(response.headers);
|
|
310
343
|
relativizedResponseHeaders.set("Location", normalizedLocation);
|
|
311
344
|
const relativizedResponse = new Response(response.body, {
|
|
@@ -44,6 +44,6 @@ export declare function findMiddlewareFile(root: string, fileMatcher: ValidFileM
|
|
|
44
44
|
* @param request - The incoming Request object
|
|
45
45
|
* @returns Middleware result describing what action to take
|
|
46
46
|
*/
|
|
47
|
-
export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string, isStaleDataRequest?: boolean, skipTrailingSlashRedirect?: boolean): Promise<MiddlewareResult>;
|
|
47
|
+
export declare function runMiddleware(runner: ModuleRunner, middlewarePath: string, request: Request, i18nConfig?: NextI18nConfig | null, basePath?: string, trailingSlash?: boolean, isDataRequest?: boolean, normalizedPathname?: string, isStaleDataRequest?: boolean, skipTrailingSlashRedirect?: boolean, restoreLocaleRootTrailingSlash?: boolean): Promise<MiddlewareResult>;
|
|
48
48
|
//#endregion
|
|
49
49
|
export { matchPattern, matchesMiddleware };
|
|
@@ -77,7 +77,7 @@ function isMiddlewareModule(value) {
|
|
|
77
77
|
* @param request - The incoming Request object
|
|
78
78
|
* @returns Middleware result describing what action to take
|
|
79
79
|
*/
|
|
80
|
-
async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname, isStaleDataRequest, skipTrailingSlashRedirect) {
|
|
80
|
+
async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePath, trailingSlash, isDataRequest, normalizedPathname, isStaleDataRequest, skipTrailingSlashRedirect, restoreLocaleRootTrailingSlash) {
|
|
81
81
|
const mod = await runner.import(middlewarePath);
|
|
82
82
|
if (!isMiddlewareModule(mod)) throw new Error(`Middleware module "${middlewarePath}" did not evaluate to an object.`);
|
|
83
83
|
return runGeneratedMiddleware({
|
|
@@ -92,6 +92,7 @@ async function runMiddleware(runner, middlewarePath, request, i18nConfig, basePa
|
|
|
92
92
|
module: mod,
|
|
93
93
|
normalizedPathname,
|
|
94
94
|
request,
|
|
95
|
+
restoreLocaleRootTrailingSlash,
|
|
95
96
|
skipTrailingSlashRedirect,
|
|
96
97
|
trailingSlash
|
|
97
98
|
});
|
|
@@ -262,11 +262,12 @@ function createFreshFetchDecision(facts, reason) {
|
|
|
262
262
|
}
|
|
263
263
|
function classifyNavigationReuse(facts) {
|
|
264
264
|
if (facts.navigationKind === "refresh") return createFreshFetchDecision(facts, "refresh");
|
|
265
|
-
if (
|
|
265
|
+
if (facts.bypassNavigationCache) return createFreshFetchDecision(facts, "cacheBypassed");
|
|
266
|
+
if (facts.visitedResponse.status === "available") return {
|
|
266
267
|
kind: "reuseVisitedResponse",
|
|
267
268
|
trace: createNavigationReuseTrace(NavigationTraceReasonCodes.visitedResponseReuse, facts)
|
|
268
269
|
};
|
|
269
|
-
if (
|
|
270
|
+
if (facts.prefetch.status === "available") return {
|
|
270
271
|
kind: "consumePrefetch",
|
|
271
272
|
trace: createNavigationReuseTrace(NavigationTraceReasonCodes.prefetchResponseReuse, facts)
|
|
272
273
|
};
|
|
@@ -275,9 +276,8 @@ function classifyNavigationReuse(facts) {
|
|
|
275
276
|
kind: "attemptOptimisticRouteShell",
|
|
276
277
|
trace: createNavigationReuseTrace(NavigationTraceReasonCodes.optimisticRouteShell, facts)
|
|
277
278
|
};
|
|
278
|
-
return createFreshFetchDecision(facts, facts.
|
|
279
|
+
return createFreshFetchDecision(facts, facts.optimisticRouteShell.reason);
|
|
279
280
|
}
|
|
280
|
-
if (facts.bypassNavigationCache) return createFreshFetchDecision(facts, "cacheBypassed");
|
|
281
281
|
return createFreshFetchDecision(facts, "cacheMiss");
|
|
282
282
|
}
|
|
283
283
|
function classifyNavigationPrefetchProbe(facts) {
|
|
@@ -676,10 +676,11 @@ function planFlightResponseArrived(options) {
|
|
|
676
676
|
slotBindingSource: "manifestTarget",
|
|
677
677
|
snapshot: targetSnapshot
|
|
678
678
|
});
|
|
679
|
+
const renderedRootLayoutChanged = classifyRootBoundaryTransition(options.state.visibleSnapshot.rootBoundaryId, targetSnapshot.rootBoundaryId) === "rootBoundaryChanged";
|
|
679
680
|
const traceFields = createRootBoundaryTraceFields({
|
|
680
|
-
currentRootLayoutTreePath: currentTopology.kind === "known" ? currentTopology.topology.rootLayoutTreePath : null,
|
|
681
|
+
currentRootLayoutTreePath: renderedRootLayoutChanged ? options.state.visibleSnapshot.rootBoundaryId : currentTopology.kind === "known" ? currentTopology.topology.rootLayoutTreePath : null,
|
|
681
682
|
event: options.event,
|
|
682
|
-
nextRootLayoutTreePath: targetTopology.kind === "known" ? targetTopology.topology.rootLayoutTreePath : null,
|
|
683
|
+
nextRootLayoutTreePath: renderedRootLayoutChanged ? targetSnapshot.rootBoundaryId : targetTopology.kind === "known" ? targetTopology.topology.rootLayoutTreePath : null,
|
|
683
684
|
state: options.state
|
|
684
685
|
});
|
|
685
686
|
if (options.event.token.lane === "prefetch") return {
|
|
@@ -708,6 +709,13 @@ function planFlightResponseArrived(options) {
|
|
|
708
709
|
}
|
|
709
710
|
const commitTraceFields = createAcceptedCacheProofTraceFields(traceFields, acceptedCacheEntryDecision);
|
|
710
711
|
const cacheEntryProposalFields = createCacheEntryProposalFields(acceptedCacheEntryDecision);
|
|
712
|
+
if (renderedRootLayoutChanged) return {
|
|
713
|
+
kind: "hardNavigate",
|
|
714
|
+
reason: "rootBoundaryChanged",
|
|
715
|
+
token: options.event.token,
|
|
716
|
+
trace: createNavigationTrace(NavigationTraceReasonCodes.rootBoundaryChanged, commitTraceFields),
|
|
717
|
+
url: options.event.result.href
|
|
718
|
+
};
|
|
711
719
|
if (targetSnapshot.interception !== null) {
|
|
712
720
|
if (currentTopology.kind === "unknown" || targetTopology.kind === "unknown") return createInterceptionProofRejectedDecision({
|
|
713
721
|
event: options.event,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/server/nitro-request-target-plugin.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Nitro runtime plugin that records each request's raw target before h3 sees
|
|
4
|
+
* it. srvx 0.11 writes the pathname h3 decodes (one with a `%`) back into the
|
|
5
|
+
* Node request's `url`, dropping the authority of an absolute-form target,
|
|
6
|
+
* so vinext's request-based entries read the target this plugin saved.
|
|
7
|
+
*
|
|
8
|
+
* This module runs in Nitro's server bundle, separate from vinext's request
|
|
9
|
+
* handlers, so it imports nothing and shares state through `Symbol.for`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const NITRO_REQUEST_TARGET_KEY = "vinext.nitro.requestTarget";
|
|
12
|
+
type NitroApp = {
|
|
13
|
+
fetch: (request: Request) => Response | Promise<Response>;
|
|
14
|
+
};
|
|
15
|
+
export default function nitroRequestTargetPlugin(nitroApp: NitroApp): void;
|
|
16
|
+
//#endregion
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/server/nitro-request-target-plugin.ts
|
|
2
|
+
/**
|
|
3
|
+
* Nitro runtime plugin that records each request's raw target before h3 sees
|
|
4
|
+
* it. srvx 0.11 writes the pathname h3 decodes (one with a `%`) back into the
|
|
5
|
+
* Node request's `url`, dropping the authority of an absolute-form target,
|
|
6
|
+
* so vinext's request-based entries read the target this plugin saved.
|
|
7
|
+
*
|
|
8
|
+
* This module runs in Nitro's server bundle, separate from vinext's request
|
|
9
|
+
* handlers, so it imports nothing and shares state through `Symbol.for`.
|
|
10
|
+
*/
|
|
11
|
+
const NITRO_REQUEST_TARGET_KEY = "vinext.nitro.requestTarget";
|
|
12
|
+
function nitroRequestTargetPlugin(nitroApp) {
|
|
13
|
+
const appFetch = nitroApp.fetch;
|
|
14
|
+
const key = Symbol.for(NITRO_REQUEST_TARGET_KEY);
|
|
15
|
+
nitroApp.fetch = (request) => {
|
|
16
|
+
const req = request.runtime?.node?.req;
|
|
17
|
+
if (req && !Reflect.has(req, key)) Reflect.set(req, key, req.url);
|
|
18
|
+
return appFetch(request);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { NITRO_REQUEST_TARGET_KEY, nitroRequestTargetPlugin as default };
|
|
@@ -43,7 +43,8 @@ export type RepeatedSlashRedirect = {
|
|
|
43
43
|
* `//` after the scheme trips the same check) and 500s `OPTIONS *`. Routing
|
|
44
44
|
* them would let the request line choose the origin of `request.url` that
|
|
45
45
|
* middleware and handlers see, bypassing the Host trust policy. Request-based
|
|
46
|
-
* entries
|
|
46
|
+
* entries pass a pathname. Where srvx builds `request.url` from the raw target
|
|
47
|
+
* (Nitro's Node server), `isSrvxAbsoluteFormTarget` 404s these instead.
|
|
47
48
|
*
|
|
48
49
|
* @param rawUrl - The raw request target: pathname plus optional `?query`
|
|
49
50
|
*/
|
|
@@ -44,7 +44,8 @@ const REDIRECT_LOCATION_BASE = "http://vinext.invalid";
|
|
|
44
44
|
* `//` after the scheme trips the same check) and 500s `OPTIONS *`. Routing
|
|
45
45
|
* them would let the request line choose the origin of `request.url` that
|
|
46
46
|
* middleware and handlers see, bypassing the Host trust policy. Request-based
|
|
47
|
-
* entries
|
|
47
|
+
* entries pass a pathname. Where srvx builds `request.url` from the raw target
|
|
48
|
+
* (Nitro's Node server), `isSrvxAbsoluteFormTarget` 404s these instead.
|
|
48
49
|
*
|
|
49
50
|
* @param rawUrl - The raw request target: pathname plus optional `?query`
|
|
50
51
|
*/
|
|
@@ -4,6 +4,7 @@ export type AssetCrossOrigin = "" | "anonymous" | "use-credentials";
|
|
|
4
4
|
export type PagesClientAssets = {
|
|
5
5
|
clientEntry?: string;
|
|
6
6
|
appBootstrapPreinitModules?: string[];
|
|
7
|
+
appBootstrapPreloadModules?: string[];
|
|
7
8
|
appRouteManifestModule?: string;
|
|
8
9
|
/** What each App Router client reference loads in the browser, by URL. */
|
|
9
10
|
appClientReferenceChunks?: ClientReferenceChunks;
|
|
@@ -24,6 +24,13 @@ type PagesI18nRequestInfo = {
|
|
|
24
24
|
domainLocale?: DomainLocale;
|
|
25
25
|
redirectUrl?: string;
|
|
26
26
|
};
|
|
27
|
+
/**
|
|
28
|
+
* Mark a locale-detection redirect. Next.js answers it before next.config
|
|
29
|
+
* headers are staged, so it goes out without them. The global symbol crosses
|
|
30
|
+
* the App Router / Pages Router module graph boundary.
|
|
31
|
+
*/
|
|
32
|
+
export declare function markLocaleRedirect(response: Response): Response;
|
|
33
|
+
export declare function isLocaleRedirect(response: Response): boolean;
|
|
27
34
|
/**
|
|
28
35
|
* Return the configured locale that the first pathname segment names, compared
|
|
29
36
|
* case-insensitively (`/EN/about` -> `"en"`), or `undefined` when there is none.
|
|
@@ -8,6 +8,19 @@ function readHeader(headers, name) {
|
|
|
8
8
|
return direct;
|
|
9
9
|
}
|
|
10
10
|
const normalizeHostname = normalizeDomainHostname;
|
|
11
|
+
const LOCALE_REDIRECT = Symbol.for("vinext.pages-locale-redirect");
|
|
12
|
+
/**
|
|
13
|
+
* Mark a locale-detection redirect. Next.js answers it before next.config
|
|
14
|
+
* headers are staged, so it goes out without them. The global symbol crosses
|
|
15
|
+
* the App Router / Pages Router module graph boundary.
|
|
16
|
+
*/
|
|
17
|
+
function markLocaleRedirect(response) {
|
|
18
|
+
if (!isLocaleRedirect(response)) Object.defineProperty(response, LOCALE_REDIRECT, { value: true });
|
|
19
|
+
return response;
|
|
20
|
+
}
|
|
21
|
+
function isLocaleRedirect(response) {
|
|
22
|
+
return LOCALE_REDIRECT in response;
|
|
23
|
+
}
|
|
11
24
|
/**
|
|
12
25
|
* Return the configured locale that the first pathname segment names, compared
|
|
13
26
|
* case-insensitively (`/EN/about` -> `"en"`), or `undefined` when there is none.
|
|
@@ -259,4 +272,4 @@ function resolvePagesI18nRequest(url, i18nConfig, headers, hostname, basePath =
|
|
|
259
272
|
};
|
|
260
273
|
}
|
|
261
274
|
//#endregion
|
|
262
|
-
export { detectDomainLocale, detectLocaleFromAcceptLanguage, detectPathnameLocale, extractLocaleFromUrl, getLocaleRedirect, normalizeDefaultLocalePathname, parseCookieLocaleFromHeader, resolvePagesI18nRequest, shouldRestoreLocaleRootTrailingSlash, stripI18nLocaleForApiRoute };
|
|
275
|
+
export { detectDomainLocale, detectLocaleFromAcceptLanguage, detectPathnameLocale, extractLocaleFromUrl, getLocaleRedirect, isLocaleRedirect, markLocaleRedirect, normalizeDefaultLocalePathname, parseCookieLocaleFromHeader, resolvePagesI18nRequest, shouldRestoreLocaleRootTrailingSlash, stripI18nLocaleForApiRoute };
|
|
@@ -201,10 +201,14 @@ var PagesResponseStream = class extends Writable {
|
|
|
201
201
|
return this;
|
|
202
202
|
}
|
|
203
203
|
json(data) {
|
|
204
|
-
this.resHeaders["content-type"] = "application/json";
|
|
204
|
+
this.resHeaders["content-type"] = "application/json; charset=utf-8";
|
|
205
205
|
this.end(JSON.stringify(data));
|
|
206
206
|
}
|
|
207
207
|
send(data) {
|
|
208
|
+
if (data === null || data === void 0) {
|
|
209
|
+
this.end();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
208
212
|
if (NO_BODY_RESPONSE_STATUSES.has(this.resStatusCode)) {
|
|
209
213
|
this.end();
|
|
210
214
|
return;
|
|
@@ -220,8 +224,8 @@ var PagesResponseStream = class extends Writable {
|
|
|
220
224
|
this.end(data);
|
|
221
225
|
return;
|
|
222
226
|
}
|
|
223
|
-
if (typeof data === "object"
|
|
224
|
-
this.resHeaders["content-type"] = "application/json";
|
|
227
|
+
if (typeof data === "object" || typeof data === "number" || typeof data === "boolean") {
|
|
228
|
+
this.resHeaders["content-type"] = "application/json; charset=utf-8";
|
|
225
229
|
this.end(JSON.stringify(data));
|
|
226
230
|
return;
|
|
227
231
|
}
|
|
@@ -5,7 +5,7 @@ import { NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, VINEXT_CACHE_HEADER,
|
|
|
5
5
|
import { normalizePathnameForRouteMatch } from "../routing/utils.js";
|
|
6
6
|
import { patternToNextFormat } from "../routing/route-validation.js";
|
|
7
7
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
8
|
-
import { extractLocaleFromUrl, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
8
|
+
import { extractLocaleFromUrl, markLocaleRedirect, resolvePagesI18nRequest } from "./pages-i18n.js";
|
|
9
9
|
import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
|
|
10
10
|
import { reportRequestError } from "./instrumentation.js";
|
|
11
11
|
import { logRequestError } from "./request-error-log.js";
|
|
@@ -154,7 +154,7 @@ function createPagesPageHandler(opts) {
|
|
|
154
154
|
const originalRequestPathAndSearch = originalRequestUrl.pathname + originalRequestUrl.search;
|
|
155
155
|
let dataRequestPathname = null;
|
|
156
156
|
let dataRequestSearch = "";
|
|
157
|
-
const initialDataNorm = normalizePagesDataRequest(request, buildId,
|
|
157
|
+
const initialDataNorm = normalizePagesDataRequest(request, buildId, "", hasMiddleware && vinextConfig.trailingSlash);
|
|
158
158
|
if (!isDataReq) {
|
|
159
159
|
if (initialDataNorm.notFoundResponse) return initialDataNorm.notFoundResponse;
|
|
160
160
|
if (initialDataNorm.isDataReq) {
|
|
@@ -195,10 +195,10 @@ function createPagesPageHandler(opts) {
|
|
|
195
195
|
const currentDefaultLocale = i18nConfig ? localeInfo.domainLocale ? localeInfo.domainLocale.defaultLocale : i18nConfig.defaultLocale : void 0;
|
|
196
196
|
const domainLocales = i18nConfig ? i18nConfig.domains : void 0;
|
|
197
197
|
const pageIsrCacheKey = isrCacheKeyForRequest(i18nConfig ? localeInfo.domainLocale ? JSON.stringify([String(localeInfo.domainLocale.domain).toLowerCase(), locale]) : "locale:" + String(locale) : null);
|
|
198
|
-
if (localeInfo.redirectUrl) return new Response(null, {
|
|
198
|
+
if (localeInfo.redirectUrl) return markLocaleRedirect(new Response(null, {
|
|
199
199
|
status: 307,
|
|
200
200
|
headers: { Location: localeInfo.redirectUrl }
|
|
201
|
-
});
|
|
201
|
+
}));
|
|
202
202
|
let match = options && options.__forcedRoute ? {
|
|
203
203
|
route: options.__forcedRoute,
|
|
204
204
|
params: {}
|
|
@@ -15,6 +15,14 @@ type PageRouteMatch = {
|
|
|
15
15
|
dataKind?: PagesRouteDataKind;
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Whether a filesystem route may serve `requestPathname` from the client
|
|
20
|
+
* build directory. Like Next.js, only a public file or a build asset resolves
|
|
21
|
+
* there: the directory also holds build metadata and private artifacts. A
|
|
22
|
+
* direct GET/HEAD reaches this route only for a public file, since servers
|
|
23
|
+
* answer existing build assets before middleware.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isServableFilesystemRoute(requestPathname: string, phase: FilesystemRoutePhase, method: string, publicFiles: ReadonlySet<string>, basePath?: string, assetPathPrefix?: string, pathnameHasBasePath?: boolean): boolean;
|
|
18
26
|
export declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>, publicFiles: ReadonlySet<string>, basePath?: string, assetPathPrefix?: string, pathnameHasBasePath?: boolean): Promise<Response | false>;
|
|
19
27
|
export type MiddlewareResult = {
|
|
20
28
|
continue: boolean;
|
|
@@ -77,6 +85,7 @@ export type PagesPipelineDeps = {
|
|
|
77
85
|
runMiddleware?: ((request: Request, ctx: unknown, opts: {
|
|
78
86
|
isDataRequest: boolean;
|
|
79
87
|
isStaleDataRequest?: boolean;
|
|
88
|
+
restoreLocaleRootTrailingSlash?: boolean;
|
|
80
89
|
}) => Promise<MiddlewareResult>) | null;
|
|
81
90
|
/** Original URL presented to middleware when URL normalization is disabled. */
|
|
82
91
|
middlewareRequest?: Request;
|