Skip to content

Commit 2c2f6b9

Browse files
conico974fwang
andauthored
fix: group routes react resolution (#131)
* fix: group routes react resolution * Sync --------- Co-authored-by: Frank <wangfanjie@gmail.com>
1 parent 7f085fa commit 2c2f6b9

File tree

5 files changed

+72
-48
lines changed

5 files changed

+72
-48
lines changed

.changeset/six-vans-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": patch
3+
---
4+
5+
server: fix react resolution for group routes

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface NextConfig {
3636
images: ImageConfig;
3737
}
3838

39-
interface RouteDefinition {
39+
export interface RouteDefinition {
4040
page: string;
4141
regex: string;
4242
}

packages/open-next/src/adapters/require-hooks.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@ const resolveFilename = mod._resolveFilename;
1111
const hookPropertyMapApp = new Map();
1212
const hookPropertyMapPage = new Map();
1313

14-
export function addHookAliases(
14+
export function overrideHooks(config: NextConfig) {
15+
try {
16+
overrideDefault();
17+
overrideReact(config);
18+
} catch (e) {
19+
console.error("Failed to override Next.js require hooks.", e);
20+
throw e;
21+
}
22+
}
23+
24+
function addHookAliases(
1525
aliases: [string, string][] = [],
1626
type: "app" | "page"
1727
) {
@@ -23,7 +33,7 @@ export function addHookAliases(
2333
}
2434

2535
// Add default aliases
26-
export function overrideDefault() {
36+
function overrideDefault() {
2737
addHookAliases(
2838
[
2939
// Use `require.resolve` explicitly to make them statically analyzable
@@ -37,7 +47,7 @@ export function overrideDefault() {
3747
}
3848

3949
// Override built-in React packages if necessary
40-
export function overrideReact(config: NextConfig) {
50+
function overrideReact(config: NextConfig) {
4151
addHookAliases(
4252
[
4353
["react", require.resolve(`react`)],
@@ -196,20 +206,22 @@ function isApp() {
196206
);
197207
}
198208

199-
mod._resolveFilename = function (
200-
originalResolveFilename: typeof resolveFilename,
201-
requestMapApp: Map<string, string>,
202-
requestMapPage: Map<string, string>,
203-
request: string,
204-
parent: any,
205-
isMain: boolean,
206-
options: any
207-
) {
208-
const hookResolved = isApp()
209-
? requestMapApp.get(request)
210-
: requestMapPage.get(request);
211-
if (hookResolved) request = hookResolved;
212-
return originalResolveFilename.call(mod, request, parent, isMain, options);
209+
export function applyOverride() {
210+
mod._resolveFilename = function (
211+
originalResolveFilename: typeof resolveFilename,
212+
requestMapApp: Map<string, string>,
213+
requestMapPage: Map<string, string>,
214+
request: string,
215+
parent: any,
216+
isMain: boolean,
217+
options: any
218+
) {
219+
const hookResolved = isApp()
220+
? requestMapApp.get(request)
221+
: requestMapPage.get(request);
222+
if (hookResolved) request = hookResolved;
223+
return originalResolveFilename.call(mod, request, parent, isMain, options);
213224

214-
// We use `bind` here to avoid referencing outside variables to create potential memory leaks.
215-
}.bind(null, resolveFilename, hookPropertyMapApp, hookPropertyMapPage);
225+
// We use `bind` here to avoid referencing outside variables to create potential memory leaks.
226+
}.bind(null, resolveFilename, hookPropertyMapApp, hookPropertyMapPage);
227+
}

packages/open-next/src/adapters/server-adapter.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
} from "aws-lambda";
99
import {
1010
generateUniqueId,
11-
loadAppPathsManifest,
11+
loadAppPathsManifestKeys,
1212
loadConfig,
1313
loadHtmlPages,
1414
loadPublicAssets,
@@ -18,7 +18,10 @@ import {
1818
import { isBinaryContentType } from "./binary.js";
1919
import { debug } from "./logger.js";
2020
import { convertFrom, convertTo } from "./event-mapper.js";
21-
import { overrideDefault, overrideReact } from "./require-hooks.js";
21+
import {
22+
overrideHooks as overrideNextjsRequireHooks,
23+
applyOverride as applyNextjsRequireHooksOverride,
24+
} from "./require-hooks.js";
2225
import type { WarmerEvent, WarmerResponse } from "./warmer-function.js";
2326

2427
const NEXT_DIR = path.join(__dirname, ".next");
@@ -30,17 +33,22 @@ setNextjsServerWorkingDirectory();
3033
const config = loadConfig(NEXT_DIR);
3134
const htmlPages = loadHtmlPages(NEXT_DIR);
3235
const routesManifest = loadRoutesManifest(NEXT_DIR);
33-
const appPathsManifest = loadAppPathsManifest(NEXT_DIR);
36+
const appPathsManifestKeys = loadAppPathsManifestKeys(NEXT_DIR);
3437
const publicAssets = loadPublicAssets(OPEN_NEXT_DIR);
3538
// Generate a 6 letter unique server ID
3639
const serverId = `server-${generateUniqueId()}`;
3740

38-
// Need to override the require hooks for React before Next.js server
39-
// overrides them with prebundled ones in the case of app dir
41+
// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
42+
// Step 1: Need to override the require hooks for React before Next.js server
43+
// overrides them with prebundled ones in the case of app dir
44+
// Step 2: Import Next.js server
45+
// Step 3: Apply the override after Next.js server is imported since the
46+
// override that Next.js does is done at import time
4047
overrideNextjsRequireHooks(config);
41-
4248
// @ts-ignore
4349
import NextServer from "next/dist/server/next-server.js";
50+
applyNextjsRequireHooksOverride();
51+
4452
const requestHandler = new NextServer.default({
4553
hostname: "localhost",
4654
port: Number(process.env.PORT) || 3000,
@@ -96,7 +104,7 @@ export async function handler(
96104
debug("IncomingMessage constructor props", reqProps);
97105
const req = new IncomingMessage(reqProps);
98106
const res = new ServerResponse({ method: reqProps.method });
99-
setNextjsPrebundledReact(req, config);
107+
setNextjsPrebundledReact(internalEvent.rawPath, config);
100108
await processRequest(req, res);
101109

102110
// Format Next.js response to Lambda response
@@ -135,28 +143,16 @@ function setNextjsServerWorkingDirectory() {
135143
process.chdir(__dirname);
136144
}
137145

138-
function overrideNextjsRequireHooks(config: any) {
139-
// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
140-
try {
141-
overrideDefault();
142-
overrideReact(config);
143-
} catch (e) {
144-
console.error("Failed to override Next.js require hooks.", e);
145-
throw e;
146-
}
147-
}
148-
149-
function setNextjsPrebundledReact(req: IncomingMessage, config: any) {
146+
function setNextjsPrebundledReact(rawPath: string, config: any) {
150147
// WORKAROUND: Set `__NEXT_PRIVATE_PREBUNDLED_REACT` to use prebundled React — https://github.com/serverless-stack/open-next#workaround-set-__next_private_prebundled_react-to-use-prebundled-react
151148

152149
// Get route pattern
153-
const route = [
154-
...routesManifest.staticRoutes,
155-
...routesManifest.dynamicRoutes,
156-
].find((route) => new RegExp(route.regex).test(req.url ?? ""));
150+
const route = routesManifest.find((route) =>
151+
new RegExp(route.regex).test(rawPath ?? "")
152+
);
157153

158-
const isApp = appPathsManifest[`${route?.page}/page`];
159-
debug("setNextjsPrebundledReact", { url: req.url, isApp });
154+
const isApp = appPathsManifestKeys.includes(route?.page ?? "");
155+
debug("setNextjsPrebundledReact", { url: rawPath, isApp, route });
160156

161157
// app routes => use prebundled React
162158
if (isApp) {

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ export function loadPublicAssets(openNextDir: string) {
3535
export function loadRoutesManifest(nextDir: string) {
3636
const filePath = path.join(nextDir, "routes-manifest.json");
3737
const json = fs.readFileSync(filePath, "utf-8");
38-
return JSON.parse(json) as RoutesManifest;
38+
const routesManifest = JSON.parse(json) as RoutesManifest;
39+
// Static routes take precedence over dynamic routes
40+
return [...routesManifest.staticRoutes, ...routesManifest.dynamicRoutes];
3941
}
4042

41-
export function loadAppPathsManifest(nextDir: string) {
43+
export function loadAppPathsManifestKeys(nextDir: string) {
4244
const appPathsManifestPath = path.join(
4345
nextDir,
4446
"server",
@@ -47,5 +49,14 @@ export function loadAppPathsManifest(nextDir: string) {
4749
const appPathsManifestJson = fs.existsSync(appPathsManifestPath)
4850
? fs.readFileSync(appPathsManifestPath, "utf-8")
4951
: "{}";
50-
return JSON.parse(appPathsManifestJson) as Record<string, string>;
52+
const appPathsManifest = JSON.parse(appPathsManifestJson) as Record<
53+
string,
54+
string
55+
>;
56+
return Object.keys(appPathsManifest).map((key) => {
57+
// Remove group route params and /page suffix
58+
const cleanedKey = key.replace(/\/\(\w+\)|\/page$/g, "");
59+
// We need to check if the cleaned key is empty because it means it's the root path
60+
return cleanedKey === "" ? "/" : cleanedKey;
61+
});
5162
}

0 commit comments

Comments
 (0)