Skip to content

Commit 7140ca5

Browse files
authored
add additional metadata to RoutingResult (#673)
* add additional metadata to RoutingResult * review fix * handle multiple route * add unit test * review fix * changeset * fix lint * review fix
1 parent f71490f commit 7140ca5

File tree

10 files changed

+343
-66
lines changed

10 files changed

+343
-66
lines changed

.changeset/spotty-chairs-pretend.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
Add additional metadata to RoutingResult
6+
7+
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.
8+
This PR adds 2 new fields to the RoutingResult: `initialPath` and `resolvedRoutes`

packages/open-next/src/adapters/config/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import path from "node:path";
22

33
import { debug } from "../logger";
44
import {
5+
loadAppPathRoutesManifest,
6+
loadAppPathsManifest,
57
loadAppPathsManifestKeys,
68
loadBuildId,
79
loadConfig,
810
loadConfigHeaders,
911
loadHtmlPages,
1012
loadMiddlewareManifest,
1113
loadPrerenderManifest,
12-
// loadPublicAssets,
1314
loadRoutesManifest,
1415
} from "./util.js";
1516

@@ -31,3 +32,6 @@ export const AppPathsManifestKeys =
3132
/* @__PURE__ */ loadAppPathsManifestKeys(NEXT_DIR);
3233
export const MiddlewareManifest =
3334
/* @__PURE__ */ loadMiddlewareManifest(NEXT_DIR);
35+
export const AppPathsManifest = /* @__PURE__ */ loadAppPathsManifest(NEXT_DIR);
36+
export const AppPathRoutesManifest =
37+
/* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR);

packages/open-next/src/adapters/config/util.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function loadPrerenderManifest(nextDir: string) {
7777
return JSON.parse(json) as PrerenderManifest;
7878
}
7979

80-
export function loadAppPathsManifestKeys(nextDir: string) {
80+
export function loadAppPathsManifest(nextDir: string) {
8181
const appPathsManifestPath = path.join(
8282
nextDir,
8383
"server",
@@ -86,10 +86,24 @@ export function loadAppPathsManifestKeys(nextDir: string) {
8686
const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
8787
? fs.readFileSync(appPathsManifestPath, "utf-8")
8888
: "{}";
89-
const appPathsManifest = JSON.parse(appPathsManifestJson) as Record<
90-
string,
91-
string
92-
>;
89+
return JSON.parse(appPathsManifestJson) as Record<string, string>;
90+
}
91+
92+
export function loadAppPathRoutesManifest(
93+
nextDir: string,
94+
): Record<string, string> {
95+
const appPathRoutesManifestPath = path.join(
96+
nextDir,
97+
"app-path-routes-manifest.json",
98+
);
99+
if (fs.existsSync(appPathRoutesManifestPath)) {
100+
return JSON.parse(fs.readFileSync(appPathRoutesManifestPath, "utf-8"));
101+
}
102+
return {};
103+
}
104+
105+
export function loadAppPathsManifestKeys(nextDir: string) {
106+
const appPathsManifest = loadAppPathsManifest(nextDir);
93107
return Object.keys(appPathsManifest).map((key) => {
94108
// Remove parallel route
95109
let cleanedKey = key.replace(/\/@[^\/]+/g, "");

packages/open-next/src/adapters/middleware.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import {
1414
resolveQueue,
1515
resolveTagCache,
1616
} from "../core/resolve";
17-
import routingHandler from "../core/routingHandler";
17+
import routingHandler, {
18+
INTERNAL_HEADER_INITIAL_PATH,
19+
INTERNAL_HEADER_RESOLVED_ROUTES,
20+
} from "../core/routingHandler";
1821

1922
globalThis.internalFetch = fetch;
2023
globalThis.__openNextAls = new AsyncLocalStorage();
@@ -57,10 +60,20 @@ const defaultHandler = async (
5760
);
5861
return {
5962
type: "middleware",
60-
internalEvent: result.internalEvent,
63+
internalEvent: {
64+
...result.internalEvent,
65+
headers: {
66+
...result.internalEvent.headers,
67+
[INTERNAL_HEADER_INITIAL_PATH]: internalEvent.rawPath,
68+
[INTERNAL_HEADER_RESOLVED_ROUTES]:
69+
JSON.stringify(result.resolvedRoutes) ?? "[]",
70+
},
71+
},
6172
isExternalRewrite: result.isExternalRewrite,
6273
origin,
6374
isISR: result.isISR,
75+
initialPath: result.initialPath,
76+
resolvedRoutes: result.resolvedRoutes,
6477
};
6578
}
6679
try {
@@ -79,6 +92,8 @@ const defaultHandler = async (
7992
isExternalRewrite: false,
8093
origin: false,
8194
isISR: result.isISR,
95+
initialPath: result.internalEvent.rawPath,
96+
resolvedRoutes: [{ route: "/500", type: "page" }],
8297
};
8398
}
8499
}

packages/open-next/src/core/requestHandler.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IncomingMessage } from "http/index.js";
55
import type {
66
InternalEvent,
77
InternalResult,
8+
ResolvedRoute,
89
RoutingResult,
910
StreamCreator,
1011
} from "types/open-next";
@@ -14,6 +15,8 @@ import { debug, error, warn } from "../adapters/logger";
1415
import { patchAsyncStorage } from "./patchAsyncStorage";
1516
import { convertRes, createServerResponse } from "./routing/util";
1617
import routingHandler, {
18+
INTERNAL_HEADER_INITIAL_PATH,
19+
INTERNAL_HEADER_RESOLVED_ROUTES,
1720
MIDDLEWARE_HEADER_PREFIX,
1821
MIDDLEWARE_HEADER_PREFIX_LEN,
1922
} from "./routingHandler";
@@ -28,20 +31,31 @@ export async function openNextHandler(
2831
internalEvent: InternalEvent,
2932
responseStreaming?: StreamCreator,
3033
): Promise<InternalResult> {
34+
const initialHeaders = internalEvent.headers;
3135
// We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
3236
return runWithOpenNextRequestContext(
33-
{ isISRRevalidation: internalEvent.headers["x-isr"] === "1" },
37+
{ isISRRevalidation: initialHeaders["x-isr"] === "1" },
3438
async () => {
35-
if (internalEvent.headers["x-forwarded-host"]) {
36-
internalEvent.headers.host = internalEvent.headers["x-forwarded-host"];
39+
if (initialHeaders["x-forwarded-host"]) {
40+
initialHeaders.host = initialHeaders["x-forwarded-host"];
3741
}
3842
debug("internalEvent", internalEvent);
3943

44+
// These 2 will get overwritten by the routing handler if not using an external middleware
45+
const internalHeaders = {
46+
initialPath:
47+
initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
48+
resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
49+
? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
50+
: ([] as ResolvedRoute[]),
51+
};
52+
4053
let routingResult: InternalResult | RoutingResult = {
4154
internalEvent,
4255
isExternalRewrite: false,
4356
origin: false,
4457
isISR: false,
58+
...internalHeaders,
4559
};
4660

4761
//#override withRouting
@@ -94,6 +108,8 @@ export async function openNextHandler(
94108
isExternalRewrite: false,
95109
isISR: false,
96110
origin: false,
111+
initialPath: internalEvent.rawPath,
112+
resolvedRoutes: [{ route: "/500", type: "page" }],
97113
};
98114
}
99115
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { AppPathRoutesManifest, RoutesManifest } from "config/index";
2+
import type { RouteDefinition } from "types/next-types";
3+
import type { RouteType } from "types/open-next";
4+
5+
// Add the locale prefix to the regex so we correctly match the rawPath
6+
const optionalLocalePrefixRegex = `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`;
7+
8+
// Add the basepath prefix to the regex so we correctly match the rawPath
9+
const optionalBasepathPrefixRegex = RoutesManifest.basePath
10+
? `^${RoutesManifest.basePath}/?`
11+
: "^/";
12+
13+
// Add the basePath prefix to the api routes
14+
export const apiPrefix = `${RoutesManifest.basePath ?? ""}/api`;
15+
16+
const optionalPrefix = optionalLocalePrefixRegex.replace(
17+
"^/",
18+
optionalBasepathPrefixRegex,
19+
);
20+
21+
function routeMatcher(routeDefinitions: RouteDefinition[]) {
22+
const regexp = routeDefinitions.map((route) => ({
23+
page: route.page,
24+
regexp: new RegExp(route.regex.replace("^/", optionalPrefix)),
25+
}));
26+
27+
const appPathsSet = new Set();
28+
const routePathsSet = new Set();
29+
// We need to use AppPathRoutesManifest here
30+
for (const [k, v] of Object.entries(AppPathRoutesManifest)) {
31+
if (k.endsWith("page")) {
32+
appPathsSet.add(v);
33+
} else if (k.endsWith("route")) {
34+
routePathsSet.add(v);
35+
}
36+
}
37+
38+
return function matchRoute(path: string) {
39+
const foundRoutes = regexp.filter((route) => route.regexp.test(path));
40+
41+
return foundRoutes.map((foundRoute) => {
42+
let routeType: RouteType = "page";
43+
if (appPathsSet.has(foundRoute.page)) {
44+
routeType = "app";
45+
} else if (routePathsSet.has(foundRoute.page)) {
46+
routeType = "route";
47+
}
48+
return {
49+
route: foundRoute.page,
50+
type: routeType,
51+
};
52+
});
53+
};
54+
}
55+
56+
export const staticRouteMatcher = routeMatcher(RoutesManifest.routes.static);
57+
export const dynamicRouteMatcher = routeMatcher(RoutesManifest.routes.dynamic);

packages/open-next/src/core/routingHandler.ts

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import type {
88
InternalEvent,
99
InternalResult,
10+
ResolvedRoute,
1011
RoutingResult,
1112
} from "types/open-next";
1213

@@ -20,42 +21,17 @@ import {
2021
handleRewrites,
2122
} from "./routing/matcher";
2223
import { handleMiddleware } from "./routing/middleware";
24+
import {
25+
apiPrefix,
26+
dynamicRouteMatcher,
27+
staticRouteMatcher,
28+
} from "./routing/routeMatcher";
2329

2430
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
2531
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
26-
27-
// Add the locale prefix to the regex so we correctly match the rawPath
28-
const optionalLocalePrefixRegex = RoutesManifest.locales.length
29-
? `^/(?:${RoutesManifest.locales.map((locale) => `${locale}/?`).join("|")})?`
30-
: "^/";
31-
32-
// Add the basepath prefix to the regex so we correctly match the rawPath
33-
const optionalBasepathPrefixRegex = RoutesManifest.basePath
34-
? `^${RoutesManifest.basePath}/?`
35-
: "^/";
36-
37-
// Add the basePath prefix to the api routes
38-
const apiPrefix = RoutesManifest.basePath
39-
? `${RoutesManifest.basePath}/api`
40-
: "/api";
41-
42-
const staticRegexp = RoutesManifest.routes.static.map(
43-
(route) =>
44-
new RegExp(
45-
route.regex
46-
.replace("^/", optionalLocalePrefixRegex)
47-
.replace("^/", optionalBasepathPrefixRegex),
48-
),
49-
);
50-
51-
const dynamicRegexp = RoutesManifest.routes.dynamic.map(
52-
(route) =>
53-
new RegExp(
54-
route.regex
55-
.replace("^/", optionalLocalePrefixRegex)
56-
.replace("^/", optionalBasepathPrefixRegex),
57-
),
58-
);
32+
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
33+
export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
34+
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
5935

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

74+
// First we remove internal headers
75+
// We don't want to allow users to set these headers
76+
for (const key of Object.keys(event.headers)) {
77+
if (
78+
key.startsWith(INTERNAL_HEADER_PREFIX) ||
79+
key.startsWith(MIDDLEWARE_HEADER_PREFIX)
80+
) {
81+
delete event.headers[key];
82+
}
83+
}
84+
9885
const nextHeaders = getNextConfigHeaders(event, ConfigHeaders);
9986

10087
let internalEvent = fixDataPage(event, BuildId);
@@ -127,14 +114,10 @@ export default async function routingHandler(
127114
internalEvent = beforeRewrites.internalEvent;
128115
isExternalRewrite = beforeRewrites.isExternalRewrite;
129116
}
117+
const foundStaticRoute = staticRouteMatcher(internalEvent.rawPath);
118+
const isStaticRoute = !isExternalRewrite && foundStaticRoute.length > 0;
130119

131-
const isStaticRoute =
132-
!isExternalRewrite &&
133-
staticRegexp.some((route) =>
134-
route.test((internalEvent as InternalEvent).rawPath),
135-
);
136-
137-
if (!isStaticRoute && !isExternalRewrite) {
120+
if (!(isStaticRoute || isExternalRewrite)) {
138121
// Second rewrite to be applied
139122
const afterRewrites = handleRewrites(
140123
internalEvent,
@@ -151,12 +134,10 @@ export default async function routingHandler(
151134
);
152135
internalEvent = fallbackEvent;
153136

154-
const isDynamicRoute =
155-
!isExternalRewrite &&
156-
dynamicRegexp.some((route) =>
157-
route.test((internalEvent as InternalEvent).rawPath),
158-
);
159-
if (!isDynamicRoute && !isStaticRoute && !isExternalRewrite) {
137+
const foundDynamicRoute = dynamicRouteMatcher(internalEvent.rawPath);
138+
const isDynamicRoute = !isExternalRewrite && foundDynamicRoute.length > 0;
139+
140+
if (!(isDynamicRoute || isStaticRoute || isExternalRewrite)) {
160141
// Fallback rewrite to be applied
161142
const fallbackRewrites = handleRewrites(
162143
internalEvent,
@@ -181,15 +162,13 @@ export default async function routingHandler(
181162
// If we still haven't found a route, we show the 404 page
182163
// We need to ensure that rewrites are applied before showing the 404 page
183164
if (
184-
!isRouteFoundBeforeAllRewrites &&
185-
!isApiRoute &&
186-
!isNextImageRoute &&
187-
// We need to check again once all rewrites have been applied
188-
!staticRegexp.some((route) =>
189-
route.test((internalEvent as InternalEvent).rawPath),
190-
) &&
191-
!dynamicRegexp.some((route) =>
192-
route.test((internalEvent as InternalEvent).rawPath),
165+
!(
166+
isRouteFoundBeforeAllRewrites ||
167+
isApiRoute ||
168+
isNextImageRoute ||
169+
// We need to check again once all rewrites have been applied
170+
staticRouteMatcher(internalEvent.rawPath).length > 0 ||
171+
dynamicRouteMatcher(internalEvent.rawPath).length > 0
193172
)
194173
) {
195174
internalEvent = {
@@ -229,10 +208,19 @@ export default async function routingHandler(
229208
...nextHeaders,
230209
});
231210

211+
const resolvedRoutes: ResolvedRoute[] = [
212+
...foundStaticRoute,
213+
...foundDynamicRoute,
214+
];
215+
216+
debug("resolvedRoutes", resolvedRoutes);
217+
232218
return {
233219
internalEvent,
234220
isExternalRewrite,
235221
origin: false,
236222
isISR,
223+
initialPath: event.rawPath,
224+
resolvedRoutes,
237225
};
238226
}

0 commit comments

Comments
 (0)