@opennextjs/aws 4.1.1 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/cache.js +2 -1
- package/dist/adapters/middleware.js +2 -1
- package/dist/adapters/plugins/image-optimization/image-optimization.replacement.js +6 -5
- package/dist/adapters/revalidate.js +4 -3
- package/dist/build/createAssets.js +3 -2
- package/dist/core/requestHandler.js +2 -1
- package/dist/core/routing/cacheInterceptor.js +38 -26
- package/dist/core/routing/middleware.js +3 -2
- package/dist/core/routing/util.js +16 -27
- package/dist/core/routingHandler.js +2 -1
- package/dist/http/openNextResponse.js +4 -11
- package/dist/overrides/queue/direct.js +3 -2
- package/dist/utils/cache.js +3 -2
- package/dist/utils/cacheHeaders.d.ts +52 -0
- package/dist/utils/cacheHeaders.js +58 -0
- package/package.json +1 -1
package/dist/adapters/cache.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getStaleLastModified, getTagsFromValue, hasBeenRevalidated, isStale, writeTags, } from "../utils/cache";
|
|
2
2
|
import { isBinaryContentType } from "../utils/binary";
|
|
3
|
+
import { CACHE_TAGS_HEADER } from "../utils/cacheHeaders";
|
|
3
4
|
import { compareSemver } from "../utils/semver";
|
|
4
5
|
import { debug, error, warn } from "./logger";
|
|
5
6
|
export const SOFT_TAG_PREFIX = "_N_T_/";
|
|
@@ -440,7 +441,7 @@ export default class Cache {
|
|
|
440
441
|
// is shared with headers that can legitimately repeat, so we narrow on `typeof` here.
|
|
441
442
|
// Next.js guards the same way when it reads the header back.
|
|
442
443
|
const pageCacheTags = data?.kind === "PAGE" || data?.kind === "APP_PAGE"
|
|
443
|
-
? data.headers?.[
|
|
444
|
+
? data.headers?.[CACHE_TAGS_HEADER]
|
|
444
445
|
: undefined;
|
|
445
446
|
const derivedTags = data?.kind === "FETCH"
|
|
446
447
|
? //@ts-expect-error - On older versions of next, ctx was a number, but for these cases we use data?.data?.tags
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ISR_HEADER } from "../utils/cacheHeaders";
|
|
1
2
|
import { runWithOpenNextRequestContext } from "../utils/promise";
|
|
2
3
|
import { debug, error } from "../adapters/logger";
|
|
3
4
|
import { createGenericHandler } from "../core/createGenericHandler";
|
|
@@ -21,7 +22,7 @@ const defaultHandler = async (internalEvent, options) => {
|
|
|
21
22
|
const requestId = Math.random().toString(36);
|
|
22
23
|
// We run everything in the async local storage context so that it is available in the external middleware
|
|
23
24
|
return runWithOpenNextRequestContext({
|
|
24
|
-
isISRRevalidation: internalEvent.headers[
|
|
25
|
+
isISRRevalidation: internalEvent.headers[ISR_HEADER] === "1",
|
|
25
26
|
waitUntil: options?.waitUntil,
|
|
26
27
|
requestId,
|
|
27
28
|
}, async () => {
|
|
@@ -17,18 +17,19 @@ export async function optimizeImage(headers, imageParams, nextConfig, handleRequ
|
|
|
17
17
|
*
|
|
18
18
|
* https://github.com/vercel/next.js/blob/bfe2ab4/packages/next/src/server/image-optimizer.ts#L711
|
|
19
19
|
*/
|
|
20
|
+
// Next 16 rejects upstream hosts resolving to private IPs unless the user
|
|
21
|
+
// opts out with `images.dangerouslyAllowLocalIP` — forward their setting
|
|
22
|
+
// instead of always disabling the opt-out.
|
|
23
|
+
const dangerouslyAllowLocalIP = Boolean(nextConfig.images?.dangerouslyAllowLocalIP);
|
|
20
24
|
const callFetchExternalImage = () => {
|
|
21
25
|
const version = globalThis.nextVersion;
|
|
22
|
-
// v16.1.5+
|
|
23
26
|
if (compareSemver(version, ">=", "16.1.5")) {
|
|
24
|
-
return fetchExternalImage(href,
|
|
27
|
+
return fetchExternalImage(href, dangerouslyAllowLocalIP, maximumResponseBody);
|
|
25
28
|
}
|
|
26
|
-
// v16.0.0–v16.1.4
|
|
27
29
|
if (compareSemver(version, ">=", "16")) {
|
|
28
30
|
// @ts-expect-error - fetchExternalImage signature varies across Next.js versions
|
|
29
|
-
return fetchExternalImage(href,
|
|
31
|
+
return fetchExternalImage(href, dangerouslyAllowLocalIP);
|
|
30
32
|
}
|
|
31
|
-
// v15.5.10–v15.x
|
|
32
33
|
if (compareSemver(version, ">=", "15.5.10")) {
|
|
33
34
|
// @ts-expect-error - fetchExternalImage signature varies across Next.js versions
|
|
34
35
|
return fetchExternalImage(href, maximumResponseBody);
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createGenericHandler } from "../core/createGenericHandler.js";
|
|
5
|
+
import { ISR_HEADER, NEXTJS_CACHE_HEADER, PRERENDER_REVALIDATE_HEADER, } from "../utils/cacheHeaders.js";
|
|
5
6
|
import { debug, error } from "./logger.js";
|
|
6
7
|
const prerenderManifest = loadPrerenderManifest();
|
|
7
8
|
const ALLOWED_STATUS_CODES = [200, 307, 308, 404];
|
|
@@ -23,8 +24,8 @@ const defaultHandler = async (event) => {
|
|
|
23
24
|
const req = https.request(`https://${host}${url}`, {
|
|
24
25
|
method: "HEAD",
|
|
25
26
|
headers: {
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
[PRERENDER_REVALIDATE_HEADER]: prerenderManifest.preview.previewModeId,
|
|
28
|
+
[ISR_HEADER]: "1",
|
|
28
29
|
},
|
|
29
30
|
}, (res) => {
|
|
30
31
|
debug("revalidating", {
|
|
@@ -34,7 +35,7 @@ const defaultHandler = async (event) => {
|
|
|
34
35
|
statusCode: res.statusCode,
|
|
35
36
|
});
|
|
36
37
|
if (!ALLOWED_STATUS_CODES.includes(res.statusCode ?? 0) ||
|
|
37
|
-
res.headers[
|
|
38
|
+
res.headers[NEXTJS_CACHE_HEADER] !== "REVALIDATED") {
|
|
38
39
|
failedRecords.push(record);
|
|
39
40
|
}
|
|
40
41
|
resolve(res);
|
|
@@ -4,6 +4,7 @@ import { loadConfig } from "../adapters/config/util.js";
|
|
|
4
4
|
import { safeParseJsonFile } from "../utils/safe-json-parse.js";
|
|
5
5
|
import logger from "../logger.js";
|
|
6
6
|
import { isBinaryContentType } from "../utils/binary.js";
|
|
7
|
+
import { CACHE_TAGS_HEADER } from "../utils/cacheHeaders.js";
|
|
7
8
|
import * as buildHelper from "./helper.js";
|
|
8
9
|
/**
|
|
9
10
|
* Copy the static assets to the output folder
|
|
@@ -188,8 +189,8 @@ export function createCacheAssets(options) {
|
|
|
188
189
|
buildHelper.traverseFiles(sourceDir, ({ absolutePath, relativePath }) => absolutePath.endsWith(".meta") && !isFileSkipped(relativePath), ({ absolutePath, relativePath }) => {
|
|
189
190
|
const fileContent = fs.readFileSync(absolutePath, "utf8");
|
|
190
191
|
const fileData = safeParseJsonFile(fileContent, absolutePath);
|
|
191
|
-
if (fileData?.headers?.[
|
|
192
|
-
fileData.headers[
|
|
192
|
+
if (fileData?.headers?.[CACHE_TAGS_HEADER]) {
|
|
193
|
+
fileData.headers[CACHE_TAGS_HEADER]
|
|
193
194
|
.split(",")
|
|
194
195
|
.forEach((tag) => {
|
|
195
196
|
// TODO: We should split the tag using getDerivedTags from next.js or maybe use an in house implementation
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { IncomingMessage } from "../http/index.js";
|
|
3
|
+
import { ISR_HEADER } from "../utils/cacheHeaders";
|
|
3
4
|
import { runWithOpenNextRequestContext } from "../utils/promise";
|
|
4
5
|
import { NextConfig } from "../adapters/config/index";
|
|
5
6
|
import { debug, error } from "../adapters/logger";
|
|
@@ -22,7 +23,7 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
22
23
|
: Math.random().toString(36);
|
|
23
24
|
// We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
|
|
24
25
|
return runWithOpenNextRequestContext({
|
|
25
|
-
isISRRevalidation: initialHeaders[
|
|
26
|
+
isISRRevalidation: initialHeaders[ISR_HEADER] === "1",
|
|
26
27
|
waitUntil: options?.waitUntil,
|
|
27
28
|
requestId,
|
|
28
29
|
}, async () => {
|
|
@@ -3,6 +3,7 @@ import { NextConfig, PrerenderManifest } from "../../adapters/config/index";
|
|
|
3
3
|
import { emptyReadableStream, toReadableStream } from "../../utils/stream";
|
|
4
4
|
import { isBinaryContentType } from "../../utils/binary";
|
|
5
5
|
import { getTagsFromValue, hasBeenRevalidated, isStale } from "../../utils/cache";
|
|
6
|
+
import { CACHE_CONTROL_HEADER, NO_STORE_CACHE_CONTROL, OPEN_NEXT_CACHE_HEADER, PRERENDER_REVALIDATE_HEADER, fixCacheControlForError, } from "../../utils/cacheHeaders";
|
|
6
7
|
import { debug } from "../../adapters/logger";
|
|
7
8
|
import { localizePath } from "./i18n";
|
|
8
9
|
import { generateMessageGroupId } from "./queue";
|
|
@@ -41,8 +42,8 @@ async function computeCacheControl(path, body, host, revalidate, lastModified, i
|
|
|
41
42
|
if (revalidate === 0) {
|
|
42
43
|
// This one should never happen
|
|
43
44
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
[CACHE_CONTROL_HEADER]: NO_STORE_CACHE_CONTROL,
|
|
46
|
+
[OPEN_NEXT_CACHE_HEADER]: "ERROR",
|
|
46
47
|
etag,
|
|
47
48
|
};
|
|
48
49
|
}
|
|
@@ -77,14 +78,14 @@ async function computeCacheControl(path, body, host, revalidate, lastModified, i
|
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
80
|
return {
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
[CACHE_CONTROL_HEADER]: `s-maxage=${sMaxAge}, stale-while-revalidate=${CACHE_ONE_MONTH}`,
|
|
82
|
+
[OPEN_NEXT_CACHE_HEADER]: isStale ? "STALE" : "HIT",
|
|
82
83
|
etag,
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
return {
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
[CACHE_CONTROL_HEADER]: `s-maxage=${CACHE_ONE_YEAR}, stale-while-revalidate=${CACHE_ONE_MONTH}`,
|
|
88
|
+
[OPEN_NEXT_CACHE_HEADER]: "HIT",
|
|
88
89
|
etag,
|
|
89
90
|
};
|
|
90
91
|
}
|
|
@@ -168,23 +169,30 @@ async function generateResult(event, localizedPath, cachedValue, lastModified, i
|
|
|
168
169
|
return undefined;
|
|
169
170
|
}
|
|
170
171
|
const cacheControl = await computeCacheControl(localizedPath, body, event.headers.host, cachedValue.revalidate, lastModified, isStaleFromTagCache);
|
|
172
|
+
// Sometimes other status codes can be cached, like 404. For these cases, we should return the correct status code
|
|
173
|
+
// Also set the status code to the rewriteStatusCode if defined
|
|
174
|
+
// This can happen in handleMiddleware in routingHandler.
|
|
175
|
+
// `NextResponse.rewrite(url, { status: xxx})
|
|
176
|
+
// The rewrite status code should take precedence over the cached one
|
|
177
|
+
const statusCode = event.rewriteStatusCode ?? cachedValue.meta?.status ?? 200;
|
|
178
|
+
const headers = {
|
|
179
|
+
...cacheControl,
|
|
180
|
+
"content-type": type,
|
|
181
|
+
...cachedValue.meta?.headers,
|
|
182
|
+
vary: VARY_HEADER,
|
|
183
|
+
...additionalHeaders,
|
|
184
|
+
};
|
|
185
|
+
// Applied last so that it wins over both the computed cache control and the one
|
|
186
|
+
// that could be stored in the entry's own headers. This is the same override the
|
|
187
|
+
// server path applies in `OpenNextNodeResponse.fixHeadersForError`, which the
|
|
188
|
+
// interceptor bypasses by returning a result directly.
|
|
189
|
+
fixCacheControlForError(headers, statusCode);
|
|
171
190
|
return {
|
|
172
191
|
type: "core",
|
|
173
|
-
|
|
174
|
-
// Also set the status code to the rewriteStatusCode if defined
|
|
175
|
-
// This can happen in handleMiddleware in routingHandler.
|
|
176
|
-
// `NextResponse.rewrite(url, { status: xxx})
|
|
177
|
-
// The rewrite status code should take precedence over the cached one
|
|
178
|
-
statusCode: event.rewriteStatusCode ?? cachedValue.meta?.status ?? 200,
|
|
192
|
+
statusCode,
|
|
179
193
|
body: toReadableStream(body, false),
|
|
180
194
|
isBase64Encoded: false,
|
|
181
|
-
headers
|
|
182
|
-
...cacheControl,
|
|
183
|
-
"content-type": type,
|
|
184
|
-
...cachedValue.meta?.headers,
|
|
185
|
-
vary: VARY_HEADER,
|
|
186
|
-
...additionalHeaders,
|
|
187
|
-
},
|
|
195
|
+
headers,
|
|
188
196
|
};
|
|
189
197
|
}
|
|
190
198
|
/**
|
|
@@ -209,7 +217,7 @@ function decodePathParams(pathname) {
|
|
|
209
217
|
}
|
|
210
218
|
export async function cacheInterceptor(event) {
|
|
211
219
|
if (Boolean(event.headers["next-action"]) ||
|
|
212
|
-
Boolean(event.headers[
|
|
220
|
+
Boolean(event.headers[PRERENDER_REVALIDATE_HEADER]))
|
|
213
221
|
return event;
|
|
214
222
|
// Check for Next.js preview mode cookies
|
|
215
223
|
const cookies = event.headers.cookie || "";
|
|
@@ -290,15 +298,19 @@ export async function cacheInterceptor(event) {
|
|
|
290
298
|
case "route": {
|
|
291
299
|
const cacheControl = await computeCacheControl(localizedPath, cachedData.value.body, host, cachedData.value.revalidate, cachedData.lastModified, _isStale);
|
|
292
300
|
const isBinary = isBinaryContentType(String(cachedData.value.meta?.headers?.["content-type"]));
|
|
301
|
+
const statusCode = event.rewriteStatusCode ?? cachedData.value.meta?.status ?? 200;
|
|
302
|
+
const headers = {
|
|
303
|
+
...cacheControl,
|
|
304
|
+
...cachedData.value.meta?.headers,
|
|
305
|
+
vary: VARY_HEADER,
|
|
306
|
+
};
|
|
307
|
+
// See the note in `generateResult`.
|
|
308
|
+
fixCacheControlForError(headers, statusCode);
|
|
293
309
|
return {
|
|
294
310
|
type: "core",
|
|
295
|
-
statusCode
|
|
311
|
+
statusCode,
|
|
296
312
|
body: toReadableStream(cachedData.value.body, isBinary),
|
|
297
|
-
headers
|
|
298
|
-
...cacheControl,
|
|
299
|
-
...cachedData.value.meta?.headers,
|
|
300
|
-
vary: VARY_HEADER,
|
|
301
|
-
},
|
|
313
|
+
headers,
|
|
302
314
|
isBase64Encoded: isBinary,
|
|
303
315
|
};
|
|
304
316
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, } from "../../adapters/config/index.js";
|
|
2
|
+
import { ISR_HEADER, PRERENDER_REVALIDATE_HEADER } from "../../utils/cacheHeaders.js";
|
|
2
3
|
import { emptyReadableStream } from "../../utils/stream.js";
|
|
3
4
|
import { getQueryFromSearchParams } from "../../overrides/converters/utils.js";
|
|
4
5
|
import { localizePath } from "./i18n/index.js";
|
|
@@ -23,8 +24,8 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
23
24
|
// We bypass the middleware if the request is internal
|
|
24
25
|
// We should only do that if the request has the correct `x-prerender-revalidate` header
|
|
25
26
|
// The `x-prerender-revalidate` header is set at build time and should be safe to trust
|
|
26
|
-
if (headers[
|
|
27
|
-
headers[
|
|
27
|
+
if (headers[ISR_HEADER] &&
|
|
28
|
+
headers[PRERENDER_REVALIDATE_HEADER] ===
|
|
28
29
|
PrerenderManifest?.preview?.previewModeId)
|
|
29
30
|
return internalEvent;
|
|
30
31
|
// We only need the normalizedPath to check if the middleware should run
|
|
@@ -6,6 +6,7 @@ import { getQueryFromIterator, parseHeaders } from "../../http/util.js";
|
|
|
6
6
|
import { ReadableStream } from "node:stream/web";
|
|
7
7
|
import { debug, error } from "../../adapters/logger.js";
|
|
8
8
|
import { isBinaryContentType } from "../../utils/binary.js";
|
|
9
|
+
import { CACHE_CONTROL_HEADER, ISR_HEADER, NEXTJS_CACHE_HEADER, NO_STORE_CACHE_CONTROL, fixCacheControlForError, } from "../../utils/cacheHeaders.js";
|
|
9
10
|
import { localizePath } from "./i18n/index.js";
|
|
10
11
|
import { generateMessageGroupId } from "./queue.js";
|
|
11
12
|
/**
|
|
@@ -205,11 +206,6 @@ export function convertBodyToReadableStream(method, body) {
|
|
|
205
206
|
},
|
|
206
207
|
});
|
|
207
208
|
}
|
|
208
|
-
var CommonHeaders;
|
|
209
|
-
(function (CommonHeaders) {
|
|
210
|
-
CommonHeaders["CACHE_CONTROL"] = "cache-control";
|
|
211
|
-
CommonHeaders["NEXT_CACHE"] = "x-nextjs-cache";
|
|
212
|
-
})(CommonHeaders || (CommonHeaders = {}));
|
|
213
209
|
/**
|
|
214
210
|
*
|
|
215
211
|
* @__PURE__
|
|
@@ -217,11 +213,7 @@ var CommonHeaders;
|
|
|
217
213
|
export function fixCacheHeaderForHtmlPages(internalEvent, headers) {
|
|
218
214
|
// We don't want to cache error pages
|
|
219
215
|
if (internalEvent.rawPath === "/404" || internalEvent.rawPath === "/500") {
|
|
220
|
-
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
headers[CommonHeaders.CACHE_CONTROL] =
|
|
224
|
-
"private, no-cache, no-store, max-age=0, must-revalidate";
|
|
216
|
+
fixCacheControlForError(headers, internalEvent.rawPath === "/404" ? 404 : 500);
|
|
225
217
|
return;
|
|
226
218
|
}
|
|
227
219
|
const localizedPath = localizePath(internalEvent);
|
|
@@ -230,7 +222,7 @@ export function fixCacheHeaderForHtmlPages(internalEvent, headers) {
|
|
|
230
222
|
// Requests containing an `x-middleware-prefetch` header must not be cached
|
|
231
223
|
if (HtmlPages.includes(localizedPath) &&
|
|
232
224
|
!internalEvent.headers["x-middleware-prefetch"]) {
|
|
233
|
-
headers[
|
|
225
|
+
headers[CACHE_CONTROL_HEADER] =
|
|
234
226
|
"public, max-age=0, s-maxage=31536000, must-revalidate";
|
|
235
227
|
}
|
|
236
228
|
}
|
|
@@ -240,7 +232,7 @@ export function fixCacheHeaderForHtmlPages(internalEvent, headers) {
|
|
|
240
232
|
*/
|
|
241
233
|
export function fixSWRCacheHeader(headers) {
|
|
242
234
|
// WORKAROUND: `NextServer` does not set correct SWR cache headers — https://github.com/sst/open-next#workaround-nextserver-does-not-set-correct-swr-cache-headers
|
|
243
|
-
let cacheControl = headers[
|
|
235
|
+
let cacheControl = headers[CACHE_CONTROL_HEADER];
|
|
244
236
|
if (!cacheControl)
|
|
245
237
|
return;
|
|
246
238
|
if (Array.isArray(cacheControl)) {
|
|
@@ -248,7 +240,7 @@ export function fixSWRCacheHeader(headers) {
|
|
|
248
240
|
}
|
|
249
241
|
if (typeof cacheControl !== "string")
|
|
250
242
|
return;
|
|
251
|
-
headers[
|
|
243
|
+
headers[CACHE_CONTROL_HEADER] = cacheControl.replace(/\bstale-while-revalidate(?!=)/, "stale-while-revalidate=2592000");
|
|
252
244
|
}
|
|
253
245
|
/**
|
|
254
246
|
*
|
|
@@ -271,7 +263,7 @@ export function addOpenNextHeader(headers) {
|
|
|
271
263
|
* @__PURE__
|
|
272
264
|
*/
|
|
273
265
|
export async function revalidateIfRequired(host, rawPath, headers, req) {
|
|
274
|
-
if (headers[
|
|
266
|
+
if (headers[NEXTJS_CACHE_HEADER] === "STALE") {
|
|
275
267
|
// If the URL is rewritten, revalidation needs to be done on the rewritten URL.
|
|
276
268
|
// - Link to Next.js doc: https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration#on-demand-revalidation
|
|
277
269
|
// - Link to NextInternalRequestMeta: https://github.com/vercel/next.js/blob/57ab2818b93627e91c937a130fb56a36c41629c3/packages/next/src/server/request-meta.ts#L11
|
|
@@ -315,37 +307,35 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
|
|
|
315
307
|
*/
|
|
316
308
|
export function fixISRHeaders(headers) {
|
|
317
309
|
const sMaxAgeRegex = /s-maxage=(\d+)/;
|
|
318
|
-
const match = headers[
|
|
310
|
+
const match = headers[CACHE_CONTROL_HEADER]?.match(sMaxAgeRegex);
|
|
319
311
|
const sMaxAge = match ? Number.parseInt(match[1]) : undefined;
|
|
320
312
|
// We only apply the fix if the cache-control header contains s-maxage
|
|
321
313
|
if (!sMaxAge) {
|
|
322
314
|
return;
|
|
323
315
|
}
|
|
324
|
-
if (headers[
|
|
325
|
-
headers[
|
|
326
|
-
"private, no-cache, no-store, max-age=0, must-revalidate";
|
|
316
|
+
if (headers[NEXTJS_CACHE_HEADER] === "REVALIDATED") {
|
|
317
|
+
headers[CACHE_CONTROL_HEADER] = NO_STORE_CACHE_CONTROL;
|
|
327
318
|
return;
|
|
328
319
|
}
|
|
329
320
|
const _lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
|
|
330
|
-
if (headers[
|
|
331
|
-
debug("cache-control", headers[
|
|
321
|
+
if (headers[NEXTJS_CACHE_HEADER] === "HIT" && _lastModified > 0) {
|
|
322
|
+
debug("cache-control", headers[CACHE_CONTROL_HEADER], _lastModified, Date.now());
|
|
332
323
|
// 31536000 is the default s-maxage value for SSG pages
|
|
333
324
|
if (sMaxAge && sMaxAge !== 31536000) {
|
|
334
325
|
// calculate age
|
|
335
326
|
const age = Math.round((Date.now() - _lastModified) / 1000);
|
|
336
327
|
const remainingTtl = Math.max(sMaxAge - age, 1);
|
|
337
|
-
headers[
|
|
328
|
+
headers[CACHE_CONTROL_HEADER] =
|
|
338
329
|
`s-maxage=${remainingTtl}, stale-while-revalidate=2592000`;
|
|
339
330
|
}
|
|
340
331
|
}
|
|
341
|
-
if (headers[
|
|
332
|
+
if (headers[NEXTJS_CACHE_HEADER] !== "STALE")
|
|
342
333
|
return;
|
|
343
334
|
// If the cache is stale, we revalidate in the background
|
|
344
335
|
// In order for CloudFront SWR to work, we set the stale-while-revalidate value to 2 seconds
|
|
345
336
|
// This will cause CloudFront to cache the stale data for a short period of time while we revalidate in the background
|
|
346
337
|
// Once the revalidation is complete, CloudFront will serve the fresh data
|
|
347
|
-
headers[
|
|
348
|
-
"s-maxage=2, stale-while-revalidate=2592000";
|
|
338
|
+
headers[CACHE_CONTROL_HEADER] = "s-maxage=2, stale-while-revalidate=2592000";
|
|
349
339
|
}
|
|
350
340
|
/**
|
|
351
341
|
*
|
|
@@ -371,9 +361,8 @@ export function createServerResponse(routingResult, headers, responseStream) {
|
|
|
371
361
|
export async function invalidateCDNOnRequest(params, headers) {
|
|
372
362
|
const { internalEvent, resolvedRoutes, initialURL } = params;
|
|
373
363
|
const initialPath = new URL(initialURL).pathname;
|
|
374
|
-
const isIsrRevalidation = internalEvent.headers[
|
|
375
|
-
if (!isIsrRevalidation &&
|
|
376
|
-
headers[CommonHeaders.NEXT_CACHE] === "REVALIDATED") {
|
|
364
|
+
const isIsrRevalidation = internalEvent.headers[ISR_HEADER] === "1";
|
|
365
|
+
if (!isIsrRevalidation && headers[NEXTJS_CACHE_HEADER] === "REVALIDATED") {
|
|
377
366
|
await globalThis.cdnInvalidationHandler.invalidatePaths([
|
|
378
367
|
{
|
|
379
368
|
initialPath,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BuildId, ConfigHeaders, NextConfig, PrerenderManifest, RoutesManifest, } from "../adapters/config/index";
|
|
2
2
|
import { debug, error } from "../adapters/logger";
|
|
3
|
+
import { NO_STORE_CACHE_CONTROL } from "../utils/cacheHeaders";
|
|
3
4
|
import { cacheInterceptor } from "./routing/cacheInterceptor";
|
|
4
5
|
import { detectLocale } from "./routing/i18n";
|
|
5
6
|
import { fixDataPage, getNextConfigHeaders, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
|
|
@@ -169,7 +170,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
169
170
|
url: constructNextUrl(eventOrResult.url, "/404"),
|
|
170
171
|
headers: {
|
|
171
172
|
...eventOrResult.headers,
|
|
172
|
-
"x-middleware-response-cache-control":
|
|
173
|
+
"x-middleware-response-cache-control": NO_STORE_CACHE_CONTROL,
|
|
173
174
|
},
|
|
174
175
|
};
|
|
175
176
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Transform } from "node:stream";
|
|
2
2
|
import { debug } from "../adapters/logger";
|
|
3
|
+
import { fixCacheControlForError } from "../utils/cacheHeaders";
|
|
3
4
|
import { parseHeaders, parseSetCookieHeader } from "./util";
|
|
4
5
|
const SET_COOKIE_HEADER = "set-cookie";
|
|
5
6
|
const CANNOT_BE_USED = "This cannot be used in OpenNext";
|
|
@@ -327,16 +328,8 @@ export class OpenNextNodeResponse extends Transform {
|
|
|
327
328
|
// For some reason, next returns the 500 error page with some cache-control headers
|
|
328
329
|
// We need to fix that
|
|
329
330
|
fixHeadersForError() {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
// We only check for 404 and 500 errors
|
|
334
|
-
// The rest should be errors that are handled by the user and they should set the cache headers themselves
|
|
335
|
-
if (this.statusCode === 404 || this.statusCode === 500) {
|
|
336
|
-
// For some reason calling this.setHeader("Cache-Control", "no-cache, no-store, must-revalidate") does not work here
|
|
337
|
-
// The function is not even called, i'm probably missing something obvious
|
|
338
|
-
this.headers["cache-control"] =
|
|
339
|
-
"private, no-cache, no-store, max-age=0, must-revalidate";
|
|
340
|
-
}
|
|
331
|
+
// NOTE: this assigns to `this.headers` directly rather than going through
|
|
332
|
+
// `this.setHeader()`, because for some reason `setHeader` is not even called from here.
|
|
333
|
+
fixCacheControlForError(this.headers, this.statusCode);
|
|
341
334
|
}
|
|
342
335
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ISR_HEADER, PRERENDER_REVALIDATE_HEADER } from "../../utils/cacheHeaders.js";
|
|
1
2
|
const queue = {
|
|
2
3
|
name: "dev-queue",
|
|
3
4
|
send: async (message) => {
|
|
@@ -9,8 +10,8 @@ const queue = {
|
|
|
9
10
|
await globalThis.internalFetch(`${protocol}://${host}${url}`, {
|
|
10
11
|
method: "HEAD",
|
|
11
12
|
headers: {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
[PRERENDER_REVALIDATE_HEADER]: revalidateId,
|
|
14
|
+
[ISR_HEADER]: "1",
|
|
14
15
|
},
|
|
15
16
|
});
|
|
16
17
|
},
|
package/dist/utils/cache.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { debug } from "../adapters/logger";
|
|
2
|
+
import { CACHE_TAGS_HEADER } from "./cacheHeaders";
|
|
2
3
|
import { compareSemver } from "./semver";
|
|
3
4
|
/**
|
|
4
5
|
*
|
|
@@ -90,8 +91,8 @@ export function getTagsFromValue(value) {
|
|
|
90
91
|
}
|
|
91
92
|
// The try catch is necessary for older version of next.js that may fail on this
|
|
92
93
|
try {
|
|
93
|
-
const cacheTags = value.meta?.headers?.[
|
|
94
|
-
delete value.meta?.headers?.[
|
|
94
|
+
const cacheTags = value.meta?.headers?.[CACHE_TAGS_HEADER]?.split(",") ?? [];
|
|
95
|
+
delete value.meta?.headers?.[CACHE_TAGS_HEADER];
|
|
95
96
|
return cacheTags;
|
|
96
97
|
}
|
|
97
98
|
catch (e) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { OutgoingHttpHeaders } from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* The name of the `cache-control` response header.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CACHE_CONTROL_HEADER = "cache-control";
|
|
6
|
+
/**
|
|
7
|
+
* The name of the header Next.js uses to report the cache status of a response.
|
|
8
|
+
*/
|
|
9
|
+
export declare const NEXTJS_CACHE_HEADER = "x-nextjs-cache";
|
|
10
|
+
/**
|
|
11
|
+
* The name of the header OpenNext uses to report the cache status of a response
|
|
12
|
+
* served by the cache interceptor.
|
|
13
|
+
*/
|
|
14
|
+
export declare const OPEN_NEXT_CACHE_HEADER = "x-opennext-cache";
|
|
15
|
+
/**
|
|
16
|
+
* The name of the header Next.js uses to store the tags of a cache entry.
|
|
17
|
+
*
|
|
18
|
+
* Note that `types/cache.ts` repeats the literal, on purpose: it is a types only
|
|
19
|
+
* module and importing a value into it would give it a runtime dependency.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CACHE_TAGS_HEADER = "x-next-cache-tags";
|
|
22
|
+
/**
|
|
23
|
+
* The name of the header OpenNext uses to mark a request as an ISR revalidation.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ISR_HEADER = "x-isr";
|
|
26
|
+
/**
|
|
27
|
+
* The name of the header Next.js uses to authorize a revalidation request.
|
|
28
|
+
*
|
|
29
|
+
* Carries the preview mode id from the prerender manifest.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
|
|
32
|
+
/**
|
|
33
|
+
* The `cache-control` OpenNext forces on error responses so that they are never cached.
|
|
34
|
+
*/
|
|
35
|
+
export declare const NO_STORE_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
36
|
+
/**
|
|
37
|
+
* Overrides `cache-control` on error responses so that they are not cached.
|
|
38
|
+
*
|
|
39
|
+
* Next.js attaches cacheable `cache-control` headers to some error responses, and it writes
|
|
40
|
+
* `notFound()` results for ISR routes to the incremental cache. Without this the CDN would hold
|
|
41
|
+
* on to a 404 or a 500 - a transient one, such as a row that is not replicated yet, would stay
|
|
42
|
+
* at the edge for as long as the route's revalidate period, up to a year for an SSG route.
|
|
43
|
+
*
|
|
44
|
+
* Only 404 and 500 are overridden. Any other error status is assumed to be produced by the
|
|
45
|
+
* application, which is then responsible for setting its own cache headers.
|
|
46
|
+
*
|
|
47
|
+
* Set `OPEN_NEXT_DANGEROUSLY_SET_ERROR_HEADERS=true` to opt out and keep the original headers.
|
|
48
|
+
*
|
|
49
|
+
* @param headers The response headers, mutated in place
|
|
50
|
+
* @param statusCode The status code of the response
|
|
51
|
+
*/
|
|
52
|
+
export declare function fixCacheControlForError(headers: OutgoingHttpHeaders | Record<string, string | string[]>, statusCode: number): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name of the `cache-control` response header.
|
|
3
|
+
*/
|
|
4
|
+
export const CACHE_CONTROL_HEADER = "cache-control";
|
|
5
|
+
/**
|
|
6
|
+
* The name of the header Next.js uses to report the cache status of a response.
|
|
7
|
+
*/
|
|
8
|
+
export const NEXTJS_CACHE_HEADER = "x-nextjs-cache";
|
|
9
|
+
/**
|
|
10
|
+
* The name of the header OpenNext uses to report the cache status of a response
|
|
11
|
+
* served by the cache interceptor.
|
|
12
|
+
*/
|
|
13
|
+
export const OPEN_NEXT_CACHE_HEADER = "x-opennext-cache";
|
|
14
|
+
/**
|
|
15
|
+
* The name of the header Next.js uses to store the tags of a cache entry.
|
|
16
|
+
*
|
|
17
|
+
* Note that `types/cache.ts` repeats the literal, on purpose: it is a types only
|
|
18
|
+
* module and importing a value into it would give it a runtime dependency.
|
|
19
|
+
*/
|
|
20
|
+
export const CACHE_TAGS_HEADER = "x-next-cache-tags";
|
|
21
|
+
/**
|
|
22
|
+
* The name of the header OpenNext uses to mark a request as an ISR revalidation.
|
|
23
|
+
*/
|
|
24
|
+
export const ISR_HEADER = "x-isr";
|
|
25
|
+
/**
|
|
26
|
+
* The name of the header Next.js uses to authorize a revalidation request.
|
|
27
|
+
*
|
|
28
|
+
* Carries the preview mode id from the prerender manifest.
|
|
29
|
+
*/
|
|
30
|
+
export const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
|
|
31
|
+
/**
|
|
32
|
+
* The `cache-control` OpenNext forces on error responses so that they are never cached.
|
|
33
|
+
*/
|
|
34
|
+
export const NO_STORE_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
35
|
+
/**
|
|
36
|
+
* Overrides `cache-control` on error responses so that they are not cached.
|
|
37
|
+
*
|
|
38
|
+
* Next.js attaches cacheable `cache-control` headers to some error responses, and it writes
|
|
39
|
+
* `notFound()` results for ISR routes to the incremental cache. Without this the CDN would hold
|
|
40
|
+
* on to a 404 or a 500 - a transient one, such as a row that is not replicated yet, would stay
|
|
41
|
+
* at the edge for as long as the route's revalidate period, up to a year for an SSG route.
|
|
42
|
+
*
|
|
43
|
+
* Only 404 and 500 are overridden. Any other error status is assumed to be produced by the
|
|
44
|
+
* application, which is then responsible for setting its own cache headers.
|
|
45
|
+
*
|
|
46
|
+
* Set `OPEN_NEXT_DANGEROUSLY_SET_ERROR_HEADERS=true` to opt out and keep the original headers.
|
|
47
|
+
*
|
|
48
|
+
* @param headers The response headers, mutated in place
|
|
49
|
+
* @param statusCode The status code of the response
|
|
50
|
+
*/
|
|
51
|
+
export function fixCacheControlForError(headers, statusCode) {
|
|
52
|
+
if (process.env.OPEN_NEXT_DANGEROUSLY_SET_ERROR_HEADERS === "true") {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (statusCode === 404 || statusCode === 500) {
|
|
56
|
+
headers[CACHE_CONTROL_HEADER] = NO_STORE_CACHE_CONTROL;
|
|
57
|
+
}
|
|
58
|
+
}
|