@opennextjs/aws 3.7.2 → 3.7.3
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 +1 -0
- package/dist/adapters/cache.js +14 -7
- package/dist/adapters/composable-cache.js +20 -7
- package/dist/adapters/middleware.js +2 -1
- package/dist/build/generateOutput.js +1 -1
- package/dist/core/requestHandler.js +2 -1
- package/dist/core/routing/middleware.d.ts +1 -0
- package/dist/core/routing/middleware.js +7 -1
- package/dist/core/routing/util.d.ts +13 -0
- package/dist/core/routing/util.js +34 -1
- package/dist/core/routingHandler.d.ts +1 -0
- package/dist/core/routingHandler.js +4 -3
- package/dist/http/openNextResponse.d.ts +1 -1
- package/dist/http/openNextResponse.js +4 -1
- package/dist/overrides/wrappers/express-dev.js +6 -0
- package/dist/types/open-next.d.ts +2 -0
- package/dist/types/overrides.d.ts +5 -0
- package/package.json +1 -1
package/dist/adapters/cache.d.ts
CHANGED
package/dist/adapters/cache.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getTagsFromValue, hasBeenRevalidated, writeTags } from "../utils/cache";
|
|
2
2
|
import { isBinaryContentType } from "../utils/binary";
|
|
3
3
|
import { debug, error, warn } from "./logger";
|
|
4
|
+
export const SOFT_TAG_PREFIX = "_N_T_/";
|
|
4
5
|
function isFetchCache(options) {
|
|
5
6
|
if (typeof options === "boolean") {
|
|
6
7
|
return options;
|
|
@@ -34,18 +35,22 @@ export default class Cache {
|
|
|
34
35
|
return null;
|
|
35
36
|
const _tags = [...(tags ?? []), ...(softTags ?? [])];
|
|
36
37
|
const _lastModified = cachedEntry.lastModified ?? Date.now();
|
|
37
|
-
const _hasBeenRevalidated =
|
|
38
|
+
const _hasBeenRevalidated = cachedEntry.shouldBypassTagCache
|
|
39
|
+
? false
|
|
40
|
+
: await hasBeenRevalidated(key, _tags, cachedEntry);
|
|
38
41
|
if (_hasBeenRevalidated)
|
|
39
42
|
return null;
|
|
40
43
|
// For cases where we don't have tags, we need to ensure that the soft tags are not being revalidated
|
|
41
44
|
// We only need to check for the path as it should already contain all the tags
|
|
42
45
|
if ((tags ?? []).length === 0) {
|
|
43
46
|
// Then we need to find the path for the given key
|
|
44
|
-
const path = softTags?.find((tag) => tag.startsWith(
|
|
47
|
+
const path = softTags?.find((tag) => tag.startsWith(SOFT_TAG_PREFIX) &&
|
|
45
48
|
!tag.endsWith("layout") &&
|
|
46
49
|
!tag.endsWith("page"));
|
|
47
50
|
if (path) {
|
|
48
|
-
const hasPathBeenUpdated =
|
|
51
|
+
const hasPathBeenUpdated = cachedEntry.shouldBypassTagCache
|
|
52
|
+
? false
|
|
53
|
+
: await hasBeenRevalidated(path.replace(SOFT_TAG_PREFIX, ""), [], cachedEntry);
|
|
49
54
|
if (hasPathBeenUpdated) {
|
|
50
55
|
// In case the path has been revalidated, we don't want to use the fetch cache
|
|
51
56
|
return null;
|
|
@@ -73,7 +78,9 @@ export default class Cache {
|
|
|
73
78
|
const meta = cacheData.meta;
|
|
74
79
|
const tags = getTagsFromValue(cacheData);
|
|
75
80
|
const _lastModified = cachedEntry.lastModified ?? Date.now();
|
|
76
|
-
const _hasBeenRevalidated =
|
|
81
|
+
const _hasBeenRevalidated = cachedEntry.shouldBypassTagCache
|
|
82
|
+
? false
|
|
83
|
+
: await hasBeenRevalidated(key, tags, cachedEntry);
|
|
77
84
|
if (_hasBeenRevalidated)
|
|
78
85
|
return null;
|
|
79
86
|
const store = globalThis.__openNextAls.getStore();
|
|
@@ -274,11 +281,11 @@ export default class Cache {
|
|
|
274
281
|
tag,
|
|
275
282
|
}));
|
|
276
283
|
// If the tag is a soft tag, we should also revalidate the hard tags
|
|
277
|
-
if (tag.startsWith(
|
|
284
|
+
if (tag.startsWith(SOFT_TAG_PREFIX)) {
|
|
278
285
|
for (const path of paths) {
|
|
279
286
|
// We need to find all hard tags for a given path
|
|
280
287
|
const _tags = await globalThis.tagCache.getByPath(path);
|
|
281
|
-
const hardTags = _tags.filter((t) => !t.startsWith(
|
|
288
|
+
const hardTags = _tags.filter((t) => !t.startsWith(SOFT_TAG_PREFIX));
|
|
282
289
|
// For every hard tag, we need to find all paths and revalidate them
|
|
283
290
|
for (const hardTag of hardTags) {
|
|
284
291
|
const _paths = await globalThis.tagCache.getByTag(hardTag);
|
|
@@ -296,7 +303,7 @@ export default class Cache {
|
|
|
296
303
|
// This only applies to `revalidateTag`, not to `res.revalidate()`
|
|
297
304
|
const uniquePaths = Array.from(new Set(toInsert
|
|
298
305
|
// We need to filter fetch cache key as they are not in the CDN
|
|
299
|
-
.filter((t) => t.tag.startsWith(
|
|
306
|
+
.filter((t) => t.tag.startsWith(SOFT_TAG_PREFIX))
|
|
300
307
|
.map((t) => `/${t.path}`)));
|
|
301
308
|
if (uniquePaths.length > 0) {
|
|
302
309
|
await globalThis.cdnInvalidationHandler.invalidatePaths(uniquePaths.map((path) => ({
|
|
@@ -8,7 +8,13 @@ export default {
|
|
|
8
8
|
// We first check if we have a pending write for this cache key
|
|
9
9
|
// If we do, we return the pending promise instead of fetching the cache
|
|
10
10
|
if (pendingWritePromiseMap.has(cacheKey)) {
|
|
11
|
-
|
|
11
|
+
const stored = pendingWritePromiseMap.get(cacheKey);
|
|
12
|
+
if (stored) {
|
|
13
|
+
return stored.then((entry) => ({
|
|
14
|
+
...entry,
|
|
15
|
+
value: toReadableStream(entry.value),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
const result = await globalThis.incrementalCache.get(cacheKey, "composable");
|
|
14
20
|
if (!result?.value?.value) {
|
|
@@ -18,13 +24,17 @@ export default {
|
|
|
18
24
|
// We need to check if the tags associated with this entry has been revalidated
|
|
19
25
|
if (globalThis.tagCache.mode === "nextMode" &&
|
|
20
26
|
result.value.tags.length > 0) {
|
|
21
|
-
const hasBeenRevalidated =
|
|
27
|
+
const hasBeenRevalidated = result.shouldBypassTagCache
|
|
28
|
+
? false
|
|
29
|
+
: await globalThis.tagCache.hasBeenRevalidated(result.value.tags, result.lastModified);
|
|
22
30
|
if (hasBeenRevalidated)
|
|
23
31
|
return undefined;
|
|
24
32
|
}
|
|
25
33
|
else if (globalThis.tagCache.mode === "original" ||
|
|
26
34
|
globalThis.tagCache.mode === undefined) {
|
|
27
|
-
const hasBeenRevalidated =
|
|
35
|
+
const hasBeenRevalidated = result.shouldBypassTagCache
|
|
36
|
+
? false
|
|
37
|
+
: (await globalThis.tagCache.getLastModified(cacheKey, result.lastModified)) === -1;
|
|
28
38
|
if (hasBeenRevalidated)
|
|
29
39
|
return undefined;
|
|
30
40
|
}
|
|
@@ -39,14 +49,17 @@ export default {
|
|
|
39
49
|
}
|
|
40
50
|
},
|
|
41
51
|
async set(cacheKey, pendingEntry) {
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
const promiseEntry = pendingEntry.then(async (entry) => ({
|
|
53
|
+
...entry,
|
|
54
|
+
value: await fromReadableStream(entry.value),
|
|
55
|
+
}));
|
|
56
|
+
pendingWritePromiseMap.set(cacheKey, promiseEntry);
|
|
57
|
+
const entry = await promiseEntry.finally(() => {
|
|
44
58
|
pendingWritePromiseMap.delete(cacheKey);
|
|
45
59
|
});
|
|
46
|
-
const valueToStore = await fromReadableStream(entry.value);
|
|
47
60
|
await globalThis.incrementalCache.set(cacheKey, {
|
|
48
61
|
...entry,
|
|
49
|
-
value:
|
|
62
|
+
value: entry.value,
|
|
50
63
|
}, "composable");
|
|
51
64
|
if (globalThis.tagCache.mode === "original") {
|
|
52
65
|
const storedTags = await globalThis.tagCache.getByPath(cacheKey);
|
|
@@ -3,7 +3,7 @@ import { debug, error } from "../adapters/logger";
|
|
|
3
3
|
import { createGenericHandler } from "../core/createGenericHandler";
|
|
4
4
|
import { resolveAssetResolver, resolveIncrementalCache, resolveOriginResolver, resolveProxyRequest, resolveQueue, resolveTagCache, } from "../core/resolve";
|
|
5
5
|
import { constructNextUrl } from "../core/routing/util";
|
|
6
|
-
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, } from "../core/routingHandler";
|
|
6
|
+
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, } from "../core/routingHandler";
|
|
7
7
|
globalThis.internalFetch = fetch;
|
|
8
8
|
globalThis.__openNextAls = new AsyncLocalStorage();
|
|
9
9
|
const defaultHandler = async (internalEvent, options) => {
|
|
@@ -39,6 +39,7 @@ const defaultHandler = async (internalEvent, options) => {
|
|
|
39
39
|
[INTERNAL_HEADER_INITIAL_URL]: internalEvent.url,
|
|
40
40
|
[INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(result.resolvedRoutes),
|
|
41
41
|
[INTERNAL_EVENT_REQUEST_ID]: requestId,
|
|
42
|
+
[INTERNAL_HEADER_REWRITE_STATUS_CODE]: String(result.rewriteStatusCode),
|
|
42
43
|
},
|
|
43
44
|
},
|
|
44
45
|
isExternalRewrite: result.isExternalRewrite,
|
|
@@ -74,7 +74,7 @@ export async function generateOutput(options) {
|
|
|
74
74
|
Object.entries(config.functions ?? {}).forEach(async ([key, value]) => {
|
|
75
75
|
if (value.placement === "global") {
|
|
76
76
|
edgeFunctions[key] = {
|
|
77
|
-
bundle: `.open-next/functions/${key}`,
|
|
77
|
+
bundle: `.open-next/server-functions/${key}`,
|
|
78
78
|
handler: indexHandler,
|
|
79
79
|
...(await extractOverrideFn(value.override)),
|
|
80
80
|
};
|
|
@@ -5,7 +5,7 @@ import { NextConfig } from "../adapters/config/index";
|
|
|
5
5
|
import { debug, error } from "../adapters/logger";
|
|
6
6
|
import { patchAsyncStorage } from "./patchAsyncStorage";
|
|
7
7
|
import { constructNextUrl, convertRes, convertToQuery, convertToQueryString, createServerResponse, } from "./routing/util";
|
|
8
|
-
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
8
|
+
import routingHandler, { INTERNAL_EVENT_REQUEST_ID, INTERNAL_HEADER_REWRITE_STATUS_CODE, INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
|
|
9
9
|
import { requestHandler, setNextjsPrebundledReact } from "./util";
|
|
10
10
|
// This is used to identify requests in the cache
|
|
11
11
|
globalThis.__openNextAls = new AsyncLocalStorage();
|
|
@@ -37,6 +37,7 @@ export async function openNextHandler(internalEvent, options) {
|
|
|
37
37
|
resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
|
|
38
38
|
? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
|
|
39
39
|
: [],
|
|
40
|
+
rewriteStatusCode: Number.parseInt(initialHeaders[INTERNAL_HEADER_REWRITE_STATUS_CODE]),
|
|
40
41
|
};
|
|
41
42
|
let routingResult = {
|
|
42
43
|
internalEvent,
|
|
@@ -2,6 +2,7 @@ import type { InternalEvent, InternalResult } from "../../types/open-next.js";
|
|
|
2
2
|
type MiddlewareEvent = InternalEvent & {
|
|
3
3
|
responseHeaders?: Record<string, string | string[]>;
|
|
4
4
|
isExternalRewrite?: boolean;
|
|
5
|
+
rewriteStatusCode?: number;
|
|
5
6
|
};
|
|
6
7
|
type Middleware = (request: Request) => Response | Promise<Response>;
|
|
7
8
|
type MiddlewareLoader = () => Promise<{
|
|
@@ -2,10 +2,11 @@ import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManif
|
|
|
2
2
|
import { emptyReadableStream } from "../../utils/stream.js";
|
|
3
3
|
import { getQueryFromSearchParams } from "../../overrides/converters/utils.js";
|
|
4
4
|
import { localizePath } from "./i18n/index.js";
|
|
5
|
-
import { convertBodyToReadableStream, getMiddlewareMatch, isExternal, } from "./util.js";
|
|
5
|
+
import { convertBodyToReadableStream, getMiddlewareMatch, isExternal, normalizeLocationHeader, } from "./util.js";
|
|
6
6
|
const middlewareManifest = MiddlewareManifest;
|
|
7
7
|
const functionsConfigManifest = FunctionsConfigManifest;
|
|
8
8
|
const middleMatch = getMiddlewareMatch(middlewareManifest, functionsConfigManifest);
|
|
9
|
+
const REDIRECTS = new Set([301, 302, 303, 307, 308]);
|
|
9
10
|
function defaultMiddlewareLoader() {
|
|
10
11
|
// @ts-expect-error - This is bundled
|
|
11
12
|
return import("./middleware.mjs");
|
|
@@ -94,6 +95,10 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
94
95
|
? [...resHeaders[key], value]
|
|
95
96
|
: [value];
|
|
96
97
|
}
|
|
98
|
+
else if (REDIRECTS.has(statusCode) &&
|
|
99
|
+
key.toLowerCase() === "location") {
|
|
100
|
+
resHeaders[key] = normalizeLocationHeader(value, internalEvent.url);
|
|
101
|
+
}
|
|
97
102
|
else {
|
|
98
103
|
resHeaders[key] = value;
|
|
99
104
|
}
|
|
@@ -146,5 +151,6 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
146
151
|
cookies: internalEvent.cookies,
|
|
147
152
|
remoteAddress: internalEvent.remoteAddress,
|
|
148
153
|
isExternalRewrite,
|
|
154
|
+
rewriteStatusCode: statusCode,
|
|
149
155
|
};
|
|
150
156
|
}
|
|
@@ -104,3 +104,16 @@ export declare function fixISRHeaders(headers: OutgoingHttpHeaders): void;
|
|
|
104
104
|
*/
|
|
105
105
|
export declare function createServerResponse(routingResult: RoutingResult, headers: Record<string, string | string[] | undefined>, responseStream?: StreamCreator): OpenNextNodeResponse;
|
|
106
106
|
export declare function invalidateCDNOnRequest(params: RoutingResult, headers: OutgoingHttpHeaders): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Normalizes the Location header to either be a relative path or a full URL.
|
|
109
|
+
* If the Location header is relative to the origin, it will return a relative path.
|
|
110
|
+
* If it is an absolute URL, it will return the full URL.
|
|
111
|
+
* Redirects from Next config query parameters are encoded using `stringifyQs`
|
|
112
|
+
* Redirects from the middleware the query parameters are not encoded.
|
|
113
|
+
*
|
|
114
|
+
* @param location The Location header value
|
|
115
|
+
* @param baseUrl The base URL to use for relative paths (i.e the original request URL)
|
|
116
|
+
* @param encodeQuery Optional flag to indicate if query parameters should be encoded in the Location header
|
|
117
|
+
* @returns An absolute or relative Location header value
|
|
118
|
+
*/
|
|
119
|
+
export declare function normalizeLocationHeader(location: string, baseUrl: string, encodeQuery?: boolean): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import { parse as parseQs, stringify as stringifyQs } from "node:querystring";
|
|
2
3
|
import { Readable } from "node:stream";
|
|
3
4
|
import { BuildId, HtmlPages, NextConfig } from "../../adapters/config/index.js";
|
|
4
5
|
import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
|
|
@@ -338,7 +339,7 @@ export function createServerResponse(routingResult, headers, responseStream) {
|
|
|
338
339
|
}, async (_headers) => {
|
|
339
340
|
await revalidateIfRequired(internalEvent.headers.host, internalEvent.rawPath, _headers);
|
|
340
341
|
await invalidateCDNOnRequest(routingResult, _headers);
|
|
341
|
-
}, responseStream, headers);
|
|
342
|
+
}, responseStream, headers, routingResult.rewriteStatusCode);
|
|
342
343
|
}
|
|
343
344
|
// This function is used only for `res.revalidate()`
|
|
344
345
|
export async function invalidateCDNOnRequest(params, headers) {
|
|
@@ -356,3 +357,35 @@ export async function invalidateCDNOnRequest(params, headers) {
|
|
|
356
357
|
]);
|
|
357
358
|
}
|
|
358
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Normalizes the Location header to either be a relative path or a full URL.
|
|
362
|
+
* If the Location header is relative to the origin, it will return a relative path.
|
|
363
|
+
* If it is an absolute URL, it will return the full URL.
|
|
364
|
+
* Redirects from Next config query parameters are encoded using `stringifyQs`
|
|
365
|
+
* Redirects from the middleware the query parameters are not encoded.
|
|
366
|
+
*
|
|
367
|
+
* @param location The Location header value
|
|
368
|
+
* @param baseUrl The base URL to use for relative paths (i.e the original request URL)
|
|
369
|
+
* @param encodeQuery Optional flag to indicate if query parameters should be encoded in the Location header
|
|
370
|
+
* @returns An absolute or relative Location header value
|
|
371
|
+
*/
|
|
372
|
+
export function normalizeLocationHeader(location, baseUrl, encodeQuery = false) {
|
|
373
|
+
if (!URL.canParse(location)) {
|
|
374
|
+
// If the location is not a valid URL, return it as-is
|
|
375
|
+
return location;
|
|
376
|
+
}
|
|
377
|
+
const locationURL = new URL(location);
|
|
378
|
+
const origin = new URL(baseUrl).origin;
|
|
379
|
+
let search = locationURL.search;
|
|
380
|
+
// If encodeQuery is true, we need to encode the query parameters
|
|
381
|
+
// We could have used URLSearchParams, but that doesn't match what Next does.
|
|
382
|
+
if (encodeQuery && search) {
|
|
383
|
+
search = `?${stringifyQs(parseQs(search.slice(1)))}`;
|
|
384
|
+
}
|
|
385
|
+
const href = `${locationURL.origin}${locationURL.pathname}${search}${locationURL.hash}`;
|
|
386
|
+
// The URL is relative if the origin is the same as the base URL's origin
|
|
387
|
+
if (locationURL.origin === origin) {
|
|
388
|
+
return href.slice(origin.length);
|
|
389
|
+
}
|
|
390
|
+
return href;
|
|
391
|
+
}
|
|
@@ -6,6 +6,7 @@ export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
|
|
|
6
6
|
export declare const INTERNAL_HEADER_INITIAL_URL = "x-opennext-initial-url";
|
|
7
7
|
export declare const INTERNAL_HEADER_LOCALE = "x-opennext-locale";
|
|
8
8
|
export declare const INTERNAL_HEADER_RESOLVED_ROUTES = "x-opennext-resolved-routes";
|
|
9
|
+
export declare const INTERNAL_HEADER_REWRITE_STATUS_CODE = "x-opennext-rewrite-status-code";
|
|
9
10
|
export declare const INTERNAL_EVENT_REQUEST_ID = "x-opennext-request-id";
|
|
10
11
|
export default function routingHandler(event: InternalEvent, { assetResolver }: {
|
|
11
12
|
assetResolver?: AssetResolver;
|
|
@@ -5,13 +5,14 @@ import { detectLocale } from "./routing/i18n";
|
|
|
5
5
|
import { fixDataPage, getNextConfigHeaders, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
|
|
6
6
|
import { handleMiddleware } from "./routing/middleware";
|
|
7
7
|
import { dynamicRouteMatcher, staticRouteMatcher, } from "./routing/routeMatcher";
|
|
8
|
-
import { constructNextUrl } from "./routing/util";
|
|
8
|
+
import { constructNextUrl, normalizeLocationHeader } from "./routing/util";
|
|
9
9
|
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
|
|
10
10
|
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
|
|
11
11
|
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
|
|
12
12
|
export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
|
|
13
13
|
export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
|
|
14
14
|
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
|
|
15
|
+
export const INTERNAL_HEADER_REWRITE_STATUS_CODE = `${INTERNAL_HEADER_PREFIX}rewrite-status-code`;
|
|
15
16
|
export const INTERNAL_EVENT_REQUEST_ID = `${INTERNAL_HEADER_PREFIX}request-id`;
|
|
16
17
|
// Geolocation headers starting from Nextjs 15
|
|
17
18
|
// See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
|
|
@@ -66,8 +67,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
66
67
|
const redirect = handleRedirects(eventOrResult, RoutesManifest.redirects);
|
|
67
68
|
if (redirect) {
|
|
68
69
|
// We need to encode the value in the Location header to make sure it is valid according to RFC
|
|
69
|
-
|
|
70
|
-
redirect.headers.Location = new URL(redirect.headers.Location).href;
|
|
70
|
+
redirect.headers.Location = normalizeLocationHeader(redirect.headers.Location, event.url, true);
|
|
71
71
|
debug("redirect", redirect);
|
|
72
72
|
return redirect;
|
|
73
73
|
}
|
|
@@ -171,6 +171,7 @@ export default async function routingHandler(event, { assetResolver }) {
|
|
|
171
171
|
locale: NextConfig.i18n
|
|
172
172
|
? detectLocale(eventOrResult, NextConfig.i18n)
|
|
173
173
|
: undefined,
|
|
174
|
+
rewriteStatusCode: middlewareEventOrResult.rewriteStatusCode,
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
177
|
catch (e) {
|
|
@@ -34,7 +34,7 @@ export declare class OpenNextNodeResponse extends Transform implements ServerRes
|
|
|
34
34
|
socket: Socket | null;
|
|
35
35
|
setTimeout(_msecs: number, _callback?: (() => void) | undefined): this;
|
|
36
36
|
addTrailers(_headers: OutgoingHttpHeaders | readonly [string, string][]): void;
|
|
37
|
-
constructor(fixHeaders: (headers: OutgoingHttpHeaders) => void, onEnd: (headers: OutgoingHttpHeaders) => Promise<void>, streamCreator?: StreamCreator | undefined, initialHeaders?: OutgoingHttpHeaders | undefined);
|
|
37
|
+
constructor(fixHeaders: (headers: OutgoingHttpHeaders) => void, onEnd: (headers: OutgoingHttpHeaders) => Promise<void>, streamCreator?: StreamCreator | undefined, initialHeaders?: OutgoingHttpHeaders | undefined, statusCode?: number);
|
|
38
38
|
get originalResponse(): this;
|
|
39
39
|
get finished(): boolean;
|
|
40
40
|
setHeader(name: string, value: string | string[]): this;
|
|
@@ -51,12 +51,15 @@ export class OpenNextNodeResponse extends Transform {
|
|
|
51
51
|
addTrailers(_headers) {
|
|
52
52
|
throw new Error(CANNOT_BE_USED);
|
|
53
53
|
}
|
|
54
|
-
constructor(fixHeaders, onEnd, streamCreator, initialHeaders) {
|
|
54
|
+
constructor(fixHeaders, onEnd, streamCreator, initialHeaders, statusCode) {
|
|
55
55
|
super();
|
|
56
56
|
this.fixHeaders = fixHeaders;
|
|
57
57
|
this.onEnd = onEnd;
|
|
58
58
|
this.streamCreator = streamCreator;
|
|
59
59
|
this.initialHeaders = initialHeaders;
|
|
60
|
+
if (statusCode !== undefined) {
|
|
61
|
+
this.statusCode = statusCode;
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
64
|
// Necessary for next 12
|
|
62
65
|
// We might have to implement all the methods here
|
|
@@ -20,6 +20,12 @@ const wrapper = async (handler, converter) => {
|
|
|
20
20
|
await imageHandler(internalEvent, { streamCreator });
|
|
21
21
|
});
|
|
22
22
|
app.all("*paths", async (req, res) => {
|
|
23
|
+
if (req.protocol === "http" && req.hostname === "localhost") {
|
|
24
|
+
// This is used internally by Next.js during redirects in server actions. We need to set it to the origin of the request.
|
|
25
|
+
process.env.__NEXT_PRIVATE_ORIGIN = `${req.protocol}://${req.hostname}`;
|
|
26
|
+
// This is to make `next-auth` and other libraries that rely on this header to work locally out of the box.
|
|
27
|
+
req.headers["x-forwarded-proto"] = req.protocol;
|
|
28
|
+
}
|
|
23
29
|
const internalEvent = await converter.convertFrom(req);
|
|
24
30
|
const streamCreator = {
|
|
25
31
|
writeHeaders: (prelude) => {
|
|
@@ -20,6 +20,7 @@ export type InternalResult = {
|
|
|
20
20
|
headers: Record<string, string | string[]>;
|
|
21
21
|
body: ReadableStream;
|
|
22
22
|
isBase64Encoded: boolean;
|
|
23
|
+
rewriteStatusCode?: number;
|
|
23
24
|
} & BaseEventOrResult<"core">;
|
|
24
25
|
export interface StreamCreator {
|
|
25
26
|
writeHeaders(prelude: {
|
|
@@ -92,6 +93,7 @@ export interface RoutingResult {
|
|
|
92
93
|
initialURL: string;
|
|
93
94
|
locale?: string;
|
|
94
95
|
resolvedRoutes: ResolvedRoute[];
|
|
96
|
+
rewriteStatusCode?: number;
|
|
95
97
|
}
|
|
96
98
|
export interface MiddlewareResult extends RoutingResult, BaseEventOrResult<"middleware"> {
|
|
97
99
|
}
|
|
@@ -63,6 +63,11 @@ export type CachedFetchValue = {
|
|
|
63
63
|
export type WithLastModified<T> = {
|
|
64
64
|
lastModified?: number;
|
|
65
65
|
value?: T;
|
|
66
|
+
/**
|
|
67
|
+
* If set to true, we will not check the tag cache for this entry.
|
|
68
|
+
* `revalidateTag` and `revalidatePath` may not work as expected.
|
|
69
|
+
*/
|
|
70
|
+
shouldBypassTagCache?: boolean;
|
|
66
71
|
};
|
|
67
72
|
export type CacheEntryType = Extension;
|
|
68
73
|
export type CacheValue<CacheType extends CacheEntryType> = (CacheType extends "fetch" ? CachedFetchValue : CacheType extends "cache" ? CachedFile : StoredComposableCacheEntry) & {
|