Problem
Workkit ships first-class framework integrations under integrations/{astro,hono,remix}/ that wrap typed env access, binding helpers, and request-context plumbing. Astro is supported via @workkit/astro (Cloudflare Pages adapter — context.locals.runtime.env).
OpenNext on Cloudflare (@opennextjs/cloudflare) is becoming the default Next.js → Workers path, but consumers have no idiomatic way to get workkit's EnvAccessor / binding helpers inside a Next.js app:
- Bindings live behind
getCloudflareContext() from @opennextjs/cloudflare — async, sync-cached, and only valid inside the request scope.
- Next.js Route Handlers, Server Components, Server Actions, and Middleware each have slightly different access patterns (request, async-local-storage, edge vs nodejs runtime).
@workkit/astro's defineEnv(astroContext) shape doesn't translate — there's no equivalent of context.locals.runtime to plumb through.
Proposed package
integrations/opennext-cloudflare/ → publishes as @workkit/opennext-cloudflare.
API mirrors @workkit/astro where it makes sense, adapted to Next's surface:
// In a Route Handler / Server Action / Server Component
import { defineEnv } from \"@workkit/opennext-cloudflare\";
import { z } from \"zod\";
const getEnv = defineEnv({
DB: z.custom<D1Database>(),
AI: z.custom<Ai>(),
RESEND_API_KEY: z.string(),
});
export async function GET() {
const env = await getEnv(); // wraps getCloudflareContext()
const result = await env.DB.prepare(...).first();
return Response.json(result);
}
Surface to design (this ticket is the umbrella — split into sub-issues during planning):
defineEnv(schema) → async accessor that reads getCloudflareContext().env and validates via Standard Schema (same as @workkit/astro).
getBindings() → untyped escape hatch for callers that just want env without a schema.
getRequestCtx() → returns { env, ctx, cf } shape for parity with the Astro CloudflareRuntime type.
- Middleware adapter — Next.js middleware runs on the edge runtime; needs to handle the case where
getCloudflareContext() is unavailable (build-time / dev) and degrade with a clear error message, not a generic "env is undefined."
- Docs — "Using workkit with Next.js + OpenNext on Workers" guide covering Route Handlers, Server Actions, Server Components, and Middleware.
Scope check / non-goals
- Not a Next.js plugin / build tool. We don't fork or wrap @opennextjs/cloudflare — it stays a peer dependency.
- Not a Pages adapter. Pages + Next.js is deprecated by Cloudflare; OpenNext is the path forward.
- Not a re-export hub. Don't re-export
@workkit/auth, @workkit/chat, etc. — consumers import those directly. This package is purely the env/bindings/context bridge.
Risks / open questions
getCloudflareContext() is async by default but also has a sync mode (getCloudflareContext({ async: false })). The accessor likely needs both shapes — getEnv() (async, safest) and getEnvSync() (sync, with the documented caveat that it must be called after the async version has run once in the request).
- Edge runtime vs Node.js runtime: workkit packages assume workerd. Need to document that route handlers using workkit must opt into
export const runtime = 'edge' or rely on OpenNext's default workerd execution.
- Compatibility window: pin to a minimum
@opennextjs/cloudflare version that has stable getCloudflareContext() semantics.
Acceptance
Problem
Workkit ships first-class framework integrations under
integrations/{astro,hono,remix}/that wrap typed env access, binding helpers, and request-context plumbing. Astro is supported via@workkit/astro(Cloudflare Pages adapter —context.locals.runtime.env).OpenNext on Cloudflare (@opennextjs/cloudflare) is becoming the default Next.js → Workers path, but consumers have no idiomatic way to get workkit's
EnvAccessor/ binding helpers inside a Next.js app:getCloudflareContext()from@opennextjs/cloudflare— async, sync-cached, and only valid inside the request scope.@workkit/astro'sdefineEnv(astroContext)shape doesn't translate — there's no equivalent ofcontext.locals.runtimeto plumb through.Proposed package
integrations/opennext-cloudflare/→ publishes as@workkit/opennext-cloudflare.API mirrors
@workkit/astrowhere it makes sense, adapted to Next's surface:Surface to design (this ticket is the umbrella — split into sub-issues during planning):
defineEnv(schema)→ async accessor that readsgetCloudflareContext().envand validates via Standard Schema (same as@workkit/astro).getBindings()→ untyped escape hatch for callers that just wantenvwithout a schema.getRequestCtx()→ returns{ env, ctx, cf }shape for parity with the AstroCloudflareRuntimetype.getCloudflareContext()is unavailable (build-time / dev) and degrade with a clear error message, not a generic "env is undefined."Scope check / non-goals
@workkit/auth,@workkit/chat, etc. — consumers import those directly. This package is purely the env/bindings/context bridge.Risks / open questions
getCloudflareContext()is async by default but also has a sync mode (getCloudflareContext({ async: false })). The accessor likely needs both shapes —getEnv()(async, safest) andgetEnvSync()(sync, with the documented caveat that it must be called after the async version has run once in the request).export const runtime = 'edge'or rely on OpenNext's default workerd execution.@opennextjs/cloudflareversion that has stablegetCloudflareContext()semantics.Acceptance
@workkit/astro?)integrations/opennext-cloudflare/scaffolded with same structure asintegrations/astro/defineEnv+getBindings+getRequestCtximplemented with tests against a mockedgetCloudflareContext()apps/docs/