dioxus-cloudflare
The missing bridge between Dioxus server functions and Cloudflare Workers.
What It Does
Write a #[server] function once. It runs on Cloudflare Workers. The client calls it like a normal async function. No manual routing, no manual serialization, no duplicated endpoints.
// shared crate — server function
use *;
use *;
pub async
// client component — just call it
let user = get_user.await;
Architecture
Client WASM Cloudflare Worker
┌──────────┐ fetch() ┌─────────────────────┐
│ #[server] │ ───────────▶ │ handle(req, env) │
│ generates │ │ ↓ set_context() │
│ POST to │ │ ↓ worker→http req │
│ /api/... │ │ ↓ Axum dispatch │
│ │ ◀── JSON ── │ ↓ http→worker resp │
└──────────┘ │ ↓ clear_context() │
└─────────────────────┘
Request Flow
- Client calls
get_user(id)— Dioxus serializes args, sends POST to/api/get_user - Worker
#[event(fetch)]receives the request dioxus_cloudflare::handle(req, env)is called:- Stores
Envin thread-local (cf::env()becomes available) - Stores raw
Requestin thread-local (cf::req()becomes available) - Converts
worker::Request→http::Request - Dispatches through the Dioxus Axum router (
axum_corefeature) - Converts
http::Response→worker::Response - Clears thread-local context
- Stores
- Worker returns the response
Why Thread-Local Works
Cloudflare Workers run one request per isolate at a time (single-threaded WASM). There is no concurrent access to thread-locals within a single Worker invocation.
The Crate Provides
| Export | What It Does |
|---|---|
cf::d1(name) |
D1 database — env + binding + error conversion in one call |
cf::kv(name) |
Workers KV namespace |
cf::r2(name) |
R2 bucket |
cf::durable_object(name) |
Durable Object namespace |
cf::queue(name) |
Queue producer (requires queue feature) |
cf::env() |
Full Worker Env — for bindings without a shorthand |
cf::req() |
Raw worker::Request — headers, IP |
cf::cookie(name) |
Read a named cookie from the request |
cf::cookies() |
Read all cookies from the request |
cf::set_cookie() |
Set an HttpOnly/Secure auth cookie (secure defaults) |
cf::set_cookie_with() |
Set a cookie with custom options (builder pattern) |
cf::clear_cookie() |
Clear a cookie (logout) |
handle(req, env) |
Main entry point — wire this into #[event(fetch)] |
CfError |
Newtype for worker::Error → ServerFnError conversion |
CfResultExt |
.cf() method on Result<T, worker::Error> and Result<T, KvError> |
Prerequisites
This crate requires a patched version of dioxus-server that adds wasm32 target support. Add the following to your workspace Cargo.toml:
[]
= { = "https://github.com/JaffeSystems/dioxus-server-cf.git" }
This is necessary because upstream dioxus-server 0.7.3 does not compile for wasm32-unknown-unknown. The patch applies minimal cfg-gating to make it compatible with Cloudflare Workers.
Usage
Worker Entry Point
use *;
use *;
// Import server functions so they register with inventory
use *;
extern "C"
async
Server Function (Shared Crate)
use *;
use *;
pub async
Client Component
use *;
use create_order;
Crate Structure
src/
├── lib.rs # Public API: cf module, handle(), re-exports
├── bindings.rs # Typed binding shorthands: d1(), kv(), r2(), durable_object(), queue()
├── context.rs # Thread-local Env + Request storage
├── handler.rs # handle() — Worker↔Axum request/response bridge
├── cookie.rs # Cookie read/write helpers + CookieBuilder
├── error.rs # CfError newtype + CfResultExt trait
└── prelude.rs # Convenience re-exports
Implementation Status
Working end-to-end
- Thread-local context (
cf::env(),cf::req()) - Typed binding shorthands (
cf::d1(),cf::kv(),cf::r2(),cf::durable_object(),cf::queue()) - Durable Objects —
cf::durable_object()shorthand for namespace access - Queues —
cf::queue()shorthand for producer access (requiresqueuefeature) - Request headers — read via
cf::req()inside server functions - Cookie reading (
cf::cookie(),cf::cookies()) - Cookie writing (
cf::set_cookie(),cf::clear_cookie()) — queue-based, applied to response automatically - Configurable cookie builder (
cf::set_cookie_with()) — customSameSite,Domain,Path,Max-Age, etc. - Error bridge (
CfError,.cf()) — covers bothworker::ErrorandKvError - Request/response conversion (
worker::Request↔http::Request) dispatch()— Axum router withServerFunction::collect()+.oneshot()__wasm_call_ctors+inventoryinitialization- D1 — read/write queries via
cf::d1("DB") - Workers KV — put/get/delete via
cf::kv("KV") - R2 — put/get/delete via
cf::r2("BUCKET") - Static assets via wrangler
[assets]+run_worker_first = ["/api/*"]
Not yet implemented
- Streaming response support (SSE)
- SSR rendering inside Workers
SSR / Streaming Feasibility
SSR (Hard)
dioxus-server's SSR pipeline uses LocalPoolHandle (a tokio thread pool) for rendering. Cloudflare Workers run single-threaded WASM with no native thread support. The dioxus-ssr crate itself compiles to wasm, but the integration layer in dioxus-server that drives it does not. Adapting this requires a single-threaded executor replacement — a significant refactor.
Streaming (Medium)
Cloudflare Workers support ReadableStream for streaming responses. The current handler.rs collects the entire response body into Vec<u8> before sending. Streaming would require piping axum::body::Body frames into a web_sys::ReadableStream. Dioxus has transport-agnostic streaming architecture in dioxus-server, making this more tractable than full SSR.
Recommendation: Streaming server function responses first (simpler, more immediate value), full page SSR later.
Roadmap
Near-term
- Streaming responses — pipe
axum::body::Body→ReadableStreamfor SSE and large payloads - Middleware hooks — pre/post-dispatch hooks for auth checks, CORS, rate limiting, logging
- Template project /
cargo generate— scaffolding for new dioxus-cloudflare projects with wrangler config, shared/web/worker crates, and build scripts - Remove
dioxus-serverpatch requirement — upstream the wasm32cfg-gating to Dioxus core so users don't need[patch.crates-io]
Ideas / Exploration
- SSR inside Workers — requires single-threaded executor adaptation for
dioxus-server's render pipeline - Session middleware — built-in session management backed by KV or D1 with cookie-based session IDs
- WebSocket support — Durable Object + WebSocket upgrade for real-time Dioxus apps
- Service bindings — call other Workers from server functions via
cf::env().service() - AI bindings —
cf::env().ai()for Workers AI inference from server functions - Secrets —
cf::env().secret()for accessing encrypted environment variables - Wrangler plugin — automate the build pipeline (cargo build → wasm-bindgen → shim generation) without manual wrangler.toml
[build]commands
Optional Features
| Feature | Enables |
|---|---|
queue |
cf::queue() shorthand (activates worker/queue) |
Dependencies
= { = "=0.7.3", = ["fullstack"] }
= { = "0.4", = ["http"] }
= { = "0.8", = false }
= "1"