diff --git a/examples/app-pages-router/open-next.config.local.ts b/examples/app-pages-router/open-next.config.local.ts new file mode 100644 index 000000000..ce99297af --- /dev/null +++ b/examples/app-pages-router/open-next.config.local.ts @@ -0,0 +1,34 @@ +import type { + OpenNextConfig, + OverrideOptions, +} from "@opennextjs/aws/types/open-next.js"; + +const devOverride = { + wrapper: "express-dev", + converter: "node", + incrementalCache: "fs-dev", + queue: "direct", + tagCache: "fs-dev-nextMode", +} satisfies OverrideOptions; + +export default { + default: { + override: devOverride, + }, + functions: { + api: { + override: devOverride, + routes: ["app/api/client/route", "app/api/host/route", "pages/api/hello"], + patterns: ["/api/*"], + }, + }, + imageOptimization: { + override: { + wrapper: "dummy", + converter: "dummy", + }, + loader: "fs-dev", + }, + // You can override the build command here so that you don't have to rebuild next every time you make a change + //buildCommand: "echo 'No build command'", +} satisfies OpenNextConfig; diff --git a/examples/app-pages-router/package.json b/examples/app-pages-router/package.json index 61020570a..902cca7ce 100644 --- a/examples/app-pages-router/package.json +++ b/examples/app-pages-router/package.json @@ -4,6 +4,8 @@ "private": true, "scripts": { "openbuild": "node ../../packages/open-next/dist/index.js build --build-command \"npx turbo build\"", + "openbuild:local": "node ../../packages/open-next/dist/index.js build --config-path open-next.config.local.ts", + "openbuild:local:start": "tsx proxy.ts", "dev": "next dev --turbopack --port 3003", "build": "next build", "start": "next start --port 3003", @@ -12,18 +14,21 @@ }, "dependencies": { "@example/shared": "workspace:*", - "next": "catalog:", "@opennextjs/aws": "workspace:*", + "express-http-proxy": "2.1.1", + "next": "catalog:", "react": "catalog:", "react-dom": "catalog:" }, "devDependencies": { + "@types/express-http-proxy": "1.6.7", "@types/node": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", "autoprefixer": "catalog:", "postcss": "catalog:", "tailwindcss": "catalog:", + "tsx": "4.20.5", "typescript": "catalog:" } } diff --git a/examples/app-pages-router/proxy.ts b/examples/app-pages-router/proxy.ts new file mode 100644 index 000000000..ead12fa65 --- /dev/null +++ b/examples/app-pages-router/proxy.ts @@ -0,0 +1,50 @@ +import { spawn } from "node:child_process"; + +import express from "express"; +import proxy from "express-http-proxy"; + +const PORT = process.env.PORT ?? 3000; + +// Start servers +spawn("node", [".open-next/server-functions/default/index.mjs"], { + env: { ...process.env, PORT: "3010" }, + stdio: "inherit", +}); + +spawn("node", [".open-next/server-functions/api/index.mjs"], { + env: { ...process.env, PORT: "3011" }, + stdio: "inherit", +}); + +const app = express(); + +app.use( + "/api/*", + proxy("http://localhost:3011", { + proxyReqPathResolver: (req) => req.originalUrl, + proxyReqOptDecorator: (proxyReqOpts) => { + proxyReqOpts.headers.host = `localhost:${PORT}`; + return proxyReqOpts; + }, + }), +); + +// Catch-all for everything else +app.use( + "*", + proxy("http://localhost:3010", { + proxyReqPathResolver: (req) => req.originalUrl, + proxyReqOptDecorator: (proxyReqOpts) => { + // We need to ensure the host header is set correctly else you will run into this error in `/server-actions` + // Error: Invalid Server Actions request: + // `x-forwarded-host` header with value `localhost:3010` does not match `origin` header with value `localhost:3000` from a forwarded Server Actions request. Aborting the action. + proxyReqOpts.headers.host = `localhost:${PORT}`; + delete proxyReqOpts.headers["x-forwarded-host"]; + return proxyReqOpts; + }, + }), +); + +app.listen(PORT, () => { + console.log(`Proxy running at http://localhost:${PORT}`); +}); diff --git a/examples/app-pages-router/tsconfig.json b/examples/app-pages-router/tsconfig.json index 81f2bef55..35e21a0af 100644 --- a/examples/app-pages-router/tsconfig.json +++ b/examples/app-pages-router/tsconfig.json @@ -24,5 +24,10 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": [ + "node_modules", + "open-next.config.ts", + "open-next.config.local.ts", + "proxy.ts" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b84b8347..dbd70b37a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: '@opennextjs/aws': specifier: workspace:* version: link:../../packages/open-next + express-http-proxy: + specifier: 2.1.1 + version: 2.1.1 next: specifier: 'catalog:' version: 15.4.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -78,6 +81,9 @@ importers: specifier: 'catalog:' version: 19.0.0(react@19.0.0) devDependencies: + '@types/express-http-proxy': + specifier: 1.6.7 + version: 1.6.7 '@types/node': specifier: 'catalog:' version: 20.17.6 @@ -96,6 +102,9 @@ importers: tailwindcss: specifier: 'catalog:' version: 3.3.3(ts-node@10.9.1(@types/node@20.17.6)(typescript@5.6.3)) + tsx: + specifier: 4.20.5 + version: 4.20.5 typescript: specifier: 'catalog:' version: 5.6.3 @@ -2716,6 +2725,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-http-proxy@1.6.7': + resolution: {integrity: sha512-CEp9pbnwVI1RzN9PXc+KESMxwUW5r1O7tkWb5h7Wg/YAIf+KulD/zKev8fbbn+Ljt0Yvs8MXwV2W6Id+cKxe2Q==} + '@types/express-serve-static-core@5.0.3': resolution: {integrity: sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==} @@ -3564,6 +3576,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + esbuild@0.18.13: resolution: {integrity: sha512-vhg/WR/Oiu4oUIkVhmfcc23G6/zWuEQKFS+yiosSHe4aN6+DQRXIfeloYGibIfVhkr4wyfuVsGNLr+sQU1rWWw==} engines: {node: '>=12'} @@ -3634,6 +3649,10 @@ packages: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + express-http-proxy@2.1.1: + resolution: {integrity: sha512-4aRQRqDQU7qNPV5av0/hLcyc0guB9UP71nCYrQEYml7YphTo8tmWf3nDZWdTJMMjFikyz9xKXaURor7Chygdwg==} + engines: {node: '>=6.0.0'} + express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -3808,6 +3827,9 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4968,6 +4990,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -5141,6 +5166,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} @@ -5420,6 +5446,11 @@ packages: typescript: optional: true + tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} + engines: {node: '>=18.0.0'} + hasBin: true + turbo-darwin-64@1.10.12: resolution: {integrity: sha512-vmDfGVPl5/aFenAbOj3eOx3ePNcWVUyZwYr7taRl0ZBbmv2TzjRiFotO4vrKCiTVnbqjQqAFQWY2ugbqCI1kOQ==} cpu: [x64] @@ -9354,6 +9385,10 @@ snapshots: '@types/estree@1.0.6': {} + '@types/express-http-proxy@1.6.7': + dependencies: + '@types/express': 5.0.0 + '@types/express-serve-static-core@5.0.3': dependencies: '@types/node': 20.17.6 @@ -10249,6 +10284,8 @@ snapshots: es-errors@1.3.0: {} + es6-promise@4.2.8: {} + esbuild@0.18.13: optionalDependencies: '@esbuild/android-arm': 0.18.13 @@ -10405,6 +10442,14 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 + express-http-proxy@2.1.1: + dependencies: + debug: 3.1.0 + es6-promise: 4.2.8 + raw-body: 2.5.2 + transitivePeerDependencies: + - supports-color + express@4.21.2: dependencies: accepts: 1.3.8 @@ -10651,6 +10696,10 @@ snapshots: get-stream@6.0.1: {} + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -11802,6 +11851,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -12455,6 +12506,13 @@ snapshots: - supports-color - ts-node + tsx@4.20.5: + dependencies: + esbuild: 0.25.4 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + turbo-darwin-64@1.10.12: optional: true