Skip to content

Commit 50703a3

Browse files
authored
Fix cloudflare env (#514)
* fix cookies improperly set on node * fix issue with env in cloudflare * fix cookies not properly set when set both in the middleware and a route * bundle everything for edge into one file * avoid bundling in one file when middleware is inside server function * added option for headers priority * Create weak-hotels-thank.md
1 parent 6b894df commit 50703a3

File tree

12 files changed

+113
-38
lines changed

12 files changed

+113
-38
lines changed

.changeset/weak-hotels-thank.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+
Fix cloudflare env
6+
Fix an issue with cookies and the node wrapper
7+
Fix some issue with cookies being not properly set when set both in the routing layer and the route itself
8+
Added option for headers priority

packages/open-next/src/build.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,12 +759,14 @@ async function createMiddleware() {
759759
overrides: config.middleware?.override,
760760
defaultConverter: "aws-cloudfront",
761761
includeCache: config.dangerous?.enableCacheInterception,
762+
additionalExternals: config.edgeExternals,
762763
});
763764
} else {
764765
await buildEdgeBundle({
765766
entrypoint: path.join(__dirname, "core", "edgeFunctionHandler.js"),
766767
outfile: path.join(outputDir, ".build", "middleware.mjs"),
767768
...commonMiddlewareOptions,
769+
onlyBuildOnce: true,
768770
});
769771
}
770772
}

packages/open-next/src/build/createServerBundle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export async function createServerBundle(
5151
const routes = fnOptions.routes;
5252
routes.forEach((route) => foundRoutes.add(route));
5353
if (fnOptions.runtime === "edge") {
54-
await generateEdgeBundle(name, options, fnOptions);
54+
await generateEdgeBundle(name, config, options, fnOptions);
5555
} else {
5656
await generateBundle(name, config, options, fnOptions);
5757
}

packages/open-next/src/build/edge/createEdgeBundle.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { mkdirSync } from "node:fs";
22
import url from "node:url";
33

4+
import { build } from "esbuild";
45
import fs from "fs";
56
import path from "path";
67
import { MiddlewareInfo, MiddlewareManifest } from "types/next-types";
78
import {
89
IncludedConverter,
10+
OpenNextConfig,
911
OverrideOptions,
1012
RouteTemplate,
1113
SplittedFunctionOptions,
@@ -29,6 +31,8 @@ interface BuildEdgeBundleOptions {
2931
defaultConverter?: IncludedConverter;
3032
additionalInject?: string;
3133
includeCache?: boolean;
34+
additionalExternals?: string[];
35+
onlyBuildOnce?: boolean;
3236
}
3337

3438
export async function buildEdgeBundle({
@@ -41,7 +45,13 @@ export async function buildEdgeBundle({
4145
overrides,
4246
additionalInject,
4347
includeCache,
48+
additionalExternals,
49+
onlyBuildOnce,
4450
}: BuildEdgeBundleOptions) {
51+
const isInCloudfare =
52+
typeof overrides?.wrapper === "string"
53+
? overrides.wrapper === "cloudflare"
54+
: (await overrides?.wrapper?.())?.edgeRuntime;
4555
await esbuildAsync(
4656
{
4757
entryPoints: [entrypoint],
@@ -93,7 +103,7 @@ export async function buildEdgeBundle({
93103
"../../core",
94104
"edgeFunctionHandler.js",
95105
),
96-
isInCloudfare: overrides?.wrapper === "cloudflare",
106+
isInCloudfare,
97107
}),
98108
],
99109
treeShaking: true,
@@ -106,8 +116,13 @@ export async function buildEdgeBundle({
106116
mainFields: ["module", "main"],
107117
banner: {
108118
js: `
119+
import {Buffer} from "node:buffer";
120+
globalThis.Buffer = Buffer;
121+
122+
import {AsyncLocalStorage} from "node:async_hooks";
123+
globalThis.AsyncLocalStorage = AsyncLocalStorage;
109124
${
110-
overrides?.wrapper === "cloudflare"
125+
isInCloudfare
111126
? ""
112127
: `
113128
const require = (await import("node:module")).createRequire(import.meta.url);
@@ -129,12 +144,30 @@ export async function buildEdgeBundle({
129144
},
130145
options,
131146
);
147+
148+
if (!onlyBuildOnce) {
149+
await build({
150+
entryPoints: [outfile],
151+
outfile,
152+
allowOverwrite: true,
153+
bundle: true,
154+
minify: true,
155+
platform: "node",
156+
format: "esm",
157+
conditions: ["workerd", "worker", "browser"],
158+
external: ["node:*", ...(additionalExternals ?? [])],
159+
banner: {
160+
js: 'import * as process from "node:process";',
161+
},
162+
});
163+
}
132164
}
133165

134166
export function copyMiddlewareAssetsAndWasm({}) {}
135167

136168
export async function generateEdgeBundle(
137169
name: string,
170+
config: OpenNextConfig,
138171
options: BuildOptions,
139172
fnOptions: SplittedFunctionOptions,
140173
) {
@@ -193,5 +226,6 @@ export async function generateEdgeBundle(
193226
outfile: path.join(outputPath, "index.mjs"),
194227
options,
195228
overrides: fnOptions.override,
229+
additionalExternals: config.edgeExternals,
196230
});
197231
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ declare global {
2525
requestId: string;
2626
pendingPromiseRunner: DetachedPromiseRunner;
2727
isISRRevalidation?: boolean;
28+
mergeHeadersPriority?: "middleware" | "handler";
2829
}>;
2930
}
3031

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ globalThis.__als = new AsyncLocalStorage<{
1919
requestId: string;
2020
pendingPromiseRunner: DetachedPromiseRunner;
2121
isISRRevalidation?: boolean;
22+
mergeHeadersPriority?: "middleware" | "handler";
2223
}>();
2324

2425
patchAsyncStorage();
@@ -103,8 +104,19 @@ export async function openNextHandler(
103104
const pendingPromiseRunner: DetachedPromiseRunner =
104105
new DetachedPromiseRunner();
105106
const isISRRevalidation = headers["x-isr"] === "1";
107+
const mergeHeadersPriority = globalThis.openNextConfig.dangerous
108+
?.headersAndCookiesPriority
109+
? globalThis.openNextConfig.dangerous.headersAndCookiesPriority(
110+
preprocessedEvent,
111+
)
112+
: "middleware";
106113
const internalResult = await globalThis.__als.run(
107-
{ requestId, pendingPromiseRunner, isISRRevalidation },
114+
{
115+
requestId,
116+
pendingPromiseRunner,
117+
isISRRevalidation,
118+
mergeHeadersPriority,
119+
},
108120
async () => {
109121
const preprocessedResult = preprocessResult as MiddlewareOutputEvent;
110122
const req = new IncomingMessage(reqProps);

packages/open-next/src/http/openNextResponse.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
8282
private initialHeaders?: OutgoingHttpHeaders,
8383
) {
8484
super();
85-
if (initialHeaders && initialHeaders[SET_COOKIE_HEADER]) {
86-
this._cookies = parseCookies(
87-
initialHeaders[SET_COOKIE_HEADER] as string | string[],
88-
) as string[];
89-
}
9085
this.once("finish", () => {
9186
if (!this.headersSent) {
9287
this.flushHeaders();
@@ -164,28 +159,43 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
164159
this.headersSent = true;
165160
// Initial headers should be merged with the new headers
166161
// These initial headers are the one created either in the middleware or in next.config.js
167-
// We choose to override response headers with middleware headers
168-
// This is different than the default behavior in next.js, but it allows more customization
169-
// TODO: We probably want to change this behavior in the future to follow next
170-
// We could add a prefix header that would allow to force the middleware headers
171-
// Something like open-next-force-cache-control would override the cache-control header
162+
const mergeHeadersPriority =
163+
globalThis.__als?.getStore()?.mergeHeadersPriority ?? "middleware";
172164
if (this.initialHeaders) {
173-
this.headers = {
174-
...this.headers,
175-
...this.initialHeaders,
176-
};
165+
this.headers =
166+
mergeHeadersPriority === "middleware"
167+
? {
168+
...this.headers,
169+
...this.initialHeaders,
170+
}
171+
: {
172+
...this.initialHeaders,
173+
...this.headers,
174+
};
175+
const initialCookies = parseCookies(
176+
(this.initialHeaders[SET_COOKIE_HEADER] as string | string[]) ?? [],
177+
) as string[];
178+
this._cookies =
179+
mergeHeadersPriority === "middleware"
180+
? [...this._cookies, ...initialCookies]
181+
: [...initialCookies, ...this._cookies];
177182
}
178183
this.fixHeaders(this.headers);
179-
if (this._cookies.length > 0) {
180-
// For cookies we cannot do the same as for other headers
181-
this.headers[SET_COOKIE_HEADER] = this._cookies;
182-
}
184+
185+
// We need to fix the set-cookie header here
186+
this.headers[SET_COOKIE_HEADER] = this._cookies;
187+
188+
const parsedHeaders = parseHeaders(this.headers);
189+
190+
// We need to remove the set-cookie header from the parsed headers because
191+
// it does not handle multiple set-cookie headers properly
192+
delete parsedHeaders[SET_COOKIE_HEADER];
183193

184194
if (this.streamCreator) {
185195
this.responseStream = this.streamCreator?.writeHeaders({
186196
statusCode: this.statusCode ?? 200,
187197
cookies: this._cookies,
188-
headers: parseHeaders(this.headers),
198+
headers: parsedHeaders,
189199
});
190200
this.pipe(this.responseStream);
191201
}

packages/open-next/src/plugins/edge.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,8 @@ export function openNextEdgePlugins({
9292
contents = `
9393
globalThis._ENTRIES = {};
9494
globalThis.self = globalThis;
95-
if(!globalThis.process){
96-
globalThis.process = {env: {}};
97-
}
9895
globalThis._ROUTES = ${JSON.stringify(routes)};
9996
100-
import {Buffer} from "node:buffer";
101-
globalThis.Buffer = Buffer;
102-
103-
import {AsyncLocalStorage} from "node:async_hooks";
104-
globalThis.AsyncLocalStorage = AsyncLocalStorage;
10597
${
10698
isInCloudfare
10799
? ``

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ export interface DangerousOptions {
4949
* @default false
5050
*/
5151
enableCacheInterception?: boolean;
52+
/**
53+
* Function to determine which headers or cookies takes precedence.
54+
* By default, the middleware headers and cookies will override the handler headers and cookies.
55+
* This is executed for every request and after next config headers and middleware has executed.
56+
*/
57+
headersAndCookiesPriority?: (
58+
event: InternalEvent,
59+
) => "middleware" | "handler";
5260
}
5361

5462
export type BaseOverride = {
@@ -83,6 +91,7 @@ export type Wrapper<
8391
> = BaseOverride & {
8492
wrapper: WrapperHandler<E, R>;
8593
supportStreaming: boolean;
94+
edgeRuntime?: boolean;
8695
};
8796

8897
export type Warmer = BaseOverride & {

packages/open-next/src/wrappers/aws-lambda-streaming.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ const handler: WrapperHandler = async (handler, converter) =>
3535
return;
3636
}
3737

38-
let headersWritten = false;
39-
4038
const internalEvent = await converter.convertFrom(event);
4139

4240
//Handle compression
@@ -84,14 +82,12 @@ const handler: WrapperHandler = async (handler, converter) =>
8482
"application/vnd.awslambda.http-integration-response",
8583
);
8684
_prelude.headers["content-encoding"] = contentEncoding;
87-
// We need to remove the set-cookie header as otherwise it will be set twice, once with the cookies in the prelude, and a second time with the set-cookie headers
88-
delete _prelude.headers["set-cookie"];
85+
8986
const prelude = JSON.stringify(_prelude);
9087

9188
responseStream.write(prelude);
9289

9390
responseStream.write(new Uint8Array(8));
94-
headersWritten = true;
9591

9692
return compressedStream ?? responseStream;
9793
},

0 commit comments

Comments
 (0)