Skip to content

Commit e330412

Browse files
conico974fwang
andauthored
Fix: next now use node_modules react for pages and prebundled for app (#113)
* fix: fix next using node_modules react for pages * Sync --------- Co-authored-by: Frank <[email protected]>
1 parent 322c44f commit e330412

File tree

5 files changed

+249
-2
lines changed

5 files changed

+249
-2
lines changed

.changeset/nice-cougars-buy.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: use node_modules React for Pages and prebundled for App

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ For Next.js 13.2 and later versions, you need to explicitly set the `__NEXT_PRIV
418418
419419
> Require these modules with static paths to make sure they are tracked by NFT when building the app in standalone mode, as we are now conditionally aliasing them it's tricky to track them in build time.
420420
421+
On every request, we try to detect whether the route is using the Pages Router or the App Router. If the Pages Router is being used, we set `__NEXT_PRIVATE_PREBUNDLED_REACT` to `undefined`, which means the React version from the `node_modules` is used. However, if the App Router is used, `__NEXT_PRIVATE_PREBUNDLED_REACT` is set, and the prebundled React version is used.
422+
421423
## Example
422424

423425
In the `example` folder, you can find a Next.js benchmark app. It contains a variety of pages that each test a single Next.js feature. The app is deployed to both Vercel and AWS using [SST](https://docs.sst.dev/start/nextjs).

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ type ImageConfigComplete = {
2626
type ImageConfig = Partial<ImageConfigComplete>;
2727

2828
export interface NextConfig {
29+
i18n?: {
30+
locales: string[];
31+
};
2932
experimental: {
3033
serverActions?: boolean;
34+
appDir?: boolean;
3135
};
3236
images: ImageConfig;
3337
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Synchronously inject a require hook for webpack and webpack/. It's required to use the internal ncc webpack version.
2+
// This is needed for userland plugins to attach to the same webpack instance as Next.js'.
3+
// Individually compiled modules are as defined for the compilation in bundles/webpack/packages/*.
4+
5+
import type { NextConfig } from "./next-types.js";
6+
7+
// This module will only be loaded once per process.
8+
9+
const mod = require("module");
10+
const resolveFilename = mod._resolveFilename;
11+
const hookPropertyMapApp = new Map();
12+
const hookPropertyMapPage = new Map();
13+
14+
export function addHookAliases(
15+
aliases: [string, string][] = [],
16+
type: "app" | "page"
17+
) {
18+
for (const [key, value] of aliases) {
19+
type === "app"
20+
? hookPropertyMapApp.set(key, value)
21+
: hookPropertyMapPage.set(key, value);
22+
}
23+
}
24+
25+
// Add default aliases
26+
addHookAliases(
27+
[
28+
// Use `require.resolve` explicitly to make them statically analyzable
29+
// styled-jsx needs to be resolved as the external dependency.
30+
["styled-jsx", require.resolve("styled-jsx")],
31+
["styled-jsx/style", require.resolve("styled-jsx/style")],
32+
["styled-jsx/style", require.resolve("styled-jsx/style")],
33+
["server-only", require.resolve("next/dist/compiled/server-only")],
34+
["client-only", require.resolve("next/dist/compiled/client-only")],
35+
],
36+
"app"
37+
);
38+
39+
// Override built-in React packages if necessary
40+
export function overrideReact(config: NextConfig) {
41+
addHookAliases(
42+
[
43+
["react", `/var/task/node_modules/react`],
44+
["react/jsx-runtime", `/var/task/node_modules/react/jsx-runtime`],
45+
["react/jsx-dev-runtime", `/var/task/node_modules/react/jsx-dev-runtime`],
46+
["react-dom", `/var/task/node_modules/react-dom`],
47+
[
48+
"react-dom/server.browser",
49+
`/var/task/node_modules/react-dom/server.browser`,
50+
],
51+
],
52+
"page"
53+
);
54+
if (config.experimental.appDir) {
55+
if (config.experimental.serverActions) {
56+
addHookAliases(
57+
[
58+
["react", require.resolve(`next/dist/compiled/react-experimental`)],
59+
[
60+
"react/jsx-runtime",
61+
require.resolve(
62+
`next/dist/compiled/react-experimental/jsx-runtime`
63+
),
64+
],
65+
[
66+
"react/jsx-dev-runtime",
67+
require.resolve(
68+
`next/dist/compiled/react-experimental/jsx-dev-runtime`
69+
),
70+
],
71+
[
72+
"react-dom",
73+
require.resolve(
74+
`next/dist/compiled/react-dom-experimental/server-rendering-stub`
75+
),
76+
],
77+
[
78+
"react-dom/client",
79+
require.resolve(`next/dist/compiled/react-dom-experimental/client`),
80+
],
81+
[
82+
"react-dom/server",
83+
require.resolve(`next/dist/compiled/react-dom-experimental/server`),
84+
],
85+
[
86+
"react-dom/server.browser",
87+
require.resolve(
88+
`next/dist/compiled/react-dom-experimental/server.browser`
89+
),
90+
],
91+
[
92+
"react-dom/server.edge",
93+
require.resolve(
94+
`next/dist/compiled/react-dom-experimental/server.edge`
95+
),
96+
],
97+
[
98+
"react-server-dom-webpack/client",
99+
require.resolve(
100+
`next/dist/compiled/react-server-dom-webpack-experimental/client`
101+
),
102+
],
103+
[
104+
"react-server-dom-webpack/client.edge",
105+
require.resolve(
106+
`next/dist/compiled/react-server-dom-webpack-experimental/client.edge`
107+
),
108+
],
109+
[
110+
"react-server-dom-webpack/server.edge",
111+
require.resolve(
112+
`next/dist/compiled/react-server-dom-webpack-experimental/server.edge`
113+
),
114+
],
115+
[
116+
"react-server-dom-webpack/server.node",
117+
require.resolve(
118+
`next/dist/compiled/react-server-dom-webpack-experimental/server.node`
119+
),
120+
],
121+
],
122+
"app"
123+
);
124+
} else {
125+
addHookAliases(
126+
[
127+
["react", require.resolve(`next/dist/compiled/react`)],
128+
[
129+
"react/jsx-runtime",
130+
require.resolve(`next/dist/compiled/react/jsx-runtime`),
131+
],
132+
[
133+
"react/jsx-dev-runtime",
134+
require.resolve(`next/dist/compiled/react/jsx-dev-runtime`),
135+
],
136+
[
137+
"react-dom",
138+
require.resolve(
139+
`next/dist/compiled/react-dom/server-rendering-stub`
140+
),
141+
],
142+
[
143+
"react-dom/client",
144+
require.resolve(`next/dist/compiled/react-dom/client`),
145+
],
146+
[
147+
"react-dom/server",
148+
require.resolve(`next/dist/compiled/react-dom/server`),
149+
],
150+
[
151+
"react-dom/server.browser",
152+
require.resolve(`next/dist/compiled/react-dom/server.browser`),
153+
],
154+
[
155+
"react-dom/server.edge",
156+
require.resolve(`next/dist/compiled/react-dom/server.edge`),
157+
],
158+
[
159+
"react-server-dom-webpack/client",
160+
require.resolve(
161+
`next/dist/compiled/react-server-dom-webpack/client`
162+
),
163+
],
164+
[
165+
"react-server-dom-webpack/client.edge",
166+
require.resolve(
167+
`next/dist/compiled/react-server-dom-webpack/client.edge`
168+
),
169+
],
170+
[
171+
"react-server-dom-webpack/server.edge",
172+
require.resolve(
173+
`next/dist/compiled/react-server-dom-webpack/server.edge`
174+
),
175+
],
176+
[
177+
"react-server-dom-webpack/server.node",
178+
require.resolve(
179+
`next/dist/compiled/react-server-dom-webpack/server.node`
180+
),
181+
],
182+
],
183+
"app"
184+
);
185+
}
186+
}
187+
}
188+
189+
function isApp() {
190+
return (
191+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT === "next" ||
192+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT === "experimental"
193+
);
194+
}
195+
196+
mod._resolveFilename = function (
197+
originalResolveFilename: typeof resolveFilename,
198+
requestMapApp: Map<string, string>,
199+
requestMapPage: Map<string, string>,
200+
request: string,
201+
parent: any,
202+
isMain: boolean,
203+
options: any
204+
) {
205+
const hookResolved = isApp()
206+
? requestMapApp.get(request)
207+
: requestMapPage.get(request);
208+
if (hookResolved) request = hookResolved;
209+
return originalResolveFilename.call(mod, request, parent, isMain, options);
210+
211+
// We use `bind` here to avoid referencing outside variables to create potential memory leaks.
212+
}.bind(null, resolveFilename, hookPropertyMapApp, hookPropertyMapPage);

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import type {
99
} from "aws-lambda";
1010
// @ts-ignore
1111
import NextServer from "next/dist/server/next-server.js";
12+
//@ts-ignore
13+
import { getMaybePagePath } from "next/dist/server/require.js";
1214
import { generateUniqueId, loadConfig, setNodeEnv } from "./util.js";
1315
import { isBinaryContentType } from "./binary.js";
1416
import { debug } from "./logger.js";
1517
import type { PublicFiles } from "../build.js";
1618
import { convertFrom, convertTo } from "./event-mapper.js";
19+
import { overrideReact } from "./require-hooks.js";
1720
import { WarmerEvent, WarmerResponse } from "./warmer-function.js";
1821

1922
setNodeEnv();
@@ -23,7 +26,7 @@ const openNextDir = path.join(__dirname, ".open-next");
2326
const config = loadConfig(nextDir);
2427
const htmlPages = loadHtmlPages();
2528
const publicAssets = loadPublicAssets();
26-
setNextjsPrebundledReact(config);
29+
initializeNextjsRequireHooks(config);
2730
debug({ nextDir });
2831

2932
// Generate a 6 letter unique server ID
@@ -83,6 +86,7 @@ export async function handler(
8386
debug("IncomingMessage constructor props", reqProps);
8487
const req = new IncomingMessage(reqProps);
8588
const res = new ServerResponse({ method: reqProps.method });
89+
setNextjsPrebundledReact(req, config);
8690
await processRequest(req, res);
8791

8892
// Format Next.js response to Lambda response
@@ -121,8 +125,28 @@ function setNextjsServerWorkingDirectory() {
121125
process.chdir(__dirname);
122126
}
123127

124-
function setNextjsPrebundledReact(config: any) {
128+
function initializeNextjsRequireHooks(config: any) {
125129
// 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
130+
overrideReact(config);
131+
}
132+
133+
function setNextjsPrebundledReact(req: IncomingMessage, config: any) {
134+
// 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
135+
136+
// "getMaybePagePath" is not present in older version of next.js
137+
// => use node_modules React
138+
if (!getMaybePagePath) {
139+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = undefined;
140+
return;
141+
}
142+
143+
// pages route => use node_modules React
144+
if (getMaybePagePath(req.url, nextDir, config.i18n?.locales, false)) {
145+
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = undefined;
146+
return;
147+
}
148+
149+
// app router => use prebundled React
126150
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = config.experimental
127151
.serverActions
128152
? "experimental"

0 commit comments

Comments
 (0)