Skip to content

Commit 07da3c3

Browse files
implement getCloudflareContext for production
1 parent 42bf0ff commit 07da3c3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+116
-20
lines changed

examples/api/app/api/hello/route.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { headers } from "next/headers";
22

3+
import { getCloudflareContext } from "@opennextjs/cloudflare";
4+
35
export async function GET() {
4-
// Note: we use headers just so that the route is not built as a static one
56
const headersList = headers();
6-
const sayHi = !!headersList.get("should-say-hi");
7-
return new Response(sayHi ? "Hi World!" : "Hello World!");
7+
8+
const fromToml = !!headersList.get("from-cloudflare-context");
9+
10+
if (!fromToml) {
11+
return new Response("Hello World!");
12+
}
13+
14+
const { env } = await getCloudflareContext();
15+
return new Response(env.hello);
816
}
917

1018
export async function POST(request: Request) {

examples/api/e2e-tests/base.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ test("the hello-world api GET route works as intended", async ({ page }) => {
1515
expect(await res.text()).toEqual("Hello World!");
1616
});
1717

18+
test("returns a hello world string from the cloudflare context env", async ({ page }) => {
19+
const res = await page.request.get("/api/hello", {
20+
headers: {
21+
"from-cloudflare-context": "true",
22+
},
23+
});
24+
expect(res.headers()["content-type"]).toContain("text/plain");
25+
expect(await res.text()).toEqual("Hello World from the cloudflare context!");
26+
});
27+
1828
test("the hello-world api POST route works as intended", async ({ page }) => {
1929
const res = await page.request.post("/api/hello", { data: "some body" });
2030
expect(res.headers()["content-type"]).toContain("text/plain");

examples/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"build:worker": "pnpm cloudflare",
1111
"dev:worker": "wrangler dev --port 8770",
1212
"preview:worker": "pnpm build:worker && pnpm dev:worker",
13-
"e2e": "playwright test"
13+
"e2e": "playwright test",
14+
"cf-typegen": "wrangler types --env-interface CloudflareEnv"
1415
},
1516
"dependencies": {
1617
"next": "catalog:",

examples/api/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
}
1919
]
2020
},
21-
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
21+
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "worker-configuration.d.ts"],
2222
"exclude": ["node_modules"]
2323
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Generated by Wrangler by running `wrangler types --env-interface CloudflareEnv`
2+
3+
interface CloudflareEnv {
4+
hello: "Hello World from the cloudflare context!";
5+
}

examples/api/wrangler.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ compatibility_date = "2024-09-16"
55
compatibility_flags = ["nodejs_compat_v2"]
66

77
experimental_assets = { directory = ".worker-next/assets", binding = "ASSETS" }
8+
9+
[vars]
10+
hello = 'Hello World from the cloudflare context!'

packages/cloudflare/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
"test": "vitest --run",
99
"test:watch": "vitest"
1010
},
11-
"bin": "dist/index.mjs",
11+
"bin": "dist/cli/index.mjs",
12+
"main": "./dist/api/index.mjs",
13+
"types": "./dist/api/index.d.mts",
14+
"exports": {
15+
".": {
16+
"import": "./dist/api/index.mjs",
17+
"types": "./dist/api/index.d.mts"
18+
}
19+
},
1220
"files": [
1321
"README.md",
1422
"dist"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import "server-only";
2+
3+
declare global {
4+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
5+
interface CloudflareEnv {}
6+
}
7+
8+
export type CloudflareContext<
9+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
10+
Context = ExecutionContext,
11+
> = {
12+
env: CloudflareEnv;
13+
cf: CfProperties;
14+
ctx: Context;
15+
};
16+
17+
const cloudflareContextSymbol = Symbol.for("__cloudflare-context__");
18+
19+
export async function getCloudflareContext<
20+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
21+
Context = ExecutionContext,
22+
>(): Promise<CloudflareContext<CfProperties, Context>> {
23+
const cloudflareContext = (
24+
globalThis as unknown as {
25+
[cloudflareContextSymbol]: CloudflareContext<CfProperties, Context> | undefined;
26+
}
27+
)[cloudflareContextSymbol];
28+
29+
if (!cloudflareContext) {
30+
// TODO: cloudflareContext should always be present in production/preview, if not it means that this
31+
// is running under `next dev`, in this case use `getPlatformProxy` to return local proxies
32+
throw new Error("Cloudflare context is not defined!");
33+
}
34+
35+
return cloudflareContext;
36+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./get-cloudflare-context";
File renamed without changes.

0 commit comments

Comments
 (0)