Skip to content

Commit 6123b58

Browse files
authored
Remove node-fetch dependency (#19)
1 parent bb0635d commit 6123b58

File tree

7 files changed

+51
-73
lines changed

7 files changed

+51
-73
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ Create a CloudFront distribution, and dispatch requests to their cooresponding h
139139

140140
Create a Lambda function with the code from `.open-next/middleware-function`, and attach it to the `/_next/data/*` and `/*` behaviors as `viewer request` edge function. This allows the function to run your [Middleware](https://nextjs.org/docs/advanced-features/middleware) code before the request hits your server function, and also before cached content.
141141

142+
The middleware function uses the Node.js 18 [global fetch API](https://nodejs.org/de/blog/announcements/v18-release-announce/#new-globally-available-browser-compatible-apis). It requires to run on Node.js 18 runtime. [See why Node.js 18 runtime is required.](#workaround-add-headersgetall-extension-to-the-middleware-function)
143+
142144
Note that if middleware is not used in the Next.js app, the `middleware-function` will not be generated. In this case, you don't have to create the Lambda@Edge function, and configure it in the CloudFront distribution.
143145

144146
## Limitations and workarounds
@@ -177,6 +179,15 @@ To workaround the issue, the middleware function JSON encodes all request header
177179

178180
Note that the `x-op-middleware-request-headers` and `x-op-middleware-response-headers` headers need to be added to CloudFront distribution's cache policy allowed list.
179181

182+
#### WORKAROUND: Add `Headers.getAll()` extension to the middleware function
183+
184+
Vercel uses the `Headers.getAll()` function in the middleware code. This function is not part of the Node.js 18 [global fetch API](https://nodejs.org/de/blog/announcements/v18-release-announce/#new-globally-available-browser-compatible-apis). We have two options:
185+
186+
1. Inject the `getAll()` function to the global fetch API.
187+
2. Use the [`node-fetch`](https://github.com/node-fetch/node-fetch) package to polyfill the fetch API.
188+
189+
We decided to go with option 1. It does not require addition dependency. And it is likely that Vercel removes the usage of the `getAll()` function down the road.
190+
180191
## Example
181192

182193
In the `example` folder, you can find a Next.js feature test app. Here's a link deployed using SST's [`NextjsSite`](https://docs.sst.dev/constructs/NextjsSite) construct. It contains a handful of pages. Each page aims to test a single Next.js feature.

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
"@vercel/build-utils": "^5.7.0",
2626
"@vercel/next": "^3.3.2",
2727
"esbuild": "^0.15.18",
28-
"node-fetch": "^3.3.0",
2928
"serverless-http": "^3.1.0",
3029
"yargs": "^17.6.2"
3130
},
@@ -39,7 +38,6 @@
3938
},
4039
"repository": {
4140
"type": "git",
42-
"url": "git+https://github.com/serverless-stack/open-next.git",
43-
"directory": "cli"
41+
"url": "git+https://github.com/serverless-stack/open-next.git"
4442
}
4543
}

src/adapters/middleware-adapter.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@ import type {
33
CloudFrontRequestResult,
44
CloudFrontHeaders
55
} from "aws-lambda"
6-
import { default as fetch, Headers, Request, Response } from "node-fetch";
7-
Object.assign(globalThis, {
8-
Request,
9-
Response,
10-
fetch,
11-
Headers,
12-
self: {}
13-
});
6+
147
// @ts-ignore
158
const index = await (() => import("./middleware.js"))();
169

@@ -22,6 +15,7 @@ export async function handler(event: CloudFrontRequestEvent): Promise<CloudFront
2215
console.log(request.headers);
2316

2417
// Convert CloudFront request to Node request
18+
// @ts-ignore Types has not been added for Node 18 native fetch API
2519
const requestHeaders = new Headers();
2620
for (const [key, values] of Object.entries(headers)) {
2721
for (const { value } of values) {
@@ -33,6 +27,7 @@ export async function handler(event: CloudFrontRequestEvent): Promise<CloudFront
3327
const host = headers["host"][0].value;
3428
const qs = querystring.length > 0 ? `?${querystring}` : "";
3529
const url = new URL(`${uri}${qs}`, `https://${host}`);
30+
// @ts-ignore Types has not been added for Node 18 native fetch API
3631
const nodeRequest = new Request(url.toString(), {
3732
method,
3833
headers: requestHeaders,
@@ -49,7 +44,7 @@ export async function handler(event: CloudFrontRequestEvent): Promise<CloudFront
4944
});
5045
console.log("middleware response header", response.headers);
5146

52-
// WORKAROUND (AWS): pass middleware headers to server
47+
// WORKAROUND: Pass headers from middleware function to server function (AWS specific) — https://github.com/serverless-stack/open-next#workaround-pass-headers-from-middleware-function-to-server-function-aws-specific
5348
if (response.headers.get("x-middleware-next") === "1") {
5449
headers["x-op-middleware-request-headers"] = [{
5550
key: "x-op-middleware-request-headers",

src/adapters/server-adapter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const server = slsHttp(
6161
export async function handler(event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> {
6262
console.log(event)
6363

64-
// WORKAROUND (AWS): pass middleware headers to server
64+
// WORKAROUND: Pass headers from middleware function to server function (AWS specific) — https://github.com/serverless-stack/open-next#workaround-pass-headers-from-middleware-function-to-server-function-aws-specific
6565
const middlewareRequestHeaders = JSON.parse(
6666
event.headers["x-op-middleware-request-headers"] || "{}"
6767
);
@@ -70,12 +70,12 @@ export async function handler(event: APIGatewayProxyEventV2, context: Context):
7070
// Invoke NextServer
7171
const response: APIGatewayProxyResultV2 = await server(event, context);
7272

73-
// WORKAROUND: `NextServer` does not set cache response headers for HTML pages
73+
// WORKAROUND: `NextServer` does not set cache response headers for HTML pages — https://github.com/serverless-stack/open-next#workaround-nextserver-does-not-set-cache-response-headers-for-html-pages
7474
if (htmlPages.includes(event.rawPath) && !response.headers?.["cache-control"]) {
7575
response.headers!["cache-control"] = "public, max-age=0, s-maxage=31536000, must-revalidate";
7676
}
7777

78-
// WORKAROUND (AWS): pass middleware headers to server
78+
// WORKAROUND: Pass headers from middleware function to server function (AWS specific) — https://github.com/serverless-stack/open-next#workaround-pass-headers-from-middleware-function-to-server-function-aws-specific
7979
const middlewareResponseHeaders = JSON.parse(
8080
event.headers["x-op-middleware-response-headers"] || "{}"
8181
);

src/build.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export async function build() {
2020

2121
// Generate deployable bundle
2222
printHeader("Generating OpenNext bundle");
23+
printVersion();
2324
cleanupOutputDir();
2425
copyAdapterFiles();
2526
createServerBundle();
@@ -58,6 +59,12 @@ function printHeader(header: string) {
5859
].join("\n"));
5960
}
6061

62+
function printVersion() {
63+
const pathToPackageJson = path.join(__dirname, "../package.json");
64+
const pkg = JSON.parse(fs.readFileSync(pathToPackageJson, "utf-8"));
65+
console.log(`Using v${pkg.version}`);
66+
}
67+
6168
function cleanupOutputDir() {
6269
fs.rmSync(outputDir, { recursive: true, force: true });
6370
}
@@ -198,6 +205,30 @@ function createMiddlewareBundle(buildOutput: any) {
198205
write: true,
199206
allowOverwrite: true,
200207
outfile: path.join(outputPath, "index.mjs"),
208+
banner: {
209+
js: [
210+
// WORKAROUND: Add `Headers.getAll()` extension to the middleware function — https://github.com/serverless-stack/open-next#workaround-add-headersgetall-extension-to-the-middleware-function
211+
"class Response extends globalThis.Response {",
212+
" constructor(body, init) {",
213+
" super(body, init);",
214+
" this.headers.getAll = (name) => {",
215+
" name = name.toLowerCase();",
216+
" if (name !== 'set-cookie') {",
217+
" throw new Error('Headers.getAll is only supported for Set-Cookie');",
218+
" }",
219+
" return [...this.headers.entries()]",
220+
" .filter(([key]) => key === name)",
221+
" .map(([, value]) => value);",
222+
" };",
223+
" }",
224+
"}",
225+
// Polyfill Response and self
226+
"Object.assign(globalThis, {",
227+
" Response,",
228+
" self: {},",
229+
"});",
230+
].join(""),
231+
},
201232
});
202233
if (result.errors.length > 0) {
203234
result.errors.forEach((error) => console.error(error));

tsconfig.json

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,4 @@
99
},
1010
"include": ["src"],
1111
"exclude": []
12-
}
13-
//{
14-
// "compilerOptions": {
15-
// "strict": true,
16-
// "noEmitOnError": true,
17-
// "noFallthroughCasesInSwitch": true,
18-
// "moduleResolution": "node",
19-
// "module": "commonjs",
20-
// "target": "ES2020",
21-
// "esModuleInterop": true,
22-
// "allowJs": true,
23-
// "lib": ["ES2020"],
24-
// "resolveJsonModule": true,
25-
// "sourceMap": true,
26-
// "outDir": "./dist",
27-
// },
28-
// "include": ["src"],
29-
// "exclude": []
30-
//}
12+
}

yarn.lock

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,11 +1525,6 @@ csv@^5.5.0:
15251525
csv-stringify "^5.6.5"
15261526
stream-transform "^2.1.3"
15271527

1528-
data-uri-to-buffer@^4.0.0:
1529-
version "4.0.0"
1530-
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
1531-
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
1532-
15331528
dataloader@^1.4.0:
15341529
version "1.4.0"
15351530
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
@@ -1828,14 +1823,6 @@ fastq@^1.6.0:
18281823
dependencies:
18291824
reusify "^1.0.4"
18301825

1831-
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
1832-
version "3.2.0"
1833-
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
1834-
integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
1835-
dependencies:
1836-
node-domexception "^1.0.0"
1837-
web-streams-polyfill "^3.0.3"
1838-
18391826
fill-range@^7.0.1:
18401827
version "7.0.1"
18411828
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -1867,13 +1854,6 @@ find-yarn-workspace-root2@1.2.16:
18671854
micromatch "^4.0.2"
18681855
pkg-dir "^4.2.0"
18691856

1870-
formdata-polyfill@^4.0.10:
1871-
version "4.0.10"
1872-
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
1873-
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
1874-
dependencies:
1875-
fetch-blob "^3.1.2"
1876-
18771857
fs-extra@^7.0.1:
18781858
version "7.0.1"
18791859
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
@@ -2326,27 +2306,13 @@ mixme@^0.5.1:
23262306
resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.5.4.tgz#8cb3bd0cd32a513c161bf1ca99d143f0bcf2eff3"
23272307
integrity sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==
23282308

2329-
node-domexception@^1.0.0:
2330-
version "1.0.0"
2331-
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
2332-
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
2333-
23342309
node-fetch@^2.5.0:
23352310
version "2.6.7"
23362311
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
23372312
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
23382313
dependencies:
23392314
whatwg-url "^5.0.0"
23402315

2341-
node-fetch@^3.3.0:
2342-
version "3.3.0"
2343-
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.0.tgz#37e71db4ecc257057af828d523a7243d651d91e4"
2344-
integrity sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==
2345-
dependencies:
2346-
data-uri-to-buffer "^4.0.0"
2347-
fetch-blob "^3.1.4"
2348-
formdata-polyfill "^4.0.10"
2349-
23502316
normalize-package-data@^2.5.0:
23512317
version "2.5.0"
23522318
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -2886,11 +2852,6 @@ wcwidth@^1.0.1:
28862852
dependencies:
28872853
defaults "^1.0.3"
28882854

2889-
web-streams-polyfill@^3.0.3:
2890-
version "3.2.1"
2891-
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
2892-
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
2893-
28942855
webidl-conversions@^3.0.0:
28952856
version "3.0.1"
28962857
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"

0 commit comments

Comments
 (0)