Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add additional metadata to RoutingResult #673

Merged
merged 8 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions .changeset/spotty-chairs-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@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
conico974 marked this conversation as resolved.
Show resolved Hide resolved

For some future features [#658](https://github.com/opennextjs/opennextjs-aws/issues/658) (and bug fix [#677](https://github.com/opennextjs/opennextjs-aws/issues/677)) we need to add some additional metadata to the RoutingResult.
This PR adds 2 new fields to the RoutingResult: `initialPath` and `resolvedRoutes`
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);
24 changes: 19 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,24 @@ 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,
): Record<string, string> {
const appPathRoutesManifestPath = path.join(
nextDir,
"app-path-routes-manifest.json",
);
if (fs.existsSync(appPathRoutesManifestPath)) {
return JSON.parse(fs.readFileSync(appPathRoutesManifestPath, "utf-8"));
}
return {};
}

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
57 changes: 57 additions & 0 deletions packages/open-next/src/core/routing/routeMatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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.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 ?? ""}/api`;

const optionalPrefix = optionalLocalePrefixRegex.replace(
"^/",
optionalBasepathPrefixRegex,
);

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

const appPathsSet = new Set();
const routePathsSet = new Set();
// We need to use AppPathRoutesManifest here
for (const [k, v] of Object.entries(AppPathRoutesManifest)) {
if (k.endsWith("page")) {
appPathsSet.add(v);
} else if (k.endsWith("route")) {
routePathsSet.add(v);
}
}

return function matchRoute(path: string) {
const foundRoutes = regexp.filter((route) => route.regexp.test(path));

return foundRoutes.map((foundRoute) => {
let routeType: RouteType = "page";
if (appPathsSet.has(foundRoute.page)) {
routeType = "app";
} else if (routePathsSet.has(foundRoute.page)) {
routeType = "route";
}
return {
route: foundRoute.page,
type: routeType,
};
});
};
}

export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);
98 changes: 43 additions & 55 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,14 +114,10 @@ export default async function routingHandler(
internalEvent = beforeRewrites.internalEvent;
isExternalRewrite = beforeRewrites.isExternalRewrite;
}
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;

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

if (!isStaticRoute && !isExternalRewrite) {
if (!(isStaticRoute || isExternalRewrite)) {
// Second rewrite to be applied
const afterRewrites = handleRewrites(
internalEvent,
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 && foundDynamicRoute.length > 0;

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).length > 0 ||
dynamicRouteMatcher(internalEvent.rawPath).length > 0
)
) {
internalEvent = {
Expand Down Expand Up @@ -229,10 +208,19 @@ export default async function routingHandler(
...nextHeaders,
});

const resolvedRoutes: ResolvedRoute[] = [
...foundStaticRoute,
...foundDynamicRoute,
];

debug("resolvedRoutes", resolvedRoutes);

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