Skip to content

Commit 521f12a

Browse files
alexcarpenterclaude
andcommitted
fix(astro): Fix compatibility with Astro v6 Cloudflare adapter
Astro v6's Cloudflare adapter removed `locals.runtime.env`, which causes `@clerk/astro` to crash. This adds a fallback that imports env from `cloudflare:workers` when `locals.runtime.env` is unavailable, while maintaining backwards compatibility with Astro v4/v5. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 37a4cbb commit 521f12a

5 files changed

Lines changed: 51 additions & 6 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/astro': patch
3+
---
4+
5+
Fix compatibility with Astro v6 Cloudflare adapter by using `cloudflare:workers` env when `locals.runtime.env` is unavailable

packages/astro/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
"astro": "^5.17.1"
101101
},
102102
"peerDependencies": {
103-
"astro": "^4.15.0 || ^5.0.0"
103+
"astro": "^4.15.0 || ^5.0.0 || ^6.0.0"
104104
},
105105
"engines": {
106106
"node": ">=20.9.0"

packages/astro/src/env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface ImportMeta {
3030

3131
declare namespace App {
3232
interface Locals {
33-
runtime: { env: InternalEnv };
33+
runtime?: { env: InternalEnv };
3434
keylessClaimUrl?: string;
3535
keylessApiKeysUrl?: string;
3636
keylessPublishableKey?: string;

packages/astro/src/server/clerk-middleware.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { canUseKeyless } from '../utils/feature-flags';
2828
import { buildClerkHotloadScript } from './build-clerk-hotload-script';
2929
import { clerkClient } from './clerk-client';
3030
import { createCurrentUser } from './current-user';
31-
import { getClientSafeEnv, getSafeEnv } from './get-safe-env';
31+
import { getClientSafeEnv, getSafeEnv, initCloudflareEnv } from './get-safe-env';
3232
import { resolveKeysWithKeylessFallback } from './keyless/utils';
3333
import { serverRedirectWithAuth } from './server-redirect-with-auth';
3434
import type {
@@ -79,6 +79,8 @@ export const clerkMiddleware: ClerkMiddleware = (...args: unknown[]): any => {
7979
return next();
8080
}
8181

82+
await initCloudflareEnv();
83+
8284
const clerkRequest = createClerkRequest(context.request);
8385

8486
// Resolve keyless URLs per-request in development

packages/astro/src/server/get-safe-env.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,53 @@ import type { APIContext } from 'astro';
33

44
type ContextOrLocals = APIContext | APIContext['locals'];
55

6+
/**
7+
* Cached env object from `cloudflare:workers` for Astro v6+ Cloudflare adapter.
8+
* - `undefined`: not yet attempted
9+
* - `null`: attempted but not available (non-Cloudflare environment)
10+
* - object: the env object from `cloudflare:workers`
11+
*/
12+
let cloudflareEnv: Record<string, string> | null | undefined;
13+
14+
/**
15+
* @internal
16+
* Attempts to import env from `cloudflare:workers` and caches the result.
17+
* This is needed for Astro v6+ where `locals.runtime.env` is no longer available.
18+
* Safe to call in non-Cloudflare environments — will no-op.
19+
*/
20+
async function initCloudflareEnv(): Promise<void> {
21+
if (cloudflareEnv !== undefined) {
22+
return;
23+
}
24+
try {
25+
// Use a variable to prevent TypeScript from resolving the module specifier
26+
const moduleName = 'cloudflare:workers';
27+
const mod = await import(/* @vite-ignore */ moduleName);
28+
cloudflareEnv = mod.env;
29+
} catch {
30+
cloudflareEnv = null;
31+
}
32+
}
33+
634
/**
735
* @internal
836
* Isomorphic handler for reading environment variables defined from Vite or are injected in the request context (CF Pages)
937
*/
1038
function getContextEnvVar(envVarName: keyof InternalEnv, contextOrLocals: ContextOrLocals): string | undefined {
1139
const locals = 'locals' in contextOrLocals ? contextOrLocals.locals : contextOrLocals;
1240

13-
if (locals?.runtime?.env) {
14-
return locals.runtime.env[envVarName];
41+
// Astro v4/v5 Cloudflare adapter: env is on locals.runtime.env
42+
try {
43+
if (locals?.runtime?.env) {
44+
return locals.runtime.env[envVarName];
45+
}
46+
} catch {
47+
// Astro v6 Cloudflare adapter throws when accessing locals.runtime.env
48+
}
49+
50+
// Astro v6 Cloudflare adapter: env from cloudflare:workers
51+
if (cloudflareEnv) {
52+
return cloudflareEnv[envVarName];
1553
}
1654

1755
return import.meta.env[envVarName];
@@ -72,4 +110,4 @@ function getClientSafeEnv(context: ContextOrLocals) {
72110
};
73111
}
74112

75-
export { getSafeEnv, getClientSafeEnv };
113+
export { getSafeEnv, getClientSafeEnv, initCloudflareEnv };

0 commit comments

Comments
 (0)