vinext 1.0.1 → 1.1.0
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 +72 -1
- package/dist/build/app-page-static-eligibility.d.ts +15 -0
- package/dist/build/app-page-static-eligibility.js +82 -0
- package/dist/build/prerender-paths.js +3 -78
- package/dist/build/prerender.js +33 -8
- package/dist/build/server-manifest.d.ts +7 -0
- package/dist/build/server-manifest.js +11 -1
- package/dist/cache/cache-adapters-virtual.d.ts +7 -1
- package/dist/cache/cache-adapters-virtual.js +3 -3
- package/dist/client/navigation-runtime.d.ts +11 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/config/config-matchers.js +3 -1
- package/dist/config/next-config.d.ts +7 -0
- package/dist/config/next-config.js +18 -3
- package/dist/entries/app-rsc-entry.d.ts +2 -0
- package/dist/entries/app-rsc-entry.js +64 -43
- package/dist/index.d.ts +4 -1
- package/dist/index.js +59 -30
- package/dist/plugins/cache-flight-codec.d.ts +19 -0
- package/dist/plugins/cache-flight-codec.js +68 -0
- package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
- package/dist/plugins/commonjs-esm-facade.js +80 -0
- package/dist/plugins/fonts.d.ts +8 -11
- package/dist/plugins/fonts.js +71 -34
- package/dist/routing/file-matcher.d.ts +1 -0
- package/dist/routing/file-matcher.js +1 -1
- package/dist/server/api-handler.js +17 -7
- package/dist/server/app-browser-entry.js +11 -7
- package/dist/server/app-browser-history-controller.d.ts +2 -0
- package/dist/server/app-browser-history-controller.js +8 -0
- package/dist/server/app-browser-interception-context.js +26 -2
- package/dist/server/app-browser-navigation-controller.d.ts +7 -1
- package/dist/server/app-browser-navigation-controller.js +93 -10
- package/dist/server/app-fallback-renderer.d.ts +2 -0
- package/dist/server/app-fallback-renderer.js +1 -0
- package/dist/server/app-history-state.d.ts +3 -0
- package/dist/server/app-history-state.js +7 -0
- package/dist/server/app-middleware.js +2 -1
- package/dist/server/app-optimistic-routing.js +1 -1
- package/dist/server/app-page-boundary-render.d.ts +5 -0
- package/dist/server/app-page-boundary-render.js +6 -4
- package/dist/server/app-page-cache-finalizer.d.ts +11 -0
- package/dist/server/app-page-cache-finalizer.js +41 -7
- package/dist/server/app-page-cache-render.d.ts +18 -0
- package/dist/server/app-page-cache-render.js +63 -43
- package/dist/server/app-page-cache.d.ts +5 -0
- package/dist/server/app-page-cache.js +27 -7
- package/dist/server/app-page-dispatch.d.ts +9 -1
- package/dist/server/app-page-dispatch.js +47 -30
- package/dist/server/app-page-element-builder.d.ts +7 -0
- package/dist/server/app-page-element-builder.js +27 -42
- package/dist/server/app-page-execution.d.ts +15 -12
- package/dist/server/app-page-execution.js +30 -40
- package/dist/server/app-page-head.d.ts +5 -0
- package/dist/server/app-page-head.js +9 -3
- package/dist/server/app-page-probe.d.ts +0 -109
- package/dist/server/app-page-probe.js +3 -136
- package/dist/server/app-page-render-identity.d.ts +0 -1
- package/dist/server/app-page-render-identity.js +1 -1
- package/dist/server/app-page-render-observation.js +2 -2
- package/dist/server/app-page-render.d.ts +13 -5
- package/dist/server/app-page-render.js +219 -119
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +1 -2
- package/dist/server/app-page-response.d.ts +4 -3
- package/dist/server/app-page-response.js +4 -3
- package/dist/server/app-page-route-wiring.d.ts +17 -5
- package/dist/server/app-page-route-wiring.js +55 -33
- package/dist/server/app-page-rsc-render-status.d.ts +48 -0
- package/dist/server/app-page-rsc-render-status.js +54 -0
- package/dist/server/app-page-stream.d.ts +15 -8
- package/dist/server/app-page-stream.js +6 -0
- package/dist/server/app-request-stage-dispatch.js +1 -1
- package/dist/server/app-request-stage-independent-entry.js +4 -4
- package/dist/server/app-response-header-provenance.d.ts +2 -0
- package/dist/server/app-response-header-provenance.js +10 -4
- package/dist/server/app-route-handler-dispatch.js +1 -1
- package/dist/server/app-router-entry.js +3 -3
- package/dist/server/app-rsc-handler.d.ts +4 -0
- package/dist/server/app-rsc-handler.js +212 -96
- package/dist/server/app-rsc-request-normalization.d.ts +3 -1
- package/dist/server/app-rsc-request-normalization.js +4 -2
- package/dist/server/app-rsc-response-stage.d.ts +1 -1
- package/dist/server/app-rsc-response-stage.js +6 -4
- package/dist/server/app-rsc-route-matching.d.ts +9 -1
- package/dist/server/app-rsc-route-matching.js +36 -19
- package/dist/server/app-server-action-execution.js +1 -1
- package/dist/server/app-ssr-entry.d.ts +3 -2
- package/dist/server/app-ssr-entry.js +6 -4
- package/dist/server/app-ssr-stream.d.ts +6 -1
- package/dist/server/app-ssr-stream.js +34 -15
- package/dist/server/app-worker-stages.d.ts +2 -2
- package/dist/server/app-worker-stages.js +3 -3
- package/dist/server/cacheability-request.js +9 -5
- package/dist/server/config-headers.js +2 -0
- package/dist/server/dev-server.js +12 -2
- package/dist/server/headers.d.ts +14 -0
- package/dist/server/headers.js +15 -1
- package/dist/server/image-optimization.d.ts +16 -0
- package/dist/server/image-optimization.js +26 -6
- package/dist/server/isr-cache.js +2 -2
- package/dist/server/middleware-matcher-pattern.js +8 -4
- package/dist/server/middleware-response-headers.js +2 -0
- package/dist/server/multi-stage.d.ts +8 -0
- package/dist/server/navigation-planner.js +1 -1
- package/dist/server/node-response-signal.d.ts +16 -0
- package/dist/server/node-response-signal.js +26 -0
- package/dist/server/open-redirect.d.ts +47 -0
- package/dist/server/open-redirect.js +74 -1
- package/dist/server/pages-page-handler.js +21 -4
- package/dist/server/pages-request-stage-entry.js +8 -4
- package/dist/server/prod-server.d.ts +8 -7
- package/dist/server/prod-server.js +153 -69
- package/dist/server/request-pipeline.d.ts +20 -8
- package/dist/server/request-pipeline.js +21 -7
- package/dist/server/response-compression.d.ts +43 -0
- package/dist/server/response-compression.js +111 -0
- package/dist/server/seed-cache.js +10 -7
- package/dist/server/socket-error-backstop.d.ts +14 -0
- package/dist/server/socket-error-backstop.js +27 -4
- package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
- package/dist/shims/cache-adapter-instantiate.js +118 -0
- package/dist/shims/cache-callable-runtime.d.ts +2 -40
- package/dist/shims/cache-callable-runtime.js +50 -168
- package/dist/shims/cache-flight-arguments.d.ts +22 -0
- package/dist/shims/cache-flight-arguments.js +56 -0
- package/dist/shims/cache-request-state.d.ts +15 -0
- package/dist/shims/cache-request-state.js +31 -8
- package/dist/shims/cache-runtime.d.ts +6 -19
- package/dist/shims/cache-runtime.js +165 -225
- package/dist/shims/cache.d.ts +2 -2
- package/dist/shims/cache.js +5 -4
- package/dist/shims/cacheability-classification.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.d.ts +7 -0
- package/dist/shims/error-boundary-navigation.js +19 -1
- package/dist/shims/error-boundary.d.ts +10 -10
- package/dist/shims/error-boundary.js +26 -7
- package/dist/shims/fetch-cache.d.ts +6 -0
- package/dist/shims/fetch-cache.js +19 -1
- package/dist/shims/form.js +1 -1
- package/dist/shims/headers.d.ts +13 -0
- package/dist/shims/headers.js +47 -4
- package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
- package/dist/shims/internal/app-page-props-cache-key.js +24 -15
- package/dist/shims/internal/headers-state.d.ts +0 -1
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/navigation-errors.d.ts +6 -0
- package/dist/shims/navigation-errors.js +11 -1
- package/dist/shims/navigation.d.ts +7 -0
- package/dist/shims/navigation.js +14 -4
- package/dist/shims/request-context.d.ts +9 -0
- package/dist/shims/thenable-params.d.ts +4 -0
- package/dist/shims/thenable-params.js +25 -3
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/mdx-scan.d.ts +5 -0
- package/dist/utils/mdx-scan.js +8 -1
- package/dist/utils/regex-safety.d.ts +10 -0
- package/dist/utils/regex-safety.js +70 -18
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { analyzeRegexSafety, regexAtomsMayOverlap } from "../utils/regex-safety.js";
|
|
1
|
+
import { analyzeRegexSafety, mayHaveNamedGroups, regexAtomsMayOverlap } from "../utils/regex-safety.js";
|
|
2
2
|
import { middlewarePathTokensToRegExp, normalizeMiddlewarePathTokens, parseMiddlewarePath } from "./middleware-path-to-regexp.js";
|
|
3
3
|
//#region src/server/middleware-matcher-pattern.ts
|
|
4
4
|
const MATCHER_OBJECT_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -138,8 +138,11 @@ function hasOverlappingSequentialRepetition(pattern) {
|
|
|
138
138
|
}
|
|
139
139
|
return false;
|
|
140
140
|
}
|
|
141
|
-
function unsafeTokenReason(token) {
|
|
142
|
-
const regexSafetyIssue = analyzeRegexSafety(token.pattern, {
|
|
141
|
+
function unsafeTokenReason(token, namedGroups) {
|
|
142
|
+
const regexSafetyIssue = analyzeRegexSafety(token.pattern, {
|
|
143
|
+
ignoreCase: true,
|
|
144
|
+
namedGroups
|
|
145
|
+
});
|
|
143
146
|
if (regexSafetyIssue) {
|
|
144
147
|
if (regexSafetyIssue === "analysis budget exceeded") return `parameter "${token.name}" exceeds the regex analysis budget`;
|
|
145
148
|
return `parameter "${token.name}" contains ${regexSafetyIssue}`;
|
|
@@ -150,9 +153,10 @@ function unsafeTokenReason(token) {
|
|
|
150
153
|
return null;
|
|
151
154
|
}
|
|
152
155
|
function validateTokens(tokens) {
|
|
156
|
+
const namedGroups = tokens.some((token) => typeof token !== "string" && mayHaveNamedGroups(token.pattern));
|
|
153
157
|
for (const token of tokens) {
|
|
154
158
|
if (typeof token === "string") continue;
|
|
155
|
-
const reason = unsafeTokenReason(token);
|
|
159
|
+
const reason = unsafeTokenReason(token, namedGroups);
|
|
156
160
|
if (reason) return reason;
|
|
157
161
|
}
|
|
158
162
|
return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./headers.js";
|
|
1
2
|
//#region src/server/middleware-response-headers.ts
|
|
2
3
|
const ADDITIVE_RESPONSE_HEADER_NAMES = /* @__PURE__ */ new Set(["set-cookie", "vary"]);
|
|
3
4
|
function mergeVaryHeader(target, value) {
|
|
@@ -28,6 +29,7 @@ function mergeMiddlewareResponseHeaders(target, middlewareHeaders) {
|
|
|
28
29
|
if (!middlewareHeaders) return;
|
|
29
30
|
for (const [key, value] of middlewareHeaders) {
|
|
30
31
|
const lowerName = key.toLowerCase();
|
|
32
|
+
if (lowerName === "x-vinext-special-error-status") continue;
|
|
31
33
|
if (lowerName === "link" && !value) continue;
|
|
32
34
|
if (lowerName === "vary") {
|
|
33
35
|
mergeVaryHeader(target, value);
|
|
@@ -21,6 +21,14 @@ export type VinextResponseStageDispatchOptions = {
|
|
|
21
21
|
* opt into completed-response admission and honor core's `no-store` policy.
|
|
22
22
|
*/
|
|
23
23
|
cache: "shared" | "bypass";
|
|
24
|
+
/**
|
|
25
|
+
* Render an App page document whole before its first byte, as Next.js
|
|
26
|
+
* renders a static page, so its streamed metadata is in `<head>`. An adapter
|
|
27
|
+
* sets this for a render no client is waiting on, such as a regeneration or
|
|
28
|
+
* a deployment warm-up. It is not a cache selector: a transport must not
|
|
29
|
+
* partition its lookup or stored entry by it.
|
|
30
|
+
*/
|
|
31
|
+
renderWholeDocument?: boolean;
|
|
24
32
|
/**
|
|
25
33
|
* Query-free identity of a shared App page GET/HEAD dispatch, supplied only
|
|
26
34
|
* to adapters that declare `responseStageCacheIdentity: "query-free"` and
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { splitPathnameForRouteMatch } from "../routing/utils.js";
|
|
2
2
|
import { stripBasePath } from "../utils/base-path.js";
|
|
3
|
+
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
3
4
|
import { AppElementsWire, compareAppElementsSlotIds } from "./app-elements-wire.js";
|
|
4
5
|
import "./app-elements.js";
|
|
5
|
-
import { matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments } from "../routing/route-pattern.js";
|
|
6
6
|
import { resolveHardNavigationTargetFromRscResponse, resolveRscCompatibilityNavigationDecision } from "./app-rsc-cache-busting.js";
|
|
7
7
|
import { resolveRscRedirectLifecycleHop, resolveStreamedRscRedirectLifecycleHop } from "./app-browser-rsc-redirect.js";
|
|
8
8
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Writable } from "node:stream";
|
|
2
|
+
//#region src/server/node-response-signal.d.ts
|
|
3
|
+
export declare class ResponseAborted extends Error {
|
|
4
|
+
readonly name = "ResponseAborted";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Creates an AbortSignal tied to the closing of a ServerResponse.
|
|
8
|
+
*
|
|
9
|
+
* The incoming request cannot be used: once its body has been fully read the
|
|
10
|
+
* readable side is done, so a later client disconnect fires nothing on it
|
|
11
|
+
* (and its `close` event also fires on normal requests). If `finish` fires
|
|
12
|
+
* first, `res.end()` completed and the following `close` is our own teardown;
|
|
13
|
+
* if `close` fires first, the client disconnected before we finished.
|
|
14
|
+
*/
|
|
15
|
+
export declare function signalFromNodeResponse(response: Writable): AbortSignal;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/server/node-response-signal.ts
|
|
2
|
+
const ResponseAbortedName = "ResponseAborted";
|
|
3
|
+
var ResponseAborted = class extends Error {
|
|
4
|
+
name = ResponseAbortedName;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Creates an AbortSignal tied to the closing of a ServerResponse.
|
|
8
|
+
*
|
|
9
|
+
* The incoming request cannot be used: once its body has been fully read the
|
|
10
|
+
* readable side is done, so a later client disconnect fires nothing on it
|
|
11
|
+
* (and its `close` event also fires on normal requests). If `finish` fires
|
|
12
|
+
* first, `res.end()` completed and the following `close` is our own teardown;
|
|
13
|
+
* if `close` fires first, the client disconnected before we finished.
|
|
14
|
+
*/
|
|
15
|
+
function signalFromNodeResponse(response) {
|
|
16
|
+
const { errored, destroyed } = response;
|
|
17
|
+
if (errored || destroyed) return AbortSignal.abort(errored ?? new ResponseAborted());
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
response.once("close", () => {
|
|
20
|
+
if (response.writableFinished) return;
|
|
21
|
+
controller.abort(new ResponseAborted());
|
|
22
|
+
});
|
|
23
|
+
return controller.signal;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ResponseAborted, signalFromNodeResponse };
|
|
@@ -8,4 +8,51 @@
|
|
|
8
8
|
* to resolve a `Location` containing the pathname as protocol-relative.
|
|
9
9
|
*/
|
|
10
10
|
export declare function isOpenRedirectShaped(rawPathname: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* What a request entry answers for a raw path containing a backslash or a
|
|
13
|
+
* repeated slash: Next.js's 308, or a 404 when the collapsed path would still
|
|
14
|
+
* be open-redirect shaped.
|
|
15
|
+
*/
|
|
16
|
+
export type RepeatedSlashRedirect = {
|
|
17
|
+
status: 308;
|
|
18
|
+
location: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: 404;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Returns Next.js's redirect for a raw request path containing a backslash or
|
|
24
|
+
* a repeated slash, or `null` when the path needs no change.
|
|
25
|
+
*
|
|
26
|
+
* Ported from Next.js's request entry (`base-server.ts` / `resolve-routes.ts`,
|
|
27
|
+
* via `normalizeRepeatedSlashes` in `shared/lib/utils.ts`): backslashes in the
|
|
28
|
+
* path become `/`, runs of slashes collapse to one and the query is kept.
|
|
29
|
+
* `next start` then builds the Location with `url.format(parseUrl(...))` in
|
|
30
|
+
* `router-server.ts`, so the result is serialized through a WHATWG URL here
|
|
31
|
+
* too (`/a/%2e%2e//b` → `/b`, `?q="x"` → `?q=%22x%22`, as observed). It runs
|
|
32
|
+
* before basePath handling, so `//` → `/`, `/docs//` → `/docs/` and
|
|
33
|
+
* `//evil.com` → `/evil.com`. Percent-encoded `%2F`/`%5C` are not touched and
|
|
34
|
+
* keep falling through to `isOpenRedirectShaped` (404), as in Next.js.
|
|
35
|
+
*
|
|
36
|
+
* The Location is always same-origin. Absolute-form request targets
|
|
37
|
+
* (`GET http://host//x`) are left alone. A collapsed path that still starts
|
|
38
|
+
* with an encoded slash or backslash (`//%2Fevil.com`, `/.//%2Fevil.com`) is
|
|
39
|
+
* never echoed into a Location or routed: it gets a 404 instead.
|
|
40
|
+
*
|
|
41
|
+
* @param rawUrl - The raw request target: pathname plus optional `?query`
|
|
42
|
+
*/
|
|
43
|
+
export declare function getRepeatedSlashRedirect(rawUrl: string): RepeatedSlashRedirect | null;
|
|
44
|
+
/**
|
|
45
|
+
* Build the Response for a raw request path containing a backslash or a
|
|
46
|
+
* repeated slash. See `getRepeatedSlashRedirect`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function repeatedSlashRedirectResponse(rawUrl: string): Response | null;
|
|
49
|
+
/**
|
|
50
|
+
* Node `ServerResponse` counterpart of `repeatedSlashRedirectResponse`, for
|
|
51
|
+
* entries that see the raw request target (dev and `vinext start`). Returns
|
|
52
|
+
* true when a response was sent.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sendRepeatedSlashRedirect(rawUrl: string, res: {
|
|
55
|
+
writeHead(status: number, headers?: Record<string, string>): unknown;
|
|
56
|
+
end(body: string): unknown;
|
|
57
|
+
}): boolean;
|
|
11
58
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { notFoundResponse } from "./http-error-responses.js";
|
|
1
2
|
//#region src/server/open-redirect.ts
|
|
2
3
|
/**
|
|
3
4
|
* Returns true if a request pathname looks like a protocol-relative open
|
|
@@ -17,5 +18,77 @@ function isOpenRedirectShaped(rawPathname) {
|
|
|
17
18
|
}
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
21
|
+
const REPEATED_SLASH_OR_BACKSLASH_RE = /\\|\/\//;
|
|
22
|
+
const REDIRECT_LOCATION_BASE = "http://vinext.invalid";
|
|
23
|
+
/**
|
|
24
|
+
* Returns Next.js's redirect for a raw request path containing a backslash or
|
|
25
|
+
* a repeated slash, or `null` when the path needs no change.
|
|
26
|
+
*
|
|
27
|
+
* Ported from Next.js's request entry (`base-server.ts` / `resolve-routes.ts`,
|
|
28
|
+
* via `normalizeRepeatedSlashes` in `shared/lib/utils.ts`): backslashes in the
|
|
29
|
+
* path become `/`, runs of slashes collapse to one and the query is kept.
|
|
30
|
+
* `next start` then builds the Location with `url.format(parseUrl(...))` in
|
|
31
|
+
* `router-server.ts`, so the result is serialized through a WHATWG URL here
|
|
32
|
+
* too (`/a/%2e%2e//b` → `/b`, `?q="x"` → `?q=%22x%22`, as observed). It runs
|
|
33
|
+
* before basePath handling, so `//` → `/`, `/docs//` → `/docs/` and
|
|
34
|
+
* `//evil.com` → `/evil.com`. Percent-encoded `%2F`/`%5C` are not touched and
|
|
35
|
+
* keep falling through to `isOpenRedirectShaped` (404), as in Next.js.
|
|
36
|
+
*
|
|
37
|
+
* The Location is always same-origin. Absolute-form request targets
|
|
38
|
+
* (`GET http://host//x`) are left alone. A collapsed path that still starts
|
|
39
|
+
* with an encoded slash or backslash (`//%2Fevil.com`, `/.//%2Fevil.com`) is
|
|
40
|
+
* never echoed into a Location or routed: it gets a 404 instead.
|
|
41
|
+
*
|
|
42
|
+
* @param rawUrl - The raw request target: pathname plus optional `?query`
|
|
43
|
+
*/
|
|
44
|
+
function getRepeatedSlashRedirect(rawUrl) {
|
|
45
|
+
if (!rawUrl.startsWith("/")) return null;
|
|
46
|
+
const urlParts = rawUrl.split("?");
|
|
47
|
+
const pathname = urlParts[0];
|
|
48
|
+
if (!REPEATED_SLASH_OR_BACKSLASH_RE.test(pathname)) return null;
|
|
49
|
+
const cleanUrl = pathname.replaceAll("\\", "/").replace(/\/\/+/g, "/") + (urlParts[1] ? `?${urlParts.slice(1).join("?")}` : "");
|
|
50
|
+
const parsed = new URL(REDIRECT_LOCATION_BASE + cleanUrl);
|
|
51
|
+
const location = parsed.pathname + parsed.search + parsed.hash;
|
|
52
|
+
return isOpenRedirectShaped(location) ? { status: 404 } : {
|
|
53
|
+
status: 308,
|
|
54
|
+
location
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function repeatedSlashRedirectHeaders(location) {
|
|
58
|
+
return {
|
|
59
|
+
Location: location,
|
|
60
|
+
Refresh: `0;url=${location}`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the Response for a raw request path containing a backslash or a
|
|
65
|
+
* repeated slash. See `getRepeatedSlashRedirect`.
|
|
66
|
+
*/
|
|
67
|
+
function repeatedSlashRedirectResponse(rawUrl) {
|
|
68
|
+
const redirect = getRepeatedSlashRedirect(rawUrl);
|
|
69
|
+
if (!redirect) return null;
|
|
70
|
+
if (redirect.status === 404) return notFoundResponse();
|
|
71
|
+
return new Response(redirect.location, {
|
|
72
|
+
status: 308,
|
|
73
|
+
headers: repeatedSlashRedirectHeaders(redirect.location)
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Node `ServerResponse` counterpart of `repeatedSlashRedirectResponse`, for
|
|
78
|
+
* entries that see the raw request target (dev and `vinext start`). Returns
|
|
79
|
+
* true when a response was sent.
|
|
80
|
+
*/
|
|
81
|
+
function sendRepeatedSlashRedirect(rawUrl, res) {
|
|
82
|
+
const redirect = getRepeatedSlashRedirect(rawUrl);
|
|
83
|
+
if (!redirect) return false;
|
|
84
|
+
if (redirect.status === 404) {
|
|
85
|
+
res.writeHead(404);
|
|
86
|
+
res.end("This page could not be found");
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
res.writeHead(308, repeatedSlashRedirectHeaders(redirect.location));
|
|
90
|
+
res.end(redirect.location);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
20
93
|
//#endregion
|
|
21
|
-
export { isOpenRedirectShaped };
|
|
94
|
+
export { getRepeatedSlashRedirect, isOpenRedirectShaped, repeatedSlashRedirectResponse, sendRepeatedSlashRedirect };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { preserveFullyBufferedBodyMetadata } from "./fully-buffered-response.js";
|
|
2
2
|
import { closeAfterResponse, closeAfterResponseWithBody, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
|
|
3
3
|
import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
|
|
4
|
+
import { normalizePathnameForRouteMatch } from "../routing/utils.js";
|
|
4
5
|
import { patternToNextFormat } from "../routing/route-validation.js";
|
|
5
6
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
6
7
|
import { NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, VINEXT_CACHE_HEADER, VINEXT_REVALIDATED_CACHE_TAG_HEADER } from "./headers.js";
|
|
@@ -187,7 +188,7 @@ function createPagesPageHandler(opts) {
|
|
|
187
188
|
redirectUrl: void 0
|
|
188
189
|
};
|
|
189
190
|
const locale = localeInfo.locale;
|
|
190
|
-
|
|
191
|
+
let routeUrl = localeInfo.url;
|
|
191
192
|
const currentDefaultLocale = i18nConfig ? localeInfo.domainLocale ? localeInfo.domainLocale.defaultLocale : i18nConfig.defaultLocale : void 0;
|
|
192
193
|
const domainLocales = i18nConfig ? i18nConfig.domains : void 0;
|
|
193
194
|
const pageIsrCacheKey = isrCacheKeyForRequest(i18nConfig ? localeInfo.domainLocale ? JSON.stringify([String(localeInfo.domainLocale.domain).toLowerCase(), locale]) : "locale:" + String(locale) : null);
|
|
@@ -216,6 +217,22 @@ function createPagesPageHandler(opts) {
|
|
|
216
217
|
renderAsPath = routeUrl;
|
|
217
218
|
} else return buildDefaultPagesNotFoundResponse();
|
|
218
219
|
}
|
|
220
|
+
const routePathname = routeUrl.split("?")[0];
|
|
221
|
+
const cachePathname = normalizePathnameForRouteMatch(routePathname);
|
|
222
|
+
let cacheIdentityDiverges = false;
|
|
223
|
+
if (!isRouteMissErrorRender && options?.__forcedRoute === void 0 && cachePathname !== routePathname) {
|
|
224
|
+
const cachePathRoute = matchRoute(cachePathname, pageRoutes)?.route;
|
|
225
|
+
if (cachePathRoute !== match.route) {
|
|
226
|
+
const cachePathModule = cachePathRoute?.module;
|
|
227
|
+
if (match.route.isDynamic && typeof match.route.module.getStaticProps === "function" && cachePathRoute && !cachePathRoute.isDynamic && typeof cachePathModule?.getStaticProps === "function" && typeof cachePathModule.getServerSideProps !== "function" && getPagesPreviewState(request.headers.get("cookie"), { isOnDemandRevalidate: isOnDemandRevalidateRequest(request.headers.get("x-prerender-revalidate")) }).data === false) {
|
|
228
|
+
match = {
|
|
229
|
+
route: cachePathRoute,
|
|
230
|
+
params: {}
|
|
231
|
+
};
|
|
232
|
+
routeUrl = cachePathname + routeUrl.slice(routePathname.length);
|
|
233
|
+
} else cacheIdentityDiverges = true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
219
236
|
const { route, params } = match;
|
|
220
237
|
const pageModule = traceFindPageComponents(route.pattern, () => route.module);
|
|
221
238
|
const isStaticPropsRoute = typeof pageModule.getStaticProps === "function";
|
|
@@ -234,10 +251,10 @@ function createPagesPageHandler(opts) {
|
|
|
234
251
|
});
|
|
235
252
|
else recordRouteCacheability({ cacheable: false });
|
|
236
253
|
}
|
|
237
|
-
const routeIsrGet = isCacheabilityProbe ? async () => null : isrGet;
|
|
238
|
-
const routeIsrSet = isCacheabilityProbe ? async () => {} : isrSet;
|
|
254
|
+
const routeIsrGet = isCacheabilityProbe || cacheIdentityDiverges ? async () => null : isrGet;
|
|
255
|
+
const routeIsrSet = isCacheabilityProbe || cacheIdentityDiverges ? async () => {} : isrSet;
|
|
239
256
|
const isStaticPropsRender = isStaticPropsRoute && typeof pageModule.getServerSideProps !== "function";
|
|
240
|
-
if (!options?.__skipOnDemandCoalesce && !options?.__forcedRoute && isStaticPropsRoute && isOnDemandRevalidateRequest(request.headers.get("x-prerender-revalidate"))) {
|
|
257
|
+
if (!options?.__skipOnDemandCoalesce && !options?.__forcedRoute && !cacheIdentityDiverges && isStaticPropsRoute && isOnDemandRevalidateRequest(request.headers.get("x-prerender-revalidate"))) {
|
|
241
258
|
const cacheKey = pageIsrCacheKey("pages", routeUrl.split("?")[0]);
|
|
242
259
|
const snapshot = await coalesceOnDemandRevalidation(cacheKey, async () => {
|
|
243
260
|
const response = await renderPage(request, url, manifest, middlewareHeaders, {
|
|
@@ -6,8 +6,7 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
|
6
6
|
import { runWithExecutionContext } from "../shims/request-context.js";
|
|
7
7
|
import { VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "./headers.js";
|
|
8
8
|
import { notFoundStaticAssetResponse } from "./http-error-responses.js";
|
|
9
|
-
import {
|
|
10
|
-
import { attachRequestCfMetadata, cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders } from "./request-pipeline.js";
|
|
9
|
+
import { attachRequestCfMetadata, cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl } from "./request-pipeline.js";
|
|
11
10
|
import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
|
|
12
11
|
import { getExplicitCdnCacheAdapter } from "../shims/cdn-cache-state.js";
|
|
13
12
|
import { CACHEABILITY_REQUEST_STATE } from "../shims/cacheability-classification.js";
|
|
@@ -148,7 +147,8 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
148
147
|
requestHost: url.host,
|
|
149
148
|
stagedHeaders: null
|
|
150
149
|
}, { cache: "bypass" }, env, ctx);
|
|
151
|
-
|
|
150
|
+
const protocolRelativeGuard = guardProtocolRelativeUrl(pathname, url.search);
|
|
151
|
+
if (protocolRelativeGuard) return protocolRelativeGuard;
|
|
152
152
|
try {
|
|
153
153
|
normalizePathnameForRouteMatchStrict(pathname);
|
|
154
154
|
} catch {
|
|
@@ -166,6 +166,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
const middlewareRequest = request;
|
|
169
|
+
const originalRenderUrl = pathname + new URL(request.url).search;
|
|
169
170
|
const dataNorm = normalizeDataRequest(request);
|
|
170
171
|
if (dataNorm.notFoundResponse && !vinextConfig?.skipProxyUrlNormalize) return dataNorm.notFoundResponse;
|
|
171
172
|
const isDataReq = dataNorm.isDataReq;
|
|
@@ -229,7 +230,10 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
|
|
|
229
230
|
kind: "pages-page",
|
|
230
231
|
protocolVersion: 4,
|
|
231
232
|
requestHost: new URL(req.url).host,
|
|
232
|
-
renderOptions:
|
|
233
|
+
renderOptions: isDataReq ? {
|
|
234
|
+
...options,
|
|
235
|
+
originalUrl: originalRenderUrl
|
|
236
|
+
} : options ?? null,
|
|
233
237
|
resolvedUrl,
|
|
234
238
|
stagedHeaders: isSharedStaticPage ? null : [...stagedHeaders ?? new Headers()]
|
|
235
239
|
});
|
|
@@ -2,6 +2,7 @@ import { NextI18nConfig } from "../config/next-config.js";
|
|
|
2
2
|
import { negotiateEncoding } from "./accept-encoding.js";
|
|
3
3
|
import { StaticFileCache } from "./static-file-cache.js";
|
|
4
4
|
import { resolveRequestHost, trustProxy, trustedHosts } from "./proxy-trust.js";
|
|
5
|
+
import { COMPRESS_THRESHOLD, isCompressibleContentType } from "./response-compression.js";
|
|
5
6
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
6
7
|
//#region src/server/prod-server.d.ts
|
|
7
8
|
/**
|
|
@@ -68,10 +69,6 @@ export type ProdServerOptions = {
|
|
|
68
69
|
/** Suppress the startup log for internal child-process servers. */
|
|
69
70
|
silent?: boolean;
|
|
70
71
|
};
|
|
71
|
-
/** Content types that benefit from compression. */
|
|
72
|
-
declare const COMPRESSIBLE_TYPES: Set<string>;
|
|
73
|
-
/** Minimum size threshold for compression (in bytes). Below this, compression overhead isn't worth it. */
|
|
74
|
-
declare const COMPRESS_THRESHOLD = 1024;
|
|
75
72
|
/**
|
|
76
73
|
* Merge middleware headers and a Web Response's headers into a single
|
|
77
74
|
* record suitable for Node.js `res.writeHead()`. Uses `getSetCookie()`
|
|
@@ -104,15 +101,19 @@ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body:
|
|
|
104
101
|
*
|
|
105
102
|
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
106
103
|
* unlike the old sync existsSync/statSync approach).
|
|
104
|
+
*
|
|
105
|
+
* `extraHeaders` go on every response for the file. `representationHeaders` go
|
|
106
|
+
* only on responses that carry the file's bytes (200 and 206), never on a 304,
|
|
107
|
+
* 412, 416 or 405.
|
|
107
108
|
*/
|
|
108
|
-
declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>, statusCode?: number): Promise<boolean>;
|
|
109
|
+
declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>, statusCode?: number, representationHeaders?: Record<string, string>): Promise<boolean>;
|
|
109
110
|
/**
|
|
110
111
|
* Convert a Node.js IncomingMessage to a Web Request object.
|
|
111
112
|
*
|
|
112
113
|
* When `urlOverride` is provided, it is used as the path + query string
|
|
113
114
|
* instead of `req.url`.
|
|
114
115
|
*/
|
|
115
|
-
declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string, i18nConfig?: NextI18nConfig | null, authorizeOnDemandRevalidate?: (headerValue: string | null) => boolean): Request;
|
|
116
|
+
declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string, prerenderSecret?: string, i18nConfig?: NextI18nConfig | null, authorizeOnDemandRevalidate?: (headerValue: string | null) => boolean, signal?: AbortSignal): Request;
|
|
116
117
|
/**
|
|
117
118
|
* Stream a Web Response back to a Node.js ServerResponse.
|
|
118
119
|
* Supports streaming compression for SSR responses.
|
|
@@ -155,4 +156,4 @@ export declare function resolveAppRouterPrerenderSeeder(entryModule: unknown): A
|
|
|
155
156
|
*/
|
|
156
157
|
export declare function resolveAppRouterAssetPath(pathname: string, assetPathPrefix: string, assetPrefix: string): string | null;
|
|
157
158
|
//#endregion
|
|
158
|
-
export {
|
|
159
|
+
export { COMPRESS_THRESHOLD, isCompressibleContentType, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveRequestHost as resolveHost, sendCompressed, sendWebResponse, trustProxy, trustedHosts, tryServeStatic, waitForNodeResponseCompletion };
|