Skip to content

Commit 09b33de

Browse files
enable getCloudflareContext to work in middlewares via a new enableEdgeDevGetCloudflareContext utility
1 parent e6078b5 commit 09b33de

File tree

14 files changed

+305
-97
lines changed

14 files changed

+305
-97
lines changed

.changeset/chilly-dryers-begin.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
enable `getCloudflareContext` to work in middlewares via a new `enableEdgeDevGetCloudflareContext` utility
6+
7+
`getCloudflareContext` can't work, during development (with `next dev`), in middlewares since they run in
8+
the edge runtime (see: https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) which
9+
is incompatible with the `wrangler`'s APIs which run in node.js
10+
11+
To solve this a new utility called `enableEdgeDevGetCloudflareContext` has been introduced that allows the
12+
context to be available also in the edge runtime, the utility needs to be called inside the Next.js config
13+
file, for example:
14+
15+
```js
16+
// next.config.mjs
17+
18+
import { enableEdgeDevGetCloudflareContext } from "@opennextjs/cloudflare";
19+
20+
/** @type {import('next').NextConfig} */
21+
const nextConfig = {};
22+
23+
enableEdgeDevGetCloudflareContext();
24+
25+
export default nextConfig;
26+
```
27+
28+
a helpful error is also thrown in `getCloudflareContext` to prompt developers to use the utility
29+
30+
`getCloudflareContext` called in the nodejs runtime works as before without needing any setup
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
import { headers } from "next/headers";
2+
13
export default function MiddlewarePage() {
2-
return <h1>Via middleware</h1>;
4+
const cloudflareContextHeader = headers().get("x-cloudflare-context");
5+
6+
return (
7+
<>
8+
<h1>Via middleware</h1>
9+
<p>
10+
The value of the <i>x-cloudflare-context</i> header is: <br />
11+
<span
12+
style={{
13+
display: "inline-block",
14+
margin: "1rem 2rem",
15+
color: "grey",
16+
fontSize: "1.2rem",
17+
}}
18+
data-testid="cloudflare-context-header"
19+
>
20+
{cloudflareContextHeader}
21+
</span>
22+
</p>
23+
</>
24+
);
325
}

examples/middleware/e2e/base.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ import { test, expect } from "@playwright/test";
33
test("redirect", async ({ page }) => {
44
await page.goto("/");
55
await page.click('[href="/about"]');
6-
expect(page.waitForURL("**/redirected"));
6+
await page.waitForURL("**/redirected");
77
expect(await page.textContent("h1")).toContain("Redirected");
88
});
99

1010
test("rewrite", async ({ page }) => {
1111
await page.goto("/");
1212
await page.click('[href="/another"]');
13-
expect(page.waitForURL("**/another"));
13+
await page.waitForURL("**/another");
1414
expect(await page.textContent("h1")).toContain("Rewrite");
1515
});
1616

1717
test("no matching middleware", async ({ page }) => {
1818
await page.goto("/");
1919
await page.click('[href="/about2"]');
20-
expect(page.waitForURL("**/about2"));
20+
await page.waitForURL("**/about2");
2121
expect(await page.textContent("h1")).toContain("About 2");
2222
});
2323

2424
test("matching noop middleware", async ({ page }) => {
2525
await page.goto("/");
2626
await page.click('[href="/middleware"]');
27-
expect(page.waitForURL("**/middleware"));
27+
await page.waitForURL("**/middleware");
2828
expect(await page.textContent("h1")).toContain("Via middleware");
2929
});
3030

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { test, expect } from "@playwright/test";
2+
3+
test("cloudflare context env object is populated", async ({ page }) => {
4+
await page.goto("/middleware");
5+
const cloudflareContextHeaderElement = page.getByTestId("cloudflare-context-header");
6+
// Note: the text in the span is "keys of `cloudflareContext.env`: MY_VAR, MY_KV, ASSETS" for previews
7+
// and "keys of `cloudflareContext.env`: MY_VAR, MY_KV" in dev (`next dev`)
8+
// that's why we use `toContain` instead of `toEqual`, this is incorrect and the `ASSETS` binding
9+
// should ideally also be part of the dev cloudflare context
10+
// (this is an upstream wrangler issue: https://github.com/cloudflare/workers-sdk/issues/7812)
11+
expect(await cloudflareContextHeaderElement.textContent()).toContain(
12+
"keys of `cloudflareContext.env`: MY_VAR, MY_KV"
13+
);
14+
});

examples/middleware/e2e/playwright.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,8 @@ export default defineConfig({
4949
command: "pnpm preview:worker",
5050
url: "http://localhost:8774",
5151
reuseExistingServer: !process.env.CI,
52+
// the app uses the `enableEdgeDevGetCloudflareContext` which apparently causes the boot up to
53+
// take slightly longer, that's why we need a longer timeout here (we just add 10 seconds to the default 60)
54+
timeout: 60 * 1000 + 10 * 1000,
5255
},
5356
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { defineConfig, devices } from "@playwright/test";
2+
3+
declare var process: { env: Record<string, string> };
4+
5+
/**
6+
* See https://playwright.dev/docs/test-configuration.
7+
*/
8+
export default defineConfig({
9+
testDir: "./",
10+
/* Run tests in files in parallel */
11+
fullyParallel: true,
12+
/* Fail the build on CI if you accidentally left test.only in the source code. */
13+
forbidOnly: !!process.env.CI,
14+
/* Retry on CI only */
15+
retries: process.env.CI ? 2 : 0,
16+
/* Opt out of parallel tests on CI. */
17+
workers: process.env.CI ? 1 : undefined,
18+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
19+
reporter: "html",
20+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
21+
use: {
22+
/* Base URL to use in actions like `await page.goto('/')`. */
23+
baseURL: "http://localhost:3334",
24+
25+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
26+
trace: "on-first-retry",
27+
},
28+
29+
/* Configure projects for major browsers */
30+
projects: [
31+
{
32+
name: "chromium",
33+
use: { ...devices["Desktop Chrome"] },
34+
},
35+
36+
{
37+
name: "firefox",
38+
use: { ...devices["Desktop Firefox"] },
39+
},
40+
41+
{
42+
name: "webkit",
43+
use: { ...devices["Desktop Safari"] },
44+
},
45+
],
46+
47+
/* Run your local dev server before starting the tests */
48+
webServer: {
49+
command: "pnpm dev --port 3334",
50+
url: "http://localhost:3334",
51+
reuseExistingServer: !process.env.CI,
52+
},
53+
});

examples/middleware/middleware.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { NextRequest, NextResponse, NextFetchEvent } from "next/server";
22
import { clerkMiddleware } from "@clerk/nextjs/server";
33

4-
export function middleware(request: NextRequest, event: NextFetchEvent) {
4+
import { getCloudflareContext } from "@opennextjs/cloudflare";
5+
6+
export async function middleware(request: NextRequest, event: NextFetchEvent) {
57
console.log("middleware");
68
if (request.nextUrl.pathname === "/about") {
79
return NextResponse.redirect(new URL("/redirected", request.url));
@@ -16,7 +18,19 @@ export function middleware(request: NextRequest, event: NextFetchEvent) {
1618
})(request, event);
1719
}
1820

19-
return NextResponse.next();
21+
const requestHeaders = new Headers(request.headers);
22+
const cloudflareContext = await getCloudflareContext();
23+
24+
requestHeaders.set(
25+
"x-cloudflare-context",
26+
`keys of \`cloudflareContext.env\`: ${Object.keys(cloudflareContext.env).join(", ")}`
27+
);
28+
29+
return NextResponse.next({
30+
request: {
31+
headers: requestHeaders,
32+
},
33+
});
2034
}
2135

2236
export const config = {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import { enableEdgeDevGetCloudflareContext } from "@opennextjs/cloudflare";
2+
13
/** @type {import('next').NextConfig} */
24
const nextConfig = {};
35

6+
enableEdgeDevGetCloudflareContext();
7+
48
export default nextConfig;

examples/middleware/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"build:worker": "pnpm opennextjs-cloudflare",
1010
"dev:worker": "wrangler dev --port 8774 --inspector-port 9334",
1111
"preview:worker": "pnpm build:worker && pnpm dev:worker",
12-
"e2e": "playwright test -c e2e/playwright.config.ts"
12+
"e2e": "playwright test -c e2e/playwright.config.ts",
13+
"e2e:dev": "playwright test -c e2e/playwright.dev.config.ts"
1314
},
1415
"dependencies": {
1516
"@clerk/nextjs": "6.9.6",

examples/middleware/wrangler.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
"assets": {
77
"directory": ".open-next/assets",
88
"binding": "ASSETS"
9-
}
9+
},
10+
"vars": {
11+
"MY_VAR": "my-var"
12+
},
13+
"kv_namespaces": [{ "binding": "MY_KV", "id": "<id>" }]
1014
}

0 commit comments

Comments
 (0)