Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spotty-chairs-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/aws": patch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be minor (new feature) instead of patch (bug fix) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok this way, technically there is no new feature here, and it should have no impact at all for end user (even people with custom overrides)
Other PR that will depend on this one will be minor like #665

---

Add additional metadata to RoutingResult
6 changes: 5 additions & 1 deletion packages/open-next/src/adapters/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import path from "node:path";

import { debug } from "../logger";
import {
loadAppPathRoutesManifest,
loadAppPathsManifest,
loadAppPathsManifestKeys,
loadBuildId,
loadConfig,
loadConfigHeaders,
loadHtmlPages,
loadMiddlewareManifest,
loadPrerenderManifest,
// loadPublicAssets,
loadRoutesManifest,
} from "./util.js";

Expand All @@ -31,3 +32,6 @@ export const AppPathsManifestKeys =
/* @__PURE__ */ loadAppPathsManifestKeys(NEXT_DIR);
export const MiddlewareManifest =
/* @__PURE__ */ loadMiddlewareManifest(NEXT_DIR);
export const AppPathsManifest = /* @__PURE__ */ loadAppPathsManifest(NEXT_DIR);
export const AppPathRoutesManifest =
/* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR);
22 changes: 17 additions & 5 deletions packages/open-next/src/adapters/config/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function loadPrerenderManifest(nextDir: string) {
return JSON.parse(json) as PrerenderManifest;
}

export function loadAppPathsManifestKeys(nextDir: string) {
export function loadAppPathsManifest(nextDir: string) {
const appPathsManifestPath = path.join(
nextDir,
"server",
Expand All @@ -86,10 +86,22 @@ export function loadAppPathsManifestKeys(nextDir: string) {
const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
? fs.readFileSync(appPathsManifestPath, "utf-8")
: "{}";
const appPathsManifest = JSON.parse(appPathsManifestJson) as Record<
string,
string
>;
return JSON.parse(appPathsManifestJson) as Record<string, string>;
}

export function loadAppPathRoutesManifest(nextDir: string) {
const appPathRoutesManifestPath = path.join(
nextDir,
"app-path-routes-manifest.json",
);
const appPathRoutesManifestJson = fs.existsSync(appPathRoutesManifestPath)
? fs.readFileSync(appPathRoutesManifestPath, "utf-8")
: "{}";
return JSON.parse(appPathRoutesManifestJson) as Record<string, string>;
}

export function loadAppPathsManifestKeys(nextDir: string) {
const appPathsManifest = loadAppPathsManifest(nextDir);
return Object.keys(appPathsManifest).map((key) => {
// Remove parallel route
let cleanedKey = key.replace(/\/@[^\/]+/g, "");
Expand Down
19 changes: 17 additions & 2 deletions packages/open-next/src/adapters/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
resolveQueue,
resolveTagCache,
} from "../core/resolve";
import routingHandler from "../core/routingHandler";
import routingHandler, {
INTERNAL_HEADER_INITIAL_PATH,
INTERNAL_HEADER_RESOLVED_ROUTES,
} from "../core/routingHandler";

globalThis.internalFetch = fetch;
globalThis.__openNextAls = new AsyncLocalStorage();
Expand Down Expand Up @@ -57,10 +60,20 @@ const defaultHandler = async (
);
return {
type: "middleware",
internalEvent: result.internalEvent,
internalEvent: {
...result.internalEvent,
headers: {
...result.internalEvent.headers,
[INTERNAL_HEADER_INITIAL_PATH]: internalEvent.rawPath,
[INTERNAL_HEADER_RESOLVED_ROUTES]:
JSON.stringify(result.resolvedRoutes) ?? "[]",
},
},
isExternalRewrite: result.isExternalRewrite,
origin,
isISR: result.isISR,
initialPath: result.initialPath,
resolvedRoutes: result.resolvedRoutes,
};
}
try {
Expand All @@ -79,6 +92,8 @@ const defaultHandler = async (
isExternalRewrite: false,
origin: false,
isISR: result.isISR,
initialPath: result.internalEvent.rawPath,
resolvedRoutes: [{ route: "/500", type: "page" }],
};
}
}
Expand Down
22 changes: 19 additions & 3 deletions packages/open-next/src/core/requestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IncomingMessage } from "http/index.js";
import type {
InternalEvent,
InternalResult,
ResolvedRoute,
RoutingResult,
StreamCreator,
} from "types/open-next";
Expand All @@ -14,6 +15,8 @@ import { debug, error, warn } from "../adapters/logger";
import { patchAsyncStorage } from "./patchAsyncStorage";
import { convertRes, createServerResponse } from "./routing/util";
import routingHandler, {
INTERNAL_HEADER_INITIAL_PATH,
INTERNAL_HEADER_RESOLVED_ROUTES,
MIDDLEWARE_HEADER_PREFIX,
MIDDLEWARE_HEADER_PREFIX_LEN,
} from "./routingHandler";
Expand All @@ -28,20 +31,31 @@ export async function openNextHandler(
internalEvent: InternalEvent,
responseStreaming?: StreamCreator,
): Promise<InternalResult> {
const initialHeaders = internalEvent.headers;
// We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
return runWithOpenNextRequestContext(
{ isISRRevalidation: internalEvent.headers["x-isr"] === "1" },
{ isISRRevalidation: initialHeaders["x-isr"] === "1" },
async () => {
if (internalEvent.headers["x-forwarded-host"]) {
internalEvent.headers.host = internalEvent.headers["x-forwarded-host"];
if (initialHeaders["x-forwarded-host"]) {
initialHeaders.host = initialHeaders["x-forwarded-host"];
}
debug("internalEvent", internalEvent);

// These 2 will get overwritten by the routing handler if not using an external middleware
const internalHeaders = {
initialPath:
initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
: ([] as ResolvedRoute[]),
};

let routingResult: InternalResult | RoutingResult = {
internalEvent,
isExternalRewrite: false,
origin: false,
isISR: false,
...internalHeaders,
};

//#override withRouting
Expand Down Expand Up @@ -94,6 +108,8 @@ export async function openNextHandler(
isExternalRewrite: false,
isISR: false,
origin: false,
initialPath: internalEvent.rawPath,
resolvedRoutes: [{ route: "/500", type: "page" }],
};
}
}
Expand Down
64 changes: 64 additions & 0 deletions packages/open-next/src/core/routing/routeMatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { AppPathRoutesManifest, RoutesManifest } from "config/index";
import type { RouteDefinition } from "types/next-types";
import type { RouteType } from "types/open-next";

// Add the locale prefix to the regex so we correctly match the rawPath
const optionalLocalePrefixRegex = RoutesManifest.locales.length
? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
: "^/";

// Add the basepath prefix to the regex so we correctly match the rawPath
const optionalBasepathPrefixRegex = RoutesManifest.basePath
? `^${RoutesManifest.basePath}/?`
: "^/";

// Add the basePath prefix to the api routes
export const apiPrefix = RoutesManifest.basePath
? `${RoutesManifest.basePath}/api`
: "/api";

function routeMatcher(routeDefinitions: RouteDefinition[]) {
const regexp = routeDefinitions.map((route) => ({
page: route.page,
regexp: new RegExp(
route.regex
.replace("^/", optionalLocalePrefixRegex)
.replace("^/", optionalBasepathPrefixRegex),
),
}));

// We need to use AppPathRoutesManifest here
const appPathsSet = new Set(
Object.entries(AppPathRoutesManifest)
.filter(([key, _]) => key.endsWith("page"))
.map(([_, value]) => value),
);
const routePathsSet = new Set(
Object.entries(AppPathRoutesManifest)
.filter(([key, _]) => key.endsWith("route"))
.map(([_, value]) => value),
);
return function matchRoute(path: string) {
const foundRoutes = regexp.filter((route) => route.regexp.test(path));

if (foundRoutes.length > 0) {
return foundRoutes.map((foundRoute) => {
const routeType: RouteType | undefined = appPathsSet.has(
foundRoute.page,
)
? "app"
: routePathsSet.has(foundRoute.page)
? "route"
: "page";
return {
route: foundRoute.page,
type: routeType,
};
});
}
return false;
};
}

export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);
96 changes: 42 additions & 54 deletions packages/open-next/src/core/routingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import type {
InternalEvent,
InternalResult,
ResolvedRoute,
RoutingResult,
} from "types/open-next";

Expand All @@ -20,42 +21,17 @@ import {
handleRewrites,
} from "./routing/matcher";
import { handleMiddleware } from "./routing/middleware";
import {
apiPrefix,
dynamicRouteMatcher,
staticRouteMatcher,
} from "./routing/routeMatcher";

export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;

// Add the locale prefix to the regex so we correctly match the rawPath
const optionalLocalePrefixRegex = RoutesManifest.locales.length
? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
: "^/";

// Add the basepath prefix to the regex so we correctly match the rawPath
const optionalBasepathPrefixRegex = RoutesManifest.basePath
? `^${RoutesManifest.basePath}/?`
: "^/";

// Add the basePath prefix to the api routes
const apiPrefix = RoutesManifest.basePath
? `${RoutesManifest.basePath}/api`
: "/api";

const staticRegexp = RoutesManifest.routes.static.map(
(route) =>
new RegExp(
route.regex
.replace("^/", optionalLocalePrefixRegex)
.replace("^/", optionalBasepathPrefixRegex),
),
);

const dynamicRegexp = RoutesManifest.routes.dynamic.map(
(route) =>
new RegExp(
route.regex
.replace("^/", optionalLocalePrefixRegex)
.replace("^/", optionalBasepathPrefixRegex),
),
);
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;

// Geolocation headers starting from Nextjs 15
// See https://github.com/vercel/vercel/blob/7714b1c/packages/functions/src/headers.ts
Expand Down Expand Up @@ -95,6 +71,17 @@ export default async function routingHandler(
}
}

// First we remove internal headers
// We don't want to allow users to set these headers
for (const key of Object.keys(event.headers)) {
if (
key.startsWith(INTERNAL_HEADER_PREFIX) ||
key.startsWith(MIDDLEWARE_HEADER_PREFIX)
) {
delete event.headers[key];
}
}

const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);

let internalEvent = fixDataPage(event, BuildId);
Expand Down Expand Up @@ -127,12 +114,8 @@ export default async function routingHandler(
internalEvent = beforeRewrites.internalEvent;
isExternalRewrite = beforeRewrites.isExternalRewrite;
}

const isStaticRoute =
!isExternalRewrite &&
staticRegexp.some((route) =>
route.test((internalEvent as InternalEvent).rawPath),
);
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
const isStaticRoute = !isExternalRewrite && Boolean(foundStaticRoute);

if (!isStaticRoute && !isExternalRewrite) {
// Second rewrite to be applied
Expand All @@ -151,12 +134,10 @@ export default async function routingHandler(
);
internalEvent = fallbackEvent;

const isDynamicRoute =
!isExternalRewrite &&
dynamicRegexp.some((route) =>
route.test((internalEvent as InternalEvent).rawPath),
);
if (!isDynamicRoute && !isStaticRoute && !isExternalRewrite) {
const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
const isDynamicRoute = !isExternalRewrite && Boolean(foundDynamicRoute);

if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
// Fallback rewrite to be applied
const fallbackRewrites = handleRewrites(
internalEvent,
Expand All @@ -181,15 +162,13 @@ export default async function routingHandler(
// If we still haven't found a route, we show the 404 page
// We need to ensure that rewrites are applied before showing the 404 page
if (
!isRouteFoundBeforeAllRewrites &&
!isApiRoute &&
!isNextImageRoute &&
// We need to check again once all rewrites have been applied
!staticRegexp.some((route) =>
route.test((internalEvent as InternalEvent).rawPath),
) &&
!dynamicRegexp.some((route) =>
route.test((internalEvent as InternalEvent).rawPath),
!(
isRouteFoundBeforeAllRewrites ||
isApiRoute ||
isNextImageRoute ||
// We need to check again once all rewrites have been applied
staticRouteMatcher(internalEvent.rawPath) ||
dynamicRouteMatcher(internalEvent.rawPath)
)
) {
internalEvent = {
Expand Down Expand Up @@ -229,10 +208,19 @@ export default async function routingHandler(
...nextHeaders,
});

const resolvedRoutes: ResolvedRoute[] = [
...(Array.isArray(foundStaticRoute) ? foundStaticRoute : []),
...(Array.isArray(foundDynamicRoute) ? foundDynamicRoute : []),
];

console.log("resolvedRoutes", resolvedRoutes);

return {
internalEvent,
isExternalRewrite,
origin: false,
isISR,
initialPath: event.rawPath,
resolvedRoutes,
};
}
Loading
Loading