@opennextjs/aws 3.3.0 → 3.3.1

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.
@@ -58,8 +58,7 @@ interface CacheHandlerValue {
58
58
  cacheState?: string;
59
59
  value: IncrementalCacheValue | null;
60
60
  }
61
- export declare function hasCacheExtension(key: string): boolean;
62
- export default class S3Cache {
61
+ export default class Cache {
63
62
  get(key: string, options?: boolean | {
64
63
  fetchCache?: boolean;
65
64
  kindHint?: "app" | "pages" | "fetch";
@@ -1,10 +1,5 @@
1
1
  import { isBinaryContentType } from "../utils/binary";
2
2
  import { debug, error, warn } from "./logger";
3
- /** Beginning single backslash is intentional, to look for the dot + the extension. Do not escape it again. */
4
- const CACHE_EXTENSION_REGEX = /\.(cache|fetch)$/;
5
- export function hasCacheExtension(key) {
6
- return CACHE_EXTENSION_REGEX.test(key);
7
- }
8
3
  function isFetchCache(options) {
9
4
  if (typeof options === "boolean") {
10
5
  return options;
@@ -17,7 +12,7 @@ function isFetchCache(options) {
17
12
  return false;
18
13
  }
19
14
  // We need to use globalThis client here as this class can be defined at load time in next 12 but client is not available at load time
20
- export default class S3Cache {
15
+ export default class Cache {
21
16
  async get(key,
22
17
  // fetchCache is for next 13.5 and above, kindHint is for next 14 and above and boolean is for earlier versions
23
18
  options) {
@@ -34,7 +29,6 @@ export default class S3Cache {
34
29
  debug("get fetch cache", { key, softTags, tags });
35
30
  try {
36
31
  const { value, lastModified } = await globalThis.incrementalCache.get(key, true);
37
- // const { Body, LastModified } = await this.getS3Object(key, "fetch");
38
32
  const _lastModified = await globalThis.tagCache.getLastModified(key, lastModified);
39
33
  if (_lastModified === -1) {
40
34
  // If some tags are stale we need to force revalidation
@@ -71,10 +65,6 @@ export default class S3Cache {
71
65
  async getIncrementalCache(key) {
72
66
  try {
73
67
  const { value: cacheData, lastModified } = await globalThis.incrementalCache.get(key, false);
74
- // const { Body, LastModified } = await this.getS3Object(key, "cache");
75
- // const cacheData = JSON.parse(
76
- // (await Body?.transformToString()) ?? "{}",
77
- // ) as S3CachedFile;
78
68
  const meta = cacheData?.meta;
79
69
  const _lastModified = await globalThis.tagCache.getLastModified(key, lastModified);
80
70
  if (_lastModified === -1) {
@@ -25,3 +25,5 @@ export declare const ConfigHeaders: import("../../types/next-types").Header[] |
25
25
  export declare const PrerenderManifest: import("../../types/next-types").PrerenderManifest;
26
26
  export declare const AppPathsManifestKeys: string[];
27
27
  export declare const MiddlewareManifest: import("../../types/next-types").MiddlewareManifest;
28
+ export declare const AppPathsManifest: Record<string, string>;
29
+ export declare const AppPathRoutesManifest: Record<string, string>;
@@ -1,8 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { debug } from "../logger";
3
- import { loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest,
4
- // loadPublicAssets,
5
- loadRoutesManifest, } from "./util.js";
3
+ import { loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, loadRoutesManifest, } from "./util.js";
6
4
  export const NEXT_DIR = path.join(__dirname, ".next");
7
5
  export const OPEN_NEXT_DIR = path.join(__dirname, ".open-next");
8
6
  debug({ NEXT_DIR, OPEN_NEXT_DIR });
@@ -19,3 +17,6 @@ export const AppPathsManifestKeys =
19
17
  /* @__PURE__ */ loadAppPathsManifestKeys(NEXT_DIR);
20
18
  export const MiddlewareManifest =
21
19
  /* @__PURE__ */ loadMiddlewareManifest(NEXT_DIR);
20
+ export const AppPathsManifest = /* @__PURE__ */ loadAppPathsManifest(NEXT_DIR);
21
+ export const AppPathRoutesManifest =
22
+ /* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR);
@@ -24,5 +24,7 @@ export declare function loadRoutesManifest(nextDir: string): {
24
24
  };
25
25
  export declare function loadConfigHeaders(nextDir: string): import("../../types/next-types").Header[] | undefined;
26
26
  export declare function loadPrerenderManifest(nextDir: string): PrerenderManifest;
27
+ export declare function loadAppPathsManifest(nextDir: string): Record<string, string>;
28
+ export declare function loadAppPathRoutesManifest(nextDir: string): Record<string, string>;
27
29
  export declare function loadAppPathsManifestKeys(nextDir: string): string[];
28
30
  export declare function loadMiddlewareManifest(nextDir: string): MiddlewareManifest;
@@ -60,12 +60,22 @@ export function loadPrerenderManifest(nextDir) {
60
60
  const json = fs.readFileSync(filePath, "utf-8");
61
61
  return JSON.parse(json);
62
62
  }
63
- export function loadAppPathsManifestKeys(nextDir) {
63
+ export function loadAppPathsManifest(nextDir) {
64
64
  const appPathsManifestPath = path.join(nextDir, "server", "app-paths-manifest.json");
65
65
  const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
66
66
  ? fs.readFileSync(appPathsManifestPath, "utf-8")
67
67
  : "{}";
68
- const appPathsManifest = JSON.parse(appPathsManifestJson);
68
+ return JSON.parse(appPathsManifestJson);
69
+ }
70
+ export function loadAppPathRoutesManifest(nextDir) {
71
+ const appPathRoutesManifestPath = path.join(nextDir, "app-path-routes-manifest.json");
72
+ if (fs.existsSync(appPathRoutesManifestPath)) {
73
+ return JSON.parse(fs.readFileSync(appPathRoutesManifestPath, "utf-8"));
74
+ }
75
+ return {};
76
+ }
77
+ export function loadAppPathsManifestKeys(nextDir) {
78
+ const appPathsManifest = loadAppPathsManifest(nextDir);
69
79
  return Object.keys(appPathsManifest).map((key) => {
70
80
  // Remove parallel route
71
81
  let cleanedKey = key.replace(/\/@[^\/]+/g, "");
@@ -2,7 +2,7 @@ import { runWithOpenNextRequestContext } from "../utils/promise";
2
2
  import { debug, error } from "../adapters/logger";
3
3
  import { createGenericHandler } from "../core/createGenericHandler";
4
4
  import { resolveIncrementalCache, resolveOriginResolver, resolveProxyRequest, resolveQueue, resolveTagCache, } from "../core/resolve";
5
- import routingHandler from "../core/routingHandler";
5
+ import routingHandler, { INTERNAL_HEADER_INITIAL_PATH, INTERNAL_HEADER_RESOLVED_ROUTES, } from "../core/routingHandler";
6
6
  globalThis.internalFetch = fetch;
7
7
  globalThis.__openNextAls = new AsyncLocalStorage();
8
8
  const defaultHandler = async (internalEvent) => {
@@ -22,10 +22,19 @@ const defaultHandler = async (internalEvent) => {
22
22
  const origin = await originResolver.resolve(result.internalEvent.rawPath);
23
23
  return {
24
24
  type: "middleware",
25
- internalEvent: result.internalEvent,
25
+ internalEvent: {
26
+ ...result.internalEvent,
27
+ headers: {
28
+ ...result.internalEvent.headers,
29
+ [INTERNAL_HEADER_INITIAL_PATH]: internalEvent.rawPath,
30
+ [INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(result.resolvedRoutes) ?? "[]",
31
+ },
32
+ },
26
33
  isExternalRewrite: result.isExternalRewrite,
27
34
  origin,
28
35
  isISR: result.isISR,
36
+ initialPath: result.initialPath,
37
+ resolvedRoutes: result.resolvedRoutes,
29
38
  };
30
39
  }
31
40
  try {
@@ -45,6 +54,8 @@ const defaultHandler = async (internalEvent) => {
45
54
  isExternalRewrite: false,
46
55
  origin: false,
47
56
  isISR: result.isISR,
57
+ initialPath: result.internalEvent.rawPath,
58
+ resolvedRoutes: [{ route: "/500", type: "page" }],
48
59
  };
49
60
  }
50
61
  }
@@ -1,6 +1,10 @@
1
1
  //TODO: We should probably move all the utils to a separate location
2
2
  export function setNodeEnv() {
3
- process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
3
+ // Note: we create a `processEnv` variable instead of just using `process.env` directly
4
+ // because build tools can substitute `process.env.NODE_ENV` on build making
5
+ // assignments such as `process.env.NODE_ENV = ...` problematic
6
+ const processEnv = process.env;
7
+ processEnv.NODE_ENV = process.env.NODE_ENV ?? "production";
4
8
  }
5
9
  export function generateUniqueId() {
6
10
  return Math.random().toString(36).slice(2, 8);
@@ -109,10 +109,29 @@ export function createCacheAssets(options) {
109
109
  };
110
110
  fs.writeFileSync(cacheFilePath, JSON.stringify(cacheFileContent));
111
111
  });
112
+ // We need to traverse the cache to find every .meta file
113
+ const metaFiles = [];
114
+ // Copy fetch-cache to cache folder
115
+ const fetchCachePath = path.join(appBuildOutputPath, ".next/cache/fetch-cache");
116
+ if (fs.existsSync(fetchCachePath)) {
117
+ const fetchOutputPath = path.join(outputDir, "cache", "__fetch", buildId);
118
+ fs.mkdirSync(fetchOutputPath, { recursive: true });
119
+ fs.cpSync(fetchCachePath, fetchOutputPath, { recursive: true });
120
+ buildHelper.traverseFiles(fetchCachePath, () => true, ({ absolutePath, relativePath }) => {
121
+ const fileContent = fs.readFileSync(absolutePath, "utf8");
122
+ const fileData = JSON.parse(fileContent);
123
+ fileData?.tags?.forEach((tag) => {
124
+ metaFiles.push({
125
+ tag: { S: path.posix.join(buildId, tag) },
126
+ path: {
127
+ S: path.posix.join(buildId, relativePath),
128
+ },
129
+ revalidatedAt: { N: "1" },
130
+ });
131
+ });
132
+ });
133
+ }
112
134
  if (!options.config.dangerous?.disableTagCache) {
113
- // Generate dynamodb data
114
- // We need to traverse the cache to find every .meta file
115
- const metaFiles = [];
116
135
  // Compute dynamodb cache data
117
136
  // Traverse files inside cache to find all meta files and cache tags associated with them
118
137
  buildHelper.traverseFiles(outputPath, ({ absolutePath }) => absolutePath.endsWith(".meta"), ({ absolutePath, relativePath }) => {
@@ -134,26 +153,6 @@ export function createCacheAssets(options) {
134
153
  });
135
154
  }
136
155
  });
137
- // Copy fetch-cache to cache folder
138
- const fetchCachePath = path.join(appBuildOutputPath, ".next/cache/fetch-cache");
139
- if (fs.existsSync(fetchCachePath)) {
140
- const fetchOutputPath = path.join(outputDir, "cache", "__fetch", buildId);
141
- fs.mkdirSync(fetchOutputPath, { recursive: true });
142
- fs.cpSync(fetchCachePath, fetchOutputPath, { recursive: true });
143
- buildHelper.traverseFiles(fetchCachePath, () => true, ({ absolutePath, relativePath }) => {
144
- const fileContent = fs.readFileSync(absolutePath, "utf8");
145
- const fileData = JSON.parse(fileContent);
146
- fileData?.tags?.forEach((tag) => {
147
- metaFiles.push({
148
- tag: { S: path.posix.join(buildId, tag) },
149
- path: {
150
- S: path.posix.join(buildId, relativePath),
151
- },
152
- revalidatedAt: { N: "1" },
153
- });
154
- });
155
- });
156
- }
157
156
  if (metaFiles.length > 0) {
158
157
  useTagCache = true;
159
158
  const providerPath = path.join(outputDir, "dynamodb-provider");
@@ -2,14 +2,15 @@ import type { MiddlewareInfo } from "../../types/next-types";
2
2
  import type { IncludedConverter, IncludedOriginResolver, LazyLoadedOverride, OverrideOptions, SplittedFunctionOptions } from "../../types/open-next";
3
3
  import type { OriginResolver } from "../../types/overrides.js";
4
4
  import { type BuildOptions } from "../helper.js";
5
+ type Override = OverrideOptions & {
6
+ originResolver?: LazyLoadedOverride<OriginResolver> | IncludedOriginResolver;
7
+ };
5
8
  interface BuildEdgeBundleOptions {
6
9
  middlewareInfo?: MiddlewareInfo;
7
10
  entrypoint: string;
8
11
  outfile: string;
9
12
  options: BuildOptions;
10
- overrides?: OverrideOptions & {
11
- originResolver?: LazyLoadedOverride<OriginResolver> | IncludedOriginResolver;
12
- };
13
+ overrides?: Override;
13
14
  defaultConverter?: IncludedConverter;
14
15
  additionalInject?: string;
15
16
  includeCache?: boolean;
@@ -10,6 +10,11 @@ import { isEdgeRuntime } from "../helper.js";
10
10
  import { copyOpenNextConfig, esbuildAsync } from "../helper.js";
11
11
  export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, options, defaultConverter, overrides, additionalInject, includeCache, additionalExternals, onlyBuildOnce, name, }) {
12
12
  const isInCloudfare = await isEdgeRuntime(overrides);
13
+ function override(target) {
14
+ return typeof overrides?.[target] === "string"
15
+ ? overrides[target]
16
+ : undefined;
17
+ }
13
18
  await esbuildAsync({
14
19
  entryPoints: [entrypoint],
15
20
  bundle: true,
@@ -20,31 +25,17 @@ export async function buildEdgeBundle({ middlewareInfo, entrypoint, outfile, opt
20
25
  plugins: [
21
26
  openNextResolvePlugin({
22
27
  overrides: {
23
- wrapper: typeof overrides?.wrapper === "string"
24
- ? overrides.wrapper
25
- : "aws-lambda",
26
- converter: typeof overrides?.converter === "string"
27
- ? overrides.converter
28
- : defaultConverter,
28
+ wrapper: override("wrapper") ?? "aws-lambda",
29
+ converter: override("converter") ?? defaultConverter,
29
30
  ...(includeCache
30
31
  ? {
31
- tagCache: typeof overrides?.tagCache === "string"
32
- ? overrides.tagCache
33
- : "dynamodb-lite",
34
- incrementalCache: typeof overrides?.incrementalCache === "string"
35
- ? overrides.incrementalCache
36
- : "s3-lite",
37
- queue: typeof overrides?.queue === "string"
38
- ? overrides.queue
39
- : "sqs-lite",
32
+ tagCache: override("tagCache") ?? "dynamodb-lite",
33
+ incrementalCache: override("incrementalCache") ?? "s3-lite",
34
+ queue: override("queue") ?? "sqs-lite",
40
35
  }
41
36
  : {}),
42
- originResolver: typeof overrides?.originResolver === "string"
43
- ? overrides.originResolver
44
- : "pattern-env",
45
- proxyExternalRequest: typeof overrides?.proxyExternalRequest === "string"
46
- ? overrides.proxyExternalRequest
47
- : "node",
37
+ originResolver: override("originResolver") ?? "pattern-env",
38
+ proxyExternalRequest: override("proxyExternalRequest") ?? "node",
48
39
  },
49
40
  fnName: name,
50
41
  }),
@@ -4,23 +4,32 @@ import { runWithOpenNextRequestContext } from "../utils/promise";
4
4
  import { debug, error, warn } from "../adapters/logger";
5
5
  import { patchAsyncStorage } from "./patchAsyncStorage";
6
6
  import { convertRes, createServerResponse } from "./routing/util";
7
- import routingHandler, { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
7
+ import routingHandler, { INTERNAL_HEADER_INITIAL_PATH, INTERNAL_HEADER_RESOLVED_ROUTES, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_HEADER_PREFIX_LEN, } from "./routingHandler";
8
8
  import { requestHandler, setNextjsPrebundledReact } from "./util";
9
9
  // This is used to identify requests in the cache
10
10
  globalThis.__openNextAls = new AsyncLocalStorage();
11
11
  patchAsyncStorage();
12
12
  export async function openNextHandler(internalEvent, responseStreaming) {
13
+ const initialHeaders = internalEvent.headers;
13
14
  // We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
14
- return runWithOpenNextRequestContext({ isISRRevalidation: internalEvent.headers["x-isr"] === "1" }, async () => {
15
- if (internalEvent.headers["x-forwarded-host"]) {
16
- internalEvent.headers.host = internalEvent.headers["x-forwarded-host"];
15
+ return runWithOpenNextRequestContext({ isISRRevalidation: initialHeaders["x-isr"] === "1" }, async () => {
16
+ if (initialHeaders["x-forwarded-host"]) {
17
+ initialHeaders.host = initialHeaders["x-forwarded-host"];
17
18
  }
18
19
  debug("internalEvent", internalEvent);
20
+ // These 2 will get overwritten by the routing handler if not using an external middleware
21
+ const internalHeaders = {
22
+ initialPath: initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
23
+ resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
24
+ ? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
25
+ : [],
26
+ };
19
27
  let routingResult = {
20
28
  internalEvent,
21
29
  isExternalRewrite: false,
22
30
  origin: false,
23
31
  isISR: false,
32
+ ...internalHeaders,
24
33
  };
25
34
  //#override withRouting
26
35
  try {
@@ -65,6 +74,8 @@ export async function openNextHandler(internalEvent, responseStreaming) {
65
74
  isExternalRewrite: false,
66
75
  isISR: false,
67
76
  origin: false,
77
+ initialPath: internalEvent.rawPath,
78
+ resolvedRoutes: [{ route: "/500", type: "page" }],
68
79
  };
69
80
  }
70
81
  }
@@ -0,0 +1,10 @@
1
+ import type { RouteType } from "../../types/open-next";
2
+ export declare const apiPrefix: string;
3
+ export declare const staticRouteMatcher: (path: string) => {
4
+ route: string;
5
+ type: RouteType;
6
+ }[];
7
+ export declare const dynamicRouteMatcher: (path: string) => {
8
+ route: string;
9
+ type: RouteType;
10
+ }[];
@@ -0,0 +1,45 @@
1
+ import { AppPathRoutesManifest, RoutesManifest } from "../../adapters/config/index";
2
+ // Add the locale prefix to the regex so we correctly match the rawPath
3
+ const optionalLocalePrefixRegex = `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`;
4
+ // Add the basepath prefix to the regex so we correctly match the rawPath
5
+ const optionalBasepathPrefixRegex = RoutesManifest.basePath
6
+ ? `^${RoutesManifest.basePath}/?`
7
+ : "^/";
8
+ // Add the basePath prefix to the api routes
9
+ export const apiPrefix = `${RoutesManifest.basePath ?? ""}/api`;
10
+ const optionalPrefix = optionalLocalePrefixRegex.replace("^/", optionalBasepathPrefixRegex);
11
+ function routeMatcher(routeDefinitions) {
12
+ const regexp = routeDefinitions.map((route) => ({
13
+ page: route.page,
14
+ regexp: new RegExp(route.regex.replace("^/", optionalPrefix)),
15
+ }));
16
+ const appPathsSet = new Set();
17
+ const routePathsSet = new Set();
18
+ // We need to use AppPathRoutesManifest here
19
+ for (const [k, v] of Object.entries(AppPathRoutesManifest)) {
20
+ if (k.endsWith("page")) {
21
+ appPathsSet.add(v);
22
+ }
23
+ else if (k.endsWith("route")) {
24
+ routePathsSet.add(v);
25
+ }
26
+ }
27
+ return function matchRoute(path) {
28
+ const foundRoutes = regexp.filter((route) => route.regexp.test(path));
29
+ return foundRoutes.map((foundRoute) => {
30
+ let routeType = "page";
31
+ if (appPathsSet.has(foundRoute.page)) {
32
+ routeType = "app";
33
+ }
34
+ else if (routePathsSet.has(foundRoute.page)) {
35
+ routeType = "route";
36
+ }
37
+ return {
38
+ route: foundRoute.page,
39
+ type: routeType,
40
+ };
41
+ });
42
+ };
43
+ }
44
+ export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
45
+ export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);
@@ -60,7 +60,7 @@ export declare function convertBodyToReadableStream(method: string, body?: strin
60
60
  *
61
61
  * @__PURE__
62
62
  */
63
- export declare function fixCacheHeaderForHtmlPages(rawPath: string, headers: OutgoingHttpHeaders): void;
63
+ export declare function fixCacheHeaderForHtmlPages(internalEvent: InternalEvent, headers: OutgoingHttpHeaders): void;
64
64
  /**
65
65
  *
66
66
  * @__PURE__
@@ -5,6 +5,7 @@ import { OpenNextNodeResponse } from "../../http/openNextResponse.js";
5
5
  import { parseHeaders } from "../../http/util.js";
6
6
  import { debug, error } from "../../adapters/logger.js";
7
7
  import { isBinaryContentType } from "../../utils/binary.js";
8
+ import { localizePath } from "./i18n/index.js";
8
9
  /**
9
10
  *
10
11
  * @__PURE__
@@ -169,9 +170,9 @@ var CommonHeaders;
169
170
  *
170
171
  * @__PURE__
171
172
  */
172
- export function fixCacheHeaderForHtmlPages(rawPath, headers) {
173
+ export function fixCacheHeaderForHtmlPages(internalEvent, headers) {
173
174
  // We don't want to cache error pages
174
- if (rawPath === "/404" || rawPath === "/500") {
175
+ if (internalEvent.rawPath === "/404" || internalEvent.rawPath === "/500") {
175
176
  if (process.env.OPEN_NEXT_DANGEROUSLY_SET_ERROR_HEADERS === "true") {
176
177
  return;
177
178
  }
@@ -179,9 +180,10 @@ export function fixCacheHeaderForHtmlPages(rawPath, headers) {
179
180
  "private, no-cache, no-store, max-age=0, must-revalidate";
180
181
  return;
181
182
  }
183
+ const localizedPath = localizePath(internalEvent);
182
184
  // WORKAROUND: `NextServer` does not set cache headers for HTML pages
183
185
  // https://opennext.js.org/aws/v2/advanced/workaround#workaround-nextserver-does-not-set-cache-headers-for-html-pages
184
- if (HtmlPages.includes(rawPath)) {
186
+ if (HtmlPages.includes(localizedPath)) {
185
187
  headers[CommonHeaders.CACHE_CONTROL] =
186
188
  "public, max-age=0, s-maxage=31536000, must-revalidate";
187
189
  }
@@ -352,7 +354,7 @@ export function fixISRHeaders(headers) {
352
354
  */
353
355
  export function createServerResponse(internalEvent, headers, responseStream) {
354
356
  return new OpenNextNodeResponse((_headers) => {
355
- fixCacheHeaderForHtmlPages(internalEvent.rawPath, _headers);
357
+ fixCacheHeaderForHtmlPages(internalEvent, _headers);
356
358
  fixSWRCacheHeader(_headers);
357
359
  addOpenNextHeader(_headers);
358
360
  fixISRHeaders(_headers);
@@ -1,4 +1,7 @@
1
1
  import type { InternalEvent, InternalResult, RoutingResult } from "../types/open-next";
2
2
  export declare const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
3
3
  export declare const MIDDLEWARE_HEADER_PREFIX_LEN: number;
4
+ export declare const INTERNAL_HEADER_PREFIX = "x-opennext-";
5
+ export declare const INTERNAL_HEADER_INITIAL_PATH = "x-opennext-initial-path";
6
+ export declare const INTERNAL_HEADER_RESOLVED_ROUTES = "x-opennext-resolved-routes";
4
7
  export default function routingHandler(event: InternalEvent): Promise<InternalResult | RoutingResult>;
@@ -3,26 +3,12 @@ import { debug } from "../adapters/logger";
3
3
  import { cacheInterceptor } from "./routing/cacheInterceptor";
4
4
  import { fixDataPage, getNextConfigHeaders, handleFallbackFalse, handleRedirects, handleRewrites, } from "./routing/matcher";
5
5
  import { handleMiddleware } from "./routing/middleware";
6
+ import { apiPrefix, dynamicRouteMatcher, staticRouteMatcher, } from "./routing/routeMatcher";
6
7
  export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
7
8
  export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
8
- // Add the locale prefix to the regex so we correctly match the rawPath
9
- const optionalLocalePrefixRegex = RoutesManifest.locales.length
10
- ? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
11
- : "^/";
12
- // Add the basepath prefix to the regex so we correctly match the rawPath
13
- const optionalBasepathPrefixRegex = RoutesManifest.basePath
14
- ? `^${RoutesManifest.basePath}/?`
15
- : "^/";
16
- // Add the basePath prefix to the api routes
17
- const apiPrefix = RoutesManifest.basePath
18
- ? `${RoutesManifest.basePath}/api`
19
- : "/api";
20
- const staticRegexp = RoutesManifest.routes.static.map((route) => new RegExp(route.regex
21
- .replace("^/", optionalLocalePrefixRegex)
22
- .replace("^/", optionalBasepathPrefixRegex)));
23
- const dynamicRegexp = RoutesManifest.routes.dynamic.map((route) => new RegExp(route.regex
24
- .replace("^/", optionalLocalePrefixRegex)
25
- .replace("^/", optionalBasepathPrefixRegex)));
9
+ export const INTERNAL_HEADER_PREFIX = "x-opennext-";
10
+ export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
11
+ export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
26
12
  // Geolocation headers starting from Nextjs 15
27
13
  // See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
28
14
  const geoHeaderToNextHeader = {
@@ -50,6 +36,14 @@ export default async function routingHandler(event) {
50
36
  event.headers[nextGeoName] = value;
51
37
  }
52
38
  }
39
+ // First we remove internal headers
40
+ // We don't want to allow users to set these headers
41
+ for (const key of Object.keys(event.headers)) {
42
+ if (key.startsWith(INTERNAL_HEADER_PREFIX) ||
43
+ key.startsWith(MIDDLEWARE_HEADER_PREFIX)) {
44
+ delete event.headers[key];
45
+ }
46
+ }
53
47
  const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);
54
48
  let internalEvent = fixDataPage(event, BuildId);
55
49
  if ("statusCode" in internalEvent) {
@@ -75,9 +69,9 @@ export default async function routingHandler(event) {
75
69
  internalEvent = beforeRewrites.internalEvent;
76
70
  isExternalRewrite = beforeRewrites.isExternalRewrite;
77
71
  }
78
- const isStaticRoute = !isExternalRewrite &&
79
- staticRegexp.some((route) => route.test(internalEvent.rawPath));
80
- if (!isStaticRoute && !isExternalRewrite) {
72
+ const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
73
+ const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
74
+ if (!(isStaticRoute || isExternalRewrite)) {
81
75
  // Second rewrite to be applied
82
76
  const afterRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.afterFiles);
83
77
  internalEvent = afterRewrites.internalEvent;
@@ -86,9 +80,9 @@ export default async function routingHandler(event) {
86
80
  // We want to run this just before the dynamic route check
87
81
  const { event: fallbackEvent, isISR } = handleFallbackFalse(internalEvent, PrerenderManifest);
88
82
  internalEvent = fallbackEvent;
89
- const isDynamicRoute = !isExternalRewrite &&
90
- dynamicRegexp.some((route) => route.test(internalEvent.rawPath));
91
- if (!isDynamicRoute && !isStaticRoute && !isExternalRewrite) {
83
+ const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
84
+ const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
85
+ if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
92
86
  // Fallback rewrite to be applied
93
87
  const fallbackRewrites = handleRewrites(internalEvent, RoutesManifest.rewrites.fallback);
94
88
  internalEvent = fallbackRewrites.internalEvent;
@@ -103,12 +97,12 @@ export default async function routingHandler(event) {
103
97
  const isRouteFoundBeforeAllRewrites = isStaticRoute || isDynamicRoute || isExternalRewrite;
104
98
  // If we still haven't found a route, we show the 404 page
105
99
  // We need to ensure that rewrites are applied before showing the 404 page
106
- if (!isRouteFoundBeforeAllRewrites &&
107
- !isApiRoute &&
108
- !isNextImageRoute &&
100
+ if (!(isRouteFoundBeforeAllRewrites ||
101
+ isApiRoute ||
102
+ isNextImageRoute ||
109
103
  // We need to check again once all rewrites have been applied
110
- !staticRegexp.some((route) => route.test(internalEvent.rawPath)) &&
111
- !dynamicRegexp.some((route) => route.test(internalEvent.rawPath))) {
104
+ staticRouteMatcher(internalEvent.rawPath).length > 0 ||
105
+ dynamicRouteMatcher(internalEvent.rawPath).length > 0)) {
112
106
  internalEvent = {
113
107
  ...internalEvent,
114
108
  rawPath: "/404",
@@ -136,10 +130,17 @@ export default async function routingHandler(event) {
136
130
  ...middlewareResponseHeaders,
137
131
  ...nextHeaders,
138
132
  });
133
+ const resolvedRoutes = [
134
+ ...foundStaticRoute,
135
+ ...foundDynamicRoute,
136
+ ];
137
+ debug("resolvedRoutes", resolvedRoutes);
139
138
  return {
140
139
  internalEvent,
141
140
  isExternalRewrite,
142
141
  origin: false,
143
142
  isISR,
143
+ initialPath: event.rawPath,
144
+ resolvedRoutes,
144
145
  };
145
146
  }
@@ -1,4 +1,6 @@
1
1
  import { Writable } from "node:stream";
2
+ // Response with null body status (101, 204, 205, or 304) cannot have a body.
3
+ const NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);
2
4
  const handler = async (handler, converter) => async (request, env, ctx) => {
3
5
  globalThis.process = process;
4
6
  globalThis.openNextWaitUntil = ctx.waitUntil.bind(ctx);
@@ -29,7 +31,8 @@ const handler = async (handler, converter) => async (request, env, ctx) => {
29
31
  controller.enqueue(Uint8Array.from(chunk.chunk ?? chunk));
30
32
  },
31
33
  });
32
- const response = new Response(readable, {
34
+ const body = NULL_BODY_STATUSES.has(statusCode) ? null : readable;
35
+ const response = new Response(body, {
33
36
  status: statusCode,
34
37
  headers: responseHeaders,
35
38
  });
@@ -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 { loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, loadRoutesManifest, } from "../adapters/config/util.js";
4
+ import { loadAppPathRoutesManifest, loadAppPathsManifest, loadAppPathsManifestKeys, loadBuildId, loadConfig, loadConfigHeaders, loadHtmlPages, loadMiddlewareManifest, loadPrerenderManifest, loadRoutesManifest, } from "../adapters/config/util.js";
5
5
  import logger from "../logger.js";
6
6
  /**
7
7
  * @param opts.nextDir - The path to the .next directory
@@ -126,6 +126,8 @@ ${contents}
126
126
  const PrerenderManifest = loadPrerenderManifest(nextDir);
127
127
  const AppPathsManifestKeys = loadAppPathsManifestKeys(nextDir);
128
128
  const MiddlewareManifest = loadMiddlewareManifest(nextDir);
129
+ const AppPathsManifest = loadAppPathsManifest(nextDir);
130
+ const AppPathRoutesManifest = loadAppPathRoutesManifest(nextDir);
129
131
  const contents = `
130
132
  import path from "node:path";
131
133
 
@@ -146,6 +148,9 @@ ${contents}
146
148
  export const PrerenderManifest = ${JSON.stringify(PrerenderManifest)};
147
149
  export const AppPathsManifestKeys = ${JSON.stringify(AppPathsManifestKeys)};
148
150
  export const MiddlewareManifest = ${JSON.stringify(MiddlewareManifest)};
151
+ export const AppPathsManifest = ${JSON.stringify(AppPathsManifest)};
152
+ export const AppPathRoutesManifest = ${JSON.stringify(AppPathRoutesManifest)};
153
+
149
154
 
150
155
  process.env.NEXT_BUILD_ID = BuildId;
151
156
  `;
@@ -68,11 +68,18 @@ export interface Origin {
68
68
  }
69
69
  export type IncludedWrapper = "aws-lambda" | "aws-lambda-streaming" | "node" | "cloudflare" | "cloudflare-edge" | "cloudflare-node" | "dummy";
70
70
  export type IncludedConverter = "aws-apigw-v2" | "aws-apigw-v1" | "aws-cloudfront" | "edge" | "node" | "sqs-revalidate" | "dummy";
71
+ export type RouteType = "route" | "page" | "app";
72
+ export interface ResolvedRoute {
73
+ route: string;
74
+ type: RouteType;
75
+ }
71
76
  export interface RoutingResult {
72
77
  internalEvent: InternalEvent;
73
78
  isExternalRewrite: boolean;
74
79
  origin: Origin | false;
75
80
  isISR: boolean;
81
+ initialPath: string;
82
+ resolvedRoutes: ResolvedRoute[];
76
83
  }
77
84
  export interface MiddlewareResult extends RoutingResult, BaseEventOrResult<"middleware"> {
78
85
  }
@@ -13,7 +13,7 @@ export interface Queue {
13
13
  send(message: QueueMessage): Promise<void>;
14
14
  name: string;
15
15
  }
16
- export type S3CachedFile = {
16
+ export type CachedFile = {
17
17
  type: "redirect";
18
18
  props?: Object;
19
19
  meta?: Meta;
@@ -32,12 +32,12 @@ export type S3CachedFile = {
32
32
  body: string;
33
33
  meta?: Meta;
34
34
  };
35
- export type S3FetchCache = Object;
35
+ export type FetchCache = Object;
36
36
  export type WithLastModified<T> = {
37
37
  lastModified?: number;
38
38
  value?: T;
39
39
  };
40
- export type CacheValue<IsFetch extends boolean> = (IsFetch extends true ? S3FetchCache : S3CachedFile) & {
40
+ export type CacheValue<IsFetch extends boolean> = (IsFetch extends true ? FetchCache : CachedFile) & {
41
41
  revalidate?: number | false;
42
42
  };
43
43
  export type IncrementalCache = {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "access": "public"
4
4
  },
5
5
  "name": "@opennextjs/aws",
6
- "version": "3.3.0",
6
+ "version": "3.3.1",
7
7
  "bin": {
8
8
  "open-next": "./dist/index.js"
9
9
  },