@opennextjs/aws 4.1.13 → 4.1.15
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/dist/adapters/cache.d.ts +21 -1
- package/dist/adapters/cache.js +138 -30
- package/dist/adapters/composable-cache.d.ts +20 -0
- package/dist/adapters/composable-cache.js +33 -8
- package/dist/adapters/config/index.js +14 -4
- package/dist/adapters/config/util.d.ts +19 -0
- package/dist/adapters/config/util.js +21 -0
- package/dist/adapters/edge-adapter.js +2 -11
- package/dist/adapters/image-optimization-adapter.js +8 -2
- package/dist/build/createAssets.d.ts +3 -0
- package/dist/build/createAssets.js +11 -8
- package/dist/build/createImageOptimizationBundle.js +0 -1
- package/dist/build/helper.js +1 -1
- package/dist/build/installDeps.js +6 -7
- package/dist/build/patch/patches/patchNextServer.d.ts +25 -0
- package/dist/build/patch/patches/patchNextServer.js +48 -0
- package/dist/core/requestHandler.js +5 -0
- package/dist/core/resolve.d.ts +8 -1
- package/dist/core/revalidate.d.ts +68 -0
- package/dist/core/revalidate.js +143 -0
- package/dist/core/routing/cacheInterceptor.js +27 -4
- package/dist/core/routing/matcher.js +16 -4
- package/dist/core/routing/middleware.d.ts +4 -0
- package/dist/core/routing/middleware.js +19 -0
- package/dist/core/routing/routeParamPlaceholder.d.ts +14 -0
- package/dist/core/routing/routeParamPlaceholder.js +42 -0
- package/dist/core/routingHandler.js +8 -2
- package/dist/core/util.js +5 -0
- package/dist/http/framing.d.ts +35 -0
- package/dist/http/framing.js +181 -0
- package/dist/http/util.d.ts +11 -0
- package/dist/http/util.js +26 -0
- package/dist/overrides/cdnInvalidation/cloudfront.js +2 -1
- package/dist/overrides/proxyExternalRequest/fetch.js +5 -1
- package/dist/overrides/proxyExternalRequest/node.js +5 -0
- package/dist/overrides/wrappers/express-dev.js +3 -5
- package/dist/overrides/wrappers/node.js +2 -3
- package/dist/plugins/edge.js +3 -2
- package/dist/types/cache.d.ts +12 -4
- package/dist/types/global.d.ts +7 -0
- package/dist/types/open-next.d.ts +4 -1
- package/dist/types/overrides.d.ts +47 -0
- package/dist/utils/cache.d.ts +14 -0
- package/dist/utils/cache.js +21 -1
- package/package.json +3 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dynamicRouteMatcher } from "./routeMatcher";
|
|
2
|
+
// The placeholders of a route pattern: `[slug]`, `[...slug]` or `[[...slug]]`
|
|
3
|
+
const PLACEHOLDER_REGEX = /\[\[\.\.\.[^\]]+\]\]|\[(?:\.\.\.)?[^\]]+\]/g;
|
|
4
|
+
// A placeholder starts with `[`, which is either literal or percent encoded in the pathname
|
|
5
|
+
const OPENING_BRACKET_REGEX = /\[|%5b/i;
|
|
6
|
+
/**
|
|
7
|
+
* Returns whether a segment of the pathname is the placeholder of a matched dynamic App Router page.
|
|
8
|
+
*
|
|
9
|
+
* Next.js versions before 16.4.5 treat a param whose value is its placeholder (e.g. `[slug]` for `[slug]`) as an
|
|
10
|
+
* unresolved fallback param. Such values are not legitimate and must not reach Next.js.
|
|
11
|
+
*
|
|
12
|
+
* Segments are decoded like Next.js decodes param values (catch-all values are split on `/` first).
|
|
13
|
+
* Segments with a malformed percent encoding never match.
|
|
14
|
+
* Pathnames without an opening bracket return early, without matching the dynamic routes.
|
|
15
|
+
*
|
|
16
|
+
* @param pathname The internal pathname, which may include the basePath, a locale prefix and a trailing slash
|
|
17
|
+
* @returns Whether a segment of the pathname is a placeholder of a matched dynamic App Router page
|
|
18
|
+
*/
|
|
19
|
+
export function hasRouteParamPlaceholder(pathname) {
|
|
20
|
+
if (!OPENING_BRACKET_REGEX.test(pathname)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const placeholders = new Set(dynamicRouteMatcher(pathname)
|
|
24
|
+
.filter(({ type }) => type === "app")
|
|
25
|
+
.flatMap(({ route }) => route.match(PLACEHOLDER_REGEX) ?? []));
|
|
26
|
+
return (placeholders.size > 0 &&
|
|
27
|
+
pathname.split("/").some((segment) => placeholders.has(safeDecode(segment))));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decodes a URI component without throwing.
|
|
31
|
+
*
|
|
32
|
+
* @param value The encoded value
|
|
33
|
+
* @returns The decoded value, or an empty string when the encoding is malformed
|
|
34
|
+
*/
|
|
35
|
+
function safeDecode(value) {
|
|
36
|
+
try {
|
|
37
|
+
return decodeURIComponent(value);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -6,6 +6,7 @@ import { detectLocale } from "./routing/i18n";
|
|
|
6
6
|
import { fixDataPage, getNextConfigHeaders, handleBeforeFilesRewrites, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
|
|
7
7
|
import { handleMiddleware } from "./routing/middleware";
|
|
8
8
|
import { dynamicRouteMatcher, staticRouteMatcher, } from "./routing/routeMatcher";
|
|
9
|
+
import { hasRouteParamPlaceholder } from "./routing/routeParamPlaceholder";
|
|
9
10
|
import { constructNextUrl, normalizeLocationHeader } from "./routing/util";
|
|
10
11
|
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
|
|
11
12
|
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
|
|
@@ -159,7 +160,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
159
160
|
const isRouteFoundBeforeAllRewrites = isStaticRoute || isDynamicRoute || isExternalRewrite;
|
|
160
161
|
// Resolve against the final internal pathname after every rewrite phase.
|
|
161
162
|
// These ordered matches are also the authoritative cache owner candidates.
|
|
162
|
-
|
|
163
|
+
let resolvedRoutes = [
|
|
163
164
|
...staticRouteMatcher(eventOrResult.rawPath),
|
|
164
165
|
...dynamicRouteMatcher(eventOrResult.rawPath),
|
|
165
166
|
];
|
|
@@ -169,7 +170,12 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
169
170
|
isNextImageRoute ||
|
|
170
171
|
// We need to check again once all rewrites have been applied
|
|
171
172
|
staticRouteMatcher(eventOrResult.rawPath).length > 0 ||
|
|
172
|
-
dynamicRouteMatcher(eventOrResult.rawPath).length > 0)
|
|
173
|
+
dynamicRouteMatcher(eventOrResult.rawPath).length > 0) ||
|
|
174
|
+
// Next.js versions before 16.4.5 treat a param whose value is its placeholder (e.g. `[slug]`) as unresolved.
|
|
175
|
+
// Next.js receives both the rawPath and the URL pathname, which is normalized (e.g. dot segments).
|
|
176
|
+
(!isExternalRewrite &&
|
|
177
|
+
[eventOrResult.rawPath, new URL(eventOrResult.url).pathname].some(hasRouteParamPlaceholder))) {
|
|
178
|
+
resolvedRoutes = [];
|
|
173
179
|
eventOrResult = {
|
|
174
180
|
...eventOrResult,
|
|
175
181
|
rawPath: "/404",
|
package/dist/core/util.js
CHANGED
|
@@ -3,6 +3,7 @@ import { AppPathsManifestKeys, NextConfig, RoutesManifest, } from "../adapters/c
|
|
|
3
3
|
import NextServer from "next/dist/server/next-server.js";
|
|
4
4
|
import { debug, error } from "../adapters/logger.js";
|
|
5
5
|
import { applyOverride as applyNextjsRequireHooksOverride, overrideHooks as overrideNextjsRequireHooks, } from "./require-hooks.js";
|
|
6
|
+
import { createRevalidate } from "./revalidate.js";
|
|
6
7
|
// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled Reac
|
|
7
8
|
// See https://opennext.js.org/aws/v2/advanced/workaround#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
|
|
8
9
|
// Step 1: Need to override the require hooks for React before Next.js server
|
|
@@ -67,6 +68,10 @@ const nextServer = new NextServer.default({
|
|
|
67
68
|
dev: false,
|
|
68
69
|
dir: __dirname,
|
|
69
70
|
});
|
|
71
|
+
// `res.revalidate()` in Pages Router API routes (Next 15.4+)
|
|
72
|
+
if (typeof nextServer.revalidate === "function") {
|
|
73
|
+
nextServer.revalidate = createRevalidate(nextServer.revalidate.bind(nextServer));
|
|
74
|
+
}
|
|
70
75
|
let routesLoaded = false;
|
|
71
76
|
globalThis.__next_route_preloader = async (stage) => {
|
|
72
77
|
if (routesLoaded) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ServerResponse } from "node:http";
|
|
2
|
+
import { type Writable } from "node:stream";
|
|
3
|
+
/**
|
|
4
|
+
* Largest `content-length` that is verified by buffering the body.
|
|
5
|
+
*
|
|
6
|
+
* It matches the largest response compressed by CloudFront, which only compresses responses with a `content-length`.
|
|
7
|
+
* Larger responses are streamed without `content-length`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_VERIFIED_CONTENT_LENGTH = 10000000;
|
|
10
|
+
/**
|
|
11
|
+
* Writes the head of a Node.js `ServerResponse` and returns the stream to write the body to.
|
|
12
|
+
*
|
|
13
|
+
* The framing headers set by the app may not match the body that is actually written (e.g. a `fetch()` response
|
|
14
|
+
* body is decoded but keeps the upstream `content-length`). The bytes past a stale `content-length` would stay on
|
|
15
|
+
* the keep-alive connection and be read as the next response.
|
|
16
|
+
*
|
|
17
|
+
* - `transfer-encoding` is always removed, Node.js picks the transfer coding.
|
|
18
|
+
* - A `content-length` that is not a valid length is removed.
|
|
19
|
+
* - `content-length` is kept for HEAD requests and 304 responses, Node.js does not write a body for them.
|
|
20
|
+
* - Otherwise `content-length` is only kept when the body has exactly the declared length. The body is buffered
|
|
21
|
+
* until this is known, and the head is written then, so the first byte is only sent once the whole body is
|
|
22
|
+
* received. When the length doesn't match, or is larger than `MAX_VERIFIED_CONTENT_LENGTH`, `content-length` is
|
|
23
|
+
* removed and Node.js frames the body itself (chunked on HTTP/1.1).
|
|
24
|
+
*
|
|
25
|
+
* The status code and the headers are always validated synchronously, even when the head is written later.
|
|
26
|
+
*
|
|
27
|
+
* @param res The Node.js response.
|
|
28
|
+
* @param method The request method.
|
|
29
|
+
* @param statusCode The response status code.
|
|
30
|
+
* @param headers The response headers, keys are matched case-insensitively.
|
|
31
|
+
* @returns The stream to write the body to.
|
|
32
|
+
* @throws When the status code or a header is invalid, or the headers were already sent (same errors as
|
|
33
|
+
* `res.writeHead()`).
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeResponseHead(res: ServerResponse, method: string | undefined, statusCode: number, headers: Record<string, string>): Writable;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
|
+
import { error } from "../adapters/logger";
|
|
3
|
+
/**
|
|
4
|
+
* Largest `content-length` that is verified by buffering the body.
|
|
5
|
+
*
|
|
6
|
+
* It matches the largest response compressed by CloudFront, which only compresses responses with a `content-length`.
|
|
7
|
+
* Larger responses are streamed without `content-length`.
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_VERIFIED_CONTENT_LENGTH = 10_000_000;
|
|
10
|
+
/**
|
|
11
|
+
* Writes the head of a Node.js `ServerResponse` and returns the stream to write the body to.
|
|
12
|
+
*
|
|
13
|
+
* The framing headers set by the app may not match the body that is actually written (e.g. a `fetch()` response
|
|
14
|
+
* body is decoded but keeps the upstream `content-length`). The bytes past a stale `content-length` would stay on
|
|
15
|
+
* the keep-alive connection and be read as the next response.
|
|
16
|
+
*
|
|
17
|
+
* - `transfer-encoding` is always removed, Node.js picks the transfer coding.
|
|
18
|
+
* - A `content-length` that is not a valid length is removed.
|
|
19
|
+
* - `content-length` is kept for HEAD requests and 304 responses, Node.js does not write a body for them.
|
|
20
|
+
* - Otherwise `content-length` is only kept when the body has exactly the declared length. The body is buffered
|
|
21
|
+
* until this is known, and the head is written then, so the first byte is only sent once the whole body is
|
|
22
|
+
* received. When the length doesn't match, or is larger than `MAX_VERIFIED_CONTENT_LENGTH`, `content-length` is
|
|
23
|
+
* removed and Node.js frames the body itself (chunked on HTTP/1.1).
|
|
24
|
+
*
|
|
25
|
+
* The status code and the headers are always validated synchronously, even when the head is written later.
|
|
26
|
+
*
|
|
27
|
+
* @param res The Node.js response.
|
|
28
|
+
* @param method The request method.
|
|
29
|
+
* @param statusCode The response status code.
|
|
30
|
+
* @param headers The response headers, keys are matched case-insensitively.
|
|
31
|
+
* @returns The stream to write the body to.
|
|
32
|
+
* @throws When the status code or a header is invalid, or the headers were already sent (same errors as
|
|
33
|
+
* `res.writeHead()`).
|
|
34
|
+
*/
|
|
35
|
+
export function writeResponseHead(res, method, statusCode, headers) {
|
|
36
|
+
validateHead(res, statusCode);
|
|
37
|
+
let contentLength;
|
|
38
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
39
|
+
const lowerKey = key.toLowerCase();
|
|
40
|
+
if (lowerKey === "content-length") {
|
|
41
|
+
contentLength = value;
|
|
42
|
+
}
|
|
43
|
+
else if (lowerKey !== "transfer-encoding") {
|
|
44
|
+
// `setHeader()` validates the header like `writeHead()` does
|
|
45
|
+
res.setHeader(key, value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const expectedLength = parseContentLength(contentLength);
|
|
49
|
+
if (!Number.isNaN(expectedLength) &&
|
|
50
|
+
(method?.toUpperCase() === "HEAD" || statusCode === 304)) {
|
|
51
|
+
res.setHeader("content-length", String(expectedLength));
|
|
52
|
+
res.writeHead(statusCode);
|
|
53
|
+
res.flushHeaders();
|
|
54
|
+
return res;
|
|
55
|
+
}
|
|
56
|
+
if (statusCode === 204 || !(expectedLength <= MAX_VERIFIED_CONTENT_LENGTH)) {
|
|
57
|
+
res.writeHead(statusCode);
|
|
58
|
+
res.flushHeaders();
|
|
59
|
+
return res;
|
|
60
|
+
}
|
|
61
|
+
const body = new ContentLengthVerifier(res, statusCode, expectedLength);
|
|
62
|
+
body.pipe(res);
|
|
63
|
+
return body;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Checks that the head can be written, the way `res.writeHead()` does.
|
|
67
|
+
*
|
|
68
|
+
* The headers are validated by `res.setHeader()`.
|
|
69
|
+
*
|
|
70
|
+
* @param res The Node.js response.
|
|
71
|
+
* @param statusCode The response status code.
|
|
72
|
+
* @throws {Error} When the headers were already sent (code `ERR_HTTP_HEADERS_SENT`).
|
|
73
|
+
* @throws {RangeError} When the status code is not in 100-999 (code `ERR_HTTP_INVALID_STATUS_CODE`).
|
|
74
|
+
*/
|
|
75
|
+
function validateHead(res, statusCode) {
|
|
76
|
+
if (res.headersSent) {
|
|
77
|
+
throw Object.assign(new Error("Cannot write headers after they are sent to the client"), { code: "ERR_HTTP_HEADERS_SENT" });
|
|
78
|
+
}
|
|
79
|
+
const code = statusCode | 0;
|
|
80
|
+
if (code < 100 || code > 999) {
|
|
81
|
+
throw Object.assign(new RangeError(`Invalid status code: ${statusCode}`), {
|
|
82
|
+
code: "ERR_HTTP_INVALID_STATUS_CODE",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parses a `content-length` header value.
|
|
88
|
+
*
|
|
89
|
+
* @param contentLength The header value.
|
|
90
|
+
* @returns The length, or `NaN` when the value is missing or not a valid length.
|
|
91
|
+
*/
|
|
92
|
+
function parseContentLength(contentLength) {
|
|
93
|
+
const value = contentLength?.trim() ?? "";
|
|
94
|
+
const length = /^\d+$/.test(value) ? Number(value) : Number.NaN;
|
|
95
|
+
return Number.isSafeInteger(length) ? length : Number.NaN;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Buffers the body until it is known whether it has the declared length, then writes the head and the body.
|
|
99
|
+
*
|
|
100
|
+
* The headers, other than `content-length`, are already set on the response.
|
|
101
|
+
*/
|
|
102
|
+
class ContentLengthVerifier extends Transform {
|
|
103
|
+
res;
|
|
104
|
+
statusCode;
|
|
105
|
+
expectedLength;
|
|
106
|
+
chunks = [];
|
|
107
|
+
length = 0;
|
|
108
|
+
headWritten = false;
|
|
109
|
+
/**
|
|
110
|
+
* @param res The Node.js response.
|
|
111
|
+
* @param statusCode The response status code, already validated.
|
|
112
|
+
* @param expectedLength The declared `content-length`.
|
|
113
|
+
*/
|
|
114
|
+
constructor(res, statusCode, expectedLength) {
|
|
115
|
+
super();
|
|
116
|
+
this.res = res;
|
|
117
|
+
this.statusCode = statusCode;
|
|
118
|
+
this.expectedLength = expectedLength;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Buffers the chunk until the head is written, then passes it through.
|
|
122
|
+
*
|
|
123
|
+
* The head is written without `content-length` as soon as the body is longer than declared.
|
|
124
|
+
*
|
|
125
|
+
* @param chunk The body chunk.
|
|
126
|
+
* @param _encoding Unused, chunks are buffers.
|
|
127
|
+
* @param callback Called once the chunk is processed.
|
|
128
|
+
*/
|
|
129
|
+
_transform(chunk, _encoding, callback) {
|
|
130
|
+
if (this.headWritten) {
|
|
131
|
+
callback(null, chunk);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.chunks.push(chunk);
|
|
135
|
+
this.length += chunk.length;
|
|
136
|
+
if (this.length > this.expectedLength) {
|
|
137
|
+
this.writeHead(false);
|
|
138
|
+
}
|
|
139
|
+
callback();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Writes the head, when not already written, once the whole body is received.
|
|
143
|
+
*
|
|
144
|
+
* @param callback Called once the head and the buffered chunks are written.
|
|
145
|
+
*/
|
|
146
|
+
_flush(callback) {
|
|
147
|
+
if (!this.headWritten) {
|
|
148
|
+
this.writeHead(this.length === this.expectedLength);
|
|
149
|
+
}
|
|
150
|
+
callback();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Writes the head and the buffered chunks.
|
|
154
|
+
*
|
|
155
|
+
* This runs from the stream callbacks, where a thrown error would be uncaught and crash the process. The
|
|
156
|
+
* head is validated synchronously in `writeResponseHead()`, if writing it still fails, the response is destroyed.
|
|
157
|
+
*
|
|
158
|
+
* @param keepContentLength Whether the body has the declared length.
|
|
159
|
+
*/
|
|
160
|
+
writeHead(keepContentLength) {
|
|
161
|
+
this.headWritten = true;
|
|
162
|
+
const chunks = this.chunks;
|
|
163
|
+
this.chunks = [];
|
|
164
|
+
try {
|
|
165
|
+
if (keepContentLength) {
|
|
166
|
+
this.res.setHeader("content-length", String(this.expectedLength));
|
|
167
|
+
}
|
|
168
|
+
this.res.writeHead(this.statusCode);
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
error("Failed to write the response head", e);
|
|
172
|
+
this.res.destroy();
|
|
173
|
+
// Without an error, no `error` event is emitted
|
|
174
|
+
this.destroy();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const chunk of chunks) {
|
|
178
|
+
this.push(chunk);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/http/util.d.ts
CHANGED
|
@@ -8,6 +8,17 @@ export declare const convertHeader: (header: http.OutgoingHttpHeader) => string;
|
|
|
8
8
|
* @returns A list of Set-Cookie header
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseSetCookieHeader(cookies: string | string[] | null | undefined): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Converts the headers of a response returned by an edge function.
|
|
13
|
+
*
|
|
14
|
+
* `Set-Cookie` headers are returned as a list.
|
|
15
|
+
* The internal `x-middleware-set-cookie` header is dropped: `NextResponse` sets it when
|
|
16
|
+
* `response.cookies` is modified (i.e. in a route handler) and it contains the `HttpOnly` cookies.
|
|
17
|
+
*
|
|
18
|
+
* @param headers The headers of the response
|
|
19
|
+
* @returns The response headers to send to the client
|
|
20
|
+
*/
|
|
21
|
+
export declare function convertEdgeResponseHeaders(headers: Pick<Headers, "forEach" | "getSetCookie">): Record<string, string | string[]>;
|
|
11
22
|
/**
|
|
12
23
|
*
|
|
13
24
|
* Get the query object from an iterable of [key, value] pairs
|
package/dist/http/util.js
CHANGED
|
@@ -58,6 +58,32 @@ export function parseSetCookieHeader(cookies) {
|
|
|
58
58
|
}
|
|
59
59
|
return cookies;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Converts the headers of a response returned by an edge function.
|
|
63
|
+
*
|
|
64
|
+
* `Set-Cookie` headers are returned as a list.
|
|
65
|
+
* The internal `x-middleware-set-cookie` header is dropped: `NextResponse` sets it when
|
|
66
|
+
* `response.cookies` is modified (i.e. in a route handler) and it contains the `HttpOnly` cookies.
|
|
67
|
+
*
|
|
68
|
+
* @param headers The headers of the response
|
|
69
|
+
* @returns The response headers to send to the client
|
|
70
|
+
*/
|
|
71
|
+
export function convertEdgeResponseHeaders(headers) {
|
|
72
|
+
const responseHeaders = {};
|
|
73
|
+
headers.forEach((value, key) => {
|
|
74
|
+
const lowerKey = key.toLowerCase();
|
|
75
|
+
// Headers.forEach folds same-name headers; use getSetCookie() below instead.
|
|
76
|
+
if (lowerKey === "set-cookie" || lowerKey === "x-middleware-set-cookie") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
responseHeaders[key] = value;
|
|
80
|
+
});
|
|
81
|
+
const setCookies = headers.getSetCookie();
|
|
82
|
+
if (setCookies.length > 0) {
|
|
83
|
+
responseHeaders["set-cookie"] = setCookies;
|
|
84
|
+
}
|
|
85
|
+
return responseHeaders;
|
|
86
|
+
}
|
|
61
87
|
/**
|
|
62
88
|
*
|
|
63
89
|
* Get the query object from an iterable of [key, value] pairs
|
|
@@ -11,7 +11,8 @@ export default {
|
|
|
11
11
|
? [`/${path}`, `/${path}?_rsc=*`]
|
|
12
12
|
: [
|
|
13
13
|
`/${path}`,
|
|
14
|
-
|
|
14
|
+
// Data URLs are generated by Next and use the Next build id, not the Open Next build id
|
|
15
|
+
`/_next/data/${process.env.NEXT_BUILD_ID}${path === "/" ? "/index" : `/${path}`}.json*`,
|
|
15
16
|
];
|
|
16
17
|
});
|
|
17
18
|
await cloudfront.send(new CreateInvalidationCommand({
|
|
@@ -4,7 +4,11 @@ const fetchProxy = {
|
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
proxy: async (internalEvent) => {
|
|
6
6
|
const { url, headers: eventHeaders, method, body } = internalEvent;
|
|
7
|
-
const headers = Object.fromEntries(Object.entries(eventHeaders).filter(([key]) =>
|
|
7
|
+
const headers = Object.fromEntries(Object.entries(eventHeaders).filter(([key]) => !["cf-connecting-ip", "x-forwarded-host"].includes(key.toLowerCase())));
|
|
8
|
+
// Like Next.js, do not relay the `x-forwarded-host` sent by the client
|
|
9
|
+
if (eventHeaders.host) {
|
|
10
|
+
headers["x-forwarded-host"] = eventHeaders.host;
|
|
11
|
+
}
|
|
8
12
|
const response = await fetch(url, {
|
|
9
13
|
method,
|
|
10
14
|
headers,
|
|
@@ -6,6 +6,7 @@ function filterHeadersForProxy(headers) {
|
|
|
6
6
|
const filteredHeaders = {};
|
|
7
7
|
const disallowedHeaders = [
|
|
8
8
|
"host",
|
|
9
|
+
"x-forwarded-host",
|
|
9
10
|
"connection",
|
|
10
11
|
"via",
|
|
11
12
|
"x-cache",
|
|
@@ -30,6 +31,10 @@ const nodeProxy = {
|
|
|
30
31
|
debug("proxyRequest", url);
|
|
31
32
|
return new Promise((resolve, reject) => {
|
|
32
33
|
const filteredHeaders = filterHeadersForProxy(headers);
|
|
34
|
+
// Like Next.js, do not relay the `x-forwarded-host` sent by the client
|
|
35
|
+
if (headers.host) {
|
|
36
|
+
filteredHeaders["x-forwarded-host"] = headers.host;
|
|
37
|
+
}
|
|
33
38
|
debug("filteredHeaders", filteredHeaders);
|
|
34
39
|
const req = request(url, {
|
|
35
40
|
headers: filteredHeaders,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import express from "express";
|
|
3
3
|
import { NextConfig } from "../../adapters/config/index.js";
|
|
4
|
+
import { writeResponseHead } from "../../http/framing.js";
|
|
4
5
|
import { getMonorepoRelativePath } from "../../utils/normalize-path.js";
|
|
5
6
|
const wrapper = async (handler, converter) => {
|
|
6
7
|
const app = express();
|
|
@@ -16,8 +17,7 @@ const wrapper = async (handler, converter) => {
|
|
|
16
17
|
const internalEvent = await converter.convertFrom(req);
|
|
17
18
|
const streamCreator = {
|
|
18
19
|
writeHeaders: (prelude) => {
|
|
19
|
-
res.
|
|
20
|
-
return res;
|
|
20
|
+
return writeResponseHead(res, req.method, prelude.statusCode, prelude.headers);
|
|
21
21
|
},
|
|
22
22
|
};
|
|
23
23
|
await imageHandler(internalEvent, { streamCreator });
|
|
@@ -34,9 +34,7 @@ const wrapper = async (handler, converter) => {
|
|
|
34
34
|
const streamCreator = {
|
|
35
35
|
writeHeaders: (prelude) => {
|
|
36
36
|
res.setHeader("Set-Cookie", prelude.cookies);
|
|
37
|
-
res.
|
|
38
|
-
res.flushHeaders();
|
|
39
|
-
return res;
|
|
37
|
+
return writeResponseHead(res, req.method, prelude.statusCode, prelude.headers);
|
|
40
38
|
},
|
|
41
39
|
onFinish: () => { },
|
|
42
40
|
abortSignal: abortController.signal,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { writeResponseHead } from "../../http/framing.js";
|
|
2
3
|
import { debug, error } from "../../adapters/logger";
|
|
3
4
|
const wrapper = async (handler, converter) => {
|
|
4
5
|
const server = createServer(async (req, res) => {
|
|
@@ -7,9 +8,7 @@ const wrapper = async (handler, converter) => {
|
|
|
7
8
|
const streamCreator = {
|
|
8
9
|
writeHeaders: (prelude) => {
|
|
9
10
|
res.setHeader("Set-Cookie", prelude.cookies);
|
|
10
|
-
res.
|
|
11
|
-
res.flushHeaders();
|
|
12
|
-
return res;
|
|
11
|
+
return writeResponseHead(res, req.method, prelude.statusCode, prelude.headers);
|
|
13
12
|
},
|
|
14
13
|
abortSignal: abortController.signal,
|
|
15
14
|
};
|
package/dist/plugins/edge.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadFunctionsConfigManifest, loadHtmlPages, loadMiddlewareManifest, loadPagesManifest, loadPrerenderManifest, loadRoutesManifest, } from "../adapters/config/util.js";
|
|
4
|
+
import { getOpenNextBuildId, loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadFunctionsConfigManifest, loadHtmlPages, loadMiddlewareManifest, loadPagesManifest, loadPrerenderManifest, loadRoutesManifest, } from "../adapters/config/util.js";
|
|
5
5
|
import logger from "../logger.js";
|
|
6
6
|
import { normalizePath } from "../utils/normalize-path.js";
|
|
7
7
|
import { getCrossPlatformPathRegex } from "../utils/regex.js";
|
|
@@ -109,6 +109,7 @@ ${contents}
|
|
|
109
109
|
build.onLoad({ filter: getCrossPlatformPathRegex("adapters/config/index") }, async () => {
|
|
110
110
|
const NextConfig = loadConfig(nextDir);
|
|
111
111
|
const BuildId = loadBuildId(nextDir);
|
|
112
|
+
const OpenNextBuildId = getOpenNextBuildId(NextConfig, BuildId);
|
|
112
113
|
const HtmlPages = loadHtmlPages(nextDir);
|
|
113
114
|
const RoutesManifest = loadRoutesManifest(nextDir);
|
|
114
115
|
const ConfigHeaders = loadConfigHeaders(nextDir);
|
|
@@ -145,7 +146,7 @@ ${contents}
|
|
|
145
146
|
export const PagesManifest = ${JSON.stringify(PagesManifest)};
|
|
146
147
|
|
|
147
148
|
process.env.NEXT_BUILD_ID = BuildId;
|
|
148
|
-
process.env.OPEN_NEXT_BUILD_ID =
|
|
149
|
+
process.env.OPEN_NEXT_BUILD_ID = ${JSON.stringify(OpenNextBuildId)};
|
|
149
150
|
process.env.NEXT_PREVIEW_MODE_ID = PrerenderManifest?.preview?.previewModeId;
|
|
150
151
|
`;
|
|
151
152
|
return { contents };
|
package/dist/types/cache.d.ts
CHANGED
|
@@ -57,11 +57,22 @@ export interface CacheHandlerContext {
|
|
|
57
57
|
_requestHeaders: never;
|
|
58
58
|
fetchCacheKeyPrefix?: string;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The lifetime of a cache entry, as Next.js passes it to `set()` in `ctx.cacheControl`.
|
|
62
|
+
*/
|
|
63
|
+
export interface CacheControl {
|
|
64
|
+
revalidate: number | false;
|
|
65
|
+
expire?: number;
|
|
66
|
+
}
|
|
60
67
|
export interface CacheHandlerValue {
|
|
61
68
|
lastModified?: number;
|
|
62
69
|
age?: number;
|
|
63
70
|
cacheState?: string;
|
|
64
71
|
value: IncrementalCacheValue | null;
|
|
72
|
+
/**
|
|
73
|
+
* The lifetime of the entry, only read by Next.js 16.4+.
|
|
74
|
+
*/
|
|
75
|
+
cacheControl?: CacheControl;
|
|
65
76
|
}
|
|
66
77
|
export type Extension = "cache" | "fetch" | "composable";
|
|
67
78
|
type MetaHeaders = {
|
|
@@ -104,10 +115,7 @@ interface SetIncrementalFetchCacheContext {
|
|
|
104
115
|
}
|
|
105
116
|
interface SetIncrementalResponseCacheContext {
|
|
106
117
|
fetchCache?: false;
|
|
107
|
-
cacheControl?:
|
|
108
|
-
revalidate: number | false;
|
|
109
|
-
expire?: number;
|
|
110
|
-
};
|
|
118
|
+
cacheControl?: CacheControl;
|
|
111
119
|
/**
|
|
112
120
|
* True if the route is enabled for PPR.
|
|
113
121
|
*/
|
package/dist/types/global.d.ts
CHANGED
|
@@ -58,6 +58,13 @@ interface OpenNextRequestContext {
|
|
|
58
58
|
writtenTags: Set<string>;
|
|
59
59
|
/** Per-request in-memory cache. Overrides can use this to store data scoped to the current request. */
|
|
60
60
|
requestCache: RequestCache;
|
|
61
|
+
/**
|
|
62
|
+
* Host of the request (`x-forwarded-host` or `host` header).
|
|
63
|
+
*
|
|
64
|
+
* Used by `res.revalidate()` to revalidate pages served by another split function.
|
|
65
|
+
* Only available in main functions.
|
|
66
|
+
*/
|
|
67
|
+
host?: string;
|
|
61
68
|
}
|
|
62
69
|
declare global {
|
|
63
70
|
/**
|
|
@@ -192,7 +192,10 @@ export interface InstallOptions {
|
|
|
192
192
|
packages: string[];
|
|
193
193
|
/** @default undefined */
|
|
194
194
|
arch?: "x64" | "arm64";
|
|
195
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* @default undefined
|
|
197
|
+
* @deprecated npm no longer supports the `--target` flag, so this option is ignored.
|
|
198
|
+
*/
|
|
196
199
|
nodeVersion?: string;
|
|
197
200
|
/** @default undefined */
|
|
198
201
|
libc?: "glibc" | "musl";
|
|
@@ -98,6 +98,11 @@ export type CacheValue<CacheType extends CacheEntryType> = (CacheType extends "f
|
|
|
98
98
|
* This is available for page cache entry, but only at runtime.
|
|
99
99
|
*/
|
|
100
100
|
revalidate?: number | false;
|
|
101
|
+
/**
|
|
102
|
+
* This is available for page cache entry, but only at runtime.
|
|
103
|
+
* The cache interceptor does not use it.
|
|
104
|
+
*/
|
|
105
|
+
expire?: number;
|
|
101
106
|
};
|
|
102
107
|
export type IncrementalCache = {
|
|
103
108
|
get<CacheType extends CacheEntryType = "cache">(key: string, cacheType?: CacheType): Promise<WithLastModified<CacheValue<CacheType>> | null>;
|
|
@@ -240,12 +245,54 @@ export type OriginResolver = BaseOverride & {
|
|
|
240
245
|
export type ProxyExternalRequest = BaseOverride & {
|
|
241
246
|
proxy: (event: InternalEvent) => Promise<InternalResult>;
|
|
242
247
|
};
|
|
248
|
+
/**
|
|
249
|
+
* Describes a path whose CDN entries must be invalidated.
|
|
250
|
+
*
|
|
251
|
+
* Includes the path before and after routing as well as the routes that matched
|
|
252
|
+
* while resolving the request.
|
|
253
|
+
*/
|
|
243
254
|
type CDNPath = {
|
|
255
|
+
/** The original request pathname before route resolution. */
|
|
244
256
|
initialPath: string;
|
|
257
|
+
/** The effective request pathname after route resolution. */
|
|
245
258
|
rawPath: string;
|
|
259
|
+
/** The routes that matched while resolving the request. */
|
|
246
260
|
resolvedRoutes: ResolvedRoute[];
|
|
247
261
|
};
|
|
262
|
+
/**
|
|
263
|
+
* Invalidates CDN entries after on-demand revalidation.
|
|
264
|
+
*
|
|
265
|
+
* Implementations must support path invalidation and may additionally support
|
|
266
|
+
* native cache-tag invalidation.
|
|
267
|
+
*/
|
|
248
268
|
export type CDNInvalidationHandler = BaseOverride & {
|
|
269
|
+
/**
|
|
270
|
+
* Invalidates CDN entries associated with resolved paths.
|
|
271
|
+
*
|
|
272
|
+
* @param paths Paths and routing metadata for the entries to invalidate.
|
|
273
|
+
* @returns A promise that resolves when invalidation is complete.
|
|
274
|
+
*/
|
|
249
275
|
invalidatePaths: (paths: CDNPath[]) => Promise<void>;
|
|
276
|
+
/**
|
|
277
|
+
* Invalidates CDN entries associated with cache tags.
|
|
278
|
+
*
|
|
279
|
+
* Called once per `revalidateTag` call, after the tag cache has been updated,
|
|
280
|
+
* with the tags passed to Next.js as is: hard tags from `revalidateTag()` and
|
|
281
|
+
* soft tags (prefixed with `_N_T_/`) from `revalidatePath()`. Tags are not
|
|
282
|
+
* expanded, i.e. the hard tags of the paths matching a soft tag are not added.
|
|
283
|
+
*
|
|
284
|
+
* When provided, `invalidatePaths` is no longer called for tag revalidation.
|
|
285
|
+
* It is still used for `res.revalidate()` in the Pages Router.
|
|
286
|
+
* When omitted, OpenNext resolves the tags to paths and calls `invalidatePaths`.
|
|
287
|
+
*
|
|
288
|
+
* The CDN integration must associate cached entries with the same tags, both
|
|
289
|
+
* hard and soft, for invalidation to have an effect. For response-based CDN
|
|
290
|
+
* caches, the wrapper or converter must propagate the request's cache tags to
|
|
291
|
+
* the provider's response metadata.
|
|
292
|
+
*
|
|
293
|
+
* @param tags Cache tags associated with the entries to invalidate.
|
|
294
|
+
* @returns A promise that resolves when invalidation is complete.
|
|
295
|
+
*/
|
|
296
|
+
invalidateTags?: (tags: string[]) => Promise<void>;
|
|
250
297
|
};
|
|
251
298
|
export {};
|
package/dist/utils/cache.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { CacheValue, NextModeTagCacheWriteInput, OriginalTagCacheWriteInput, WithLastModified } from "../types/overrides.js";
|
|
2
|
+
/**
|
|
3
|
+
* Computes the key used to store a composable (`"use cache"`) entry.
|
|
4
|
+
*
|
|
5
|
+
* The cache key provided by Next.js embeds the serialized arguments of the cached function and may
|
|
6
|
+
* contain any UTF-16 string. Storage backends transform keys (path normalization, URL parsing,
|
|
7
|
+
* UTF-8 encoding, ...) which could map distinct Next.js keys to the same stored entry.
|
|
8
|
+
*
|
|
9
|
+
* The storage key is the sha256 of the exact UTF-16 code units of the cache key, as a 64 characters
|
|
10
|
+
* lowercase hex string that every backend can store as is.
|
|
11
|
+
*
|
|
12
|
+
* @param cacheKey The cache key provided by Next.js
|
|
13
|
+
* @returns The key to use for the incremental cache and the tag cache
|
|
14
|
+
*/
|
|
15
|
+
export declare function getComposableCacheStorageKey(cacheKey: string): string;
|
|
2
16
|
/**
|
|
3
17
|
*
|
|
4
18
|
* @param key The key for that specific cache entry
|