Skip to content

Commit 0fef012

Browse files
split getCloudflareContext implementation in getCloudflareContextSync and getCloudflareContextAsync
1 parent 18e755d commit 0fef012

File tree

1 file changed

+73
-31
lines changed

1 file changed

+73
-31
lines changed

packages/cloudflare/src/api/cloudflare-context.ts

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -72,51 +72,93 @@ export function getCloudflareContext<
7272
>(
7373
options: GetCloudflareContextOptions = { async: false }
7474
): CloudflareContext<CfProperties, Context> | Promise<CloudflareContext<CfProperties, Context>> {
75+
return options.async ? getCloudflareContextAsync() : getCloudflareContextSync();
76+
}
77+
78+
/**
79+
* Get the cloudflare context from the current global scope
80+
*/
81+
function getCloudflareContextFromGlobalScope<
82+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
83+
Context = ExecutionContext,
84+
>(): CloudflareContext<CfProperties, Context> | undefined {
85+
const global = globalThis as InternalGlobalThis<CfProperties, Context>;
86+
return global[cloudflareContextSymbol];
87+
}
88+
89+
/**
90+
* Detects whether the current code is being evaluated in a statically generated route
91+
*/
92+
function inSSG<
93+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
94+
Context = ExecutionContext,
95+
>(): boolean {
7596
const global = globalThis as InternalGlobalThis<CfProperties, Context>;
97+
// Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes
98+
// source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57)
99+
return global.__NEXT_DATA__?.nextExport === true;
100+
}
101+
102+
/**
103+
* Utility to get the current Cloudflare context in sync mode
104+
*/
105+
function getCloudflareContextSync<
106+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
107+
Context = ExecutionContext,
108+
>(): CloudflareContext<CfProperties, Context> {
109+
const cloudflareContext = getCloudflareContextFromGlobalScope<CfProperties, Context>();
76110

77-
const cloudflareContext = global[cloudflareContextSymbol];
111+
if (cloudflareContext) {
112+
return cloudflareContext;
113+
}
114+
115+
// The sync mode of `getCloudflareContext`, relies on the context being set on the global state
116+
// by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither
117+
// can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the
118+
// normal global state so we throw with a helpful error message.
119+
if (inSSG()) {
120+
throw new Error(
121+
`\n\nERROR: \`getCloudflareContext\` has been called in a static route,` +
122+
` that is not allowed, this can be solved in different ways:\n\n` +
123+
` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` +
124+
` - avoid calling \`getCloudflareContext\` in the route\n` +
125+
` - make the route non static\n`
126+
);
127+
}
128+
129+
throwMissingInitOpenNextCloudflareForDevError();
130+
}
78131

79-
// whether `getCloudflareContext` is run in "async mode"
80-
const asyncMode = options.async;
132+
/**
133+
* Utility to get the current Cloudflare context in async mode
134+
*/
135+
async function getCloudflareContextAsync<
136+
CfProperties extends Record<string, unknown> = IncomingRequestCfProperties,
137+
Context = ExecutionContext,
138+
>(): Promise<CloudflareContext<CfProperties, Context>> {
139+
const cloudflareContext = getCloudflareContextFromGlobalScope<CfProperties, Context>();
81140

82141
if (cloudflareContext) {
83-
return asyncMode ? Promise.resolve(cloudflareContext) : cloudflareContext;
142+
return cloudflareContext;
84143
}
85144

86145
// Note: Next.js sets process.env.NEXT_RUNTIME to 'nodejs' when the runtime in use is the node.js one
87146
// We want to detect when the runtime is the node.js one so that during development (`next dev`) we know wether
88147
// we are or not in a node.js process and that access to wrangler's node.js apis
89148
const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs";
90149

91-
// Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes
92-
// source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57)
93-
const inSSG = global.__NEXT_DATA__?.nextExport === true;
94-
95-
if (asyncMode) {
96-
if (inNodejsRuntime || inSSG) {
97-
// we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context
98-
return getCloudflareContextFromWrangler<CfProperties, Context>().then((context) => {
99-
addCloudflareContextToNodejsGlobal(context);
100-
return context;
101-
});
102-
}
103-
} else {
104-
// The sync mode of `getCloudflareContext`, relies on the context being set on the global state
105-
// by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither
106-
// can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the
107-
// normal global state so we throw with a helpful error message.
108-
if (inSSG) {
109-
throw new Error(
110-
`\n\nERROR: \`getCloudflareContext\` has been called in a static route,` +
111-
` that is not allowed, this can be solved in different ways:\n\n` +
112-
` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` +
113-
` - avoid calling \`getCloudflareContext\` in the route\n` +
114-
` - make the route non static\n`
115-
);
116-
}
150+
if (inNodejsRuntime || inSSG()) {
151+
// we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context
152+
const cloudflareContext = await getCloudflareContextFromWrangler<CfProperties, Context>();
153+
addCloudflareContextToNodejsGlobal(cloudflareContext);
154+
return cloudflareContext;
117155
}
118156

119-
// The cloudflare context is initialized by the worker so it is always available.
157+
throwMissingInitOpenNextCloudflareForDevError();
158+
}
159+
160+
function throwMissingInitOpenNextCloudflareForDevError(): never {
161+
// In production the cloudflare context is initialized by the worker so it is always available.
120162
// During local development (`next dev`) it might be missing only if the developers hasn't called
121163
// the `initOpenNextCloudflareForDev` function in their Next.js config file
122164
throw new Error(

0 commit comments

Comments
 (0)