@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.
@@ -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?.["x-next-cache-tags"]
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["x-isr"] === "1",
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, false, maximumResponseBody);
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, false);
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
- "x-prerender-revalidate": prerenderManifest.preview.previewModeId,
27
- "x-isr": "1",
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["x-nextjs-cache"] !== "REVALIDATED") {
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?.["x-next-cache-tags"]) {
192
- fileData.headers["x-next-cache-tags"]
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["x-isr"] === "1",
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
- "cache-control": "private, no-cache, no-store, max-age=0, must-revalidate",
45
- "x-opennext-cache": "ERROR",
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
- "cache-control": `s-maxage=${sMaxAge}, stale-while-revalidate=${CACHE_ONE_MONTH}`,
81
- "x-opennext-cache": isStale ? "STALE" : "HIT",
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
- "cache-control": `s-maxage=${CACHE_ONE_YEAR}, stale-while-revalidate=${CACHE_ONE_MONTH}`,
87
- "x-opennext-cache": "HIT",
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
- // Sometimes other status codes can be cached, like 404. For these cases, we should return the correct status code
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["x-prerender-revalidate"]))
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: event.rewriteStatusCode ?? cachedData.value.meta?.status ?? 200,
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["x-isr"] &&
27
- headers["x-prerender-revalidate"] ===
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
- if (process.env.OPEN_NEXT_DANGEROUSLY_SET_ERROR_HEADERS === "true") {
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[CommonHeaders.CACHE_CONTROL] =
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[CommonHeaders.CACHE_CONTROL];
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[CommonHeaders.CACHE_CONTROL] = cacheControl.replace(/\bstale-while-revalidate(?!=)/, "stale-while-revalidate=2592000");
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[CommonHeaders.NEXT_CACHE] === "STALE") {
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[CommonHeaders.CACHE_CONTROL]?.match(sMaxAgeRegex);
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[CommonHeaders.NEXT_CACHE] === "REVALIDATED") {
325
- headers[CommonHeaders.CACHE_CONTROL] =
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[CommonHeaders.NEXT_CACHE] === "HIT" && _lastModified > 0) {
331
- debug("cache-control", headers[CommonHeaders.CACHE_CONTROL], _lastModified, Date.now());
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[CommonHeaders.CACHE_CONTROL] =
328
+ headers[CACHE_CONTROL_HEADER] =
338
329
  `s-maxage=${remainingTtl}, stale-while-revalidate=2592000`;
339
330
  }
340
331
  }
341
- if (headers[CommonHeaders.NEXT_CACHE] !== "STALE")
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[CommonHeaders.CACHE_CONTROL] =
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["x-isr"] === "1";
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": "private, no-cache, no-store, max-age=0, must-revalidate",
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
- if (process.env.OPEN_NEXT_DANGEROUSLY_SET_ERROR_HEADERS === "true") {
331
- return;
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
- "x-prerender-revalidate": revalidateId,
13
- "x-isr": "1",
13
+ [PRERENDER_REVALIDATE_HEADER]: revalidateId,
14
+ [ISR_HEADER]: "1",
14
15
  },
15
16
  });
16
17
  },
@@ -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?.["x-next-cache-tags"]?.split(",") ?? [];
94
- delete value.meta?.headers?.["x-next-cache-tags"];
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
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "4.1.1",
6
+ "version": "4.1.2",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },