Deployments
Cloudflare
Use the Cloudflare target when route handlers, loaders, middleware, metadata, or server-rendered pages should run on Cloudflare Workers runtime.
Build artifacts
mreact-router build --target=cloudflareThe build emits .mreact/cloudflare/worker.mjs, route modules, server route modules, and .mreact/client assets. Generated server route modules dispatch GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, and ALL exports with decoded params and context.env, so route handlers can use Worker bindings.
Cloudflare Pages advanced mode
Use Pages advanced mode when you want Cloudflare Pages to host the static assets and run the generated Worker as _worker.js. Build the Cloudflare target, package the generated worker and static assets, then deploy the packaged directory to a named Pages project.
mreact-router build --target=cloudflare
mreact-router package cloudflare-pages --from .mreact --out .mreact/pages
wrangler pages deploy .mreact/pages --project-name=mreact-app --branch=mainPages provides the ASSETS binding to _worker.js. Mreact serves only allowed manifest assets, route-linked CSS, generated client assets, and copied public files from that binding, which keeps traversal-style asset requests out of the static asset path.
Custom Pages worker
Pass --worker when the Pages deployment needs existing Cloudflare adapter options such as onResponse, onError, or logger. The package command bundles the TypeScript entry and provides mreact-router/generated-cloudflare, a package-time module whose factory preserves the generated manifests, route renderer, and static asset allowlist.
mreact-router package cloudflare-pages --from .mreact --out .mreact/pages --worker src/worker.tsimport { createDefaultCloudflarePagesHandler } from "mreact-router/generated-cloudflare";
export default createDefaultCloudflarePagesHandler({
onResponse(response, { request }) {
response.headers.append("vary", "cookie");
if (request.headers.has("cookie")) {
response.headers.set("cache-control", "private, no-store");
}
},
});The generated module exists while package cloudflare-pages bundles the custom entry; do not import it from ordinary application modules. The option changes production Pages packaging only. It does not make mreact-router dev or mreact-router start load the custom worker.
Route cache policies
Cloudflare responses honor export const revalidate = 0 as Cache-Control: no-store. A positive integer such as revalidate = 60 emits s-maxage=60, stale-while-revalidate. These static declarations work without Node compatibility. They set response headers for downstream caches; the adapter does not store rendered HTML in the Workers Cache API or implement background regeneration.
Loaders and the initial route render can also call cacheControl({ sMaxAge: 45, staleWhileRevalidate: 120 }) from @reckona/mreact-router. Runtime policies require nodejs_compat and request-local async storage; without that context the call fails rather than sharing policy state across visitors. Set the policy before returning the response, since streamed chunks cannot change headers after rendering has returned. A static revalidate declaration takes precedence over cacheControl(), matching the Node adapter. revalidatePath() is unavailable in Cloudflare workers; use the cache provider's invalidation mechanism for responses cached downstream.
The same native async storage requirement applies to requestState(). Enable nodejs_compat to isolate each request's state graph and dispose initializer-owned resources when the response ends. Without supported storage, accessing request state throws; ordinary routes that do not use it remain available.
The adapter keeps responses with credentials, Set-Cookie, CSP nonces, or request-dependent rendering out of shared caches by applying private, no-store when a policy would otherwise allow caching. Request-dependent middleware is covered too. Custom route module registries without generated request-input analysis are conservatively treated as request-dependent because components receive the full Request. Existing restrictive response directives are preserved, and revalidate = 0 strengthens weaker directives to no-store. A custom worker's onResponse hook runs after route policy application and can further restrict the headers.
Workers deployment
Use Workers when you want an ordinary Worker service with an explicit static assets binding. The generator writes wrangler.toml, and Wrangler also accepts a JSONC config when you prefer that format. Point main at the generated Worker and bind .mreact/client as ASSETS.
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "mreact-app",
"main": ".mreact/cloudflare/worker.mjs",
"compatibility_date": "2026-06-11",
"assets": {
"directory": ".mreact/client",
"binding": "ASSETS",
},
}Build, smoke-test locally, then deploy.
mreact-router build --target=cloudflare
wrangler dev
wrangler deployIf your build script also generates CSS or public assets, run that script before mreact-router build. The Hacker News example does this through pnpm --filter @reckona/example-hacker-news worker:check, which builds Tailwind CSS, emits the Worker, and imports the generated Worker for a smoke test.
Choose Workers when you manage a Worker service directly or need Worker-specific routing, service bindings, custom routes, or environment separation in wrangler.jsonc. Choose Pages advanced mode when you want Pages' project workflow and a deployable directory containing _worker.js plus static assets.
Environment bindings
Cloudflare bindings are not process.env. Read them from loader, middleware, and route handler context via context.env.
import type { RouteHandlerContext } from "@reckona/mreact-router";
interface Env {
DB: D1Database;
}
export async function GET(
_request: Request,
context: RouteHandlerContext & { env: Env },
): Promise<Response> {
const result = await context.env.DB.prepare("select 1").first();
return Response.json({ result });
}Production notes
- Use
clientSourceMaps: "hidden"and upload.mreact/source-maps/client/from CI when you need Sentry symbolication. - Configure
nodejs_compatso the adapter can use native request-local storage. Without it, mreact serializes renders to preserve query-client isolation and emits a one-time warning. - Keep CSP, cache headers, and host behavior explicit because Cloudflare can also add edge headers.
- Test route handlers, dynamic server-rendered pages, streaming pages, 404s, copied public files, generated route assets, and server action forms after deployment.
APP_URL=https://mreact-app.example.workers.dev
curl -fsS "$APP_URL/"
curl -fsS "$APP_URL/api/health"
curl -fsS -I "$APP_URL/robots.txt"
curl -sS -o /dev/null -w "%{http_code}\n" "$APP_URL/not-found-check"Replace /api/health and /robots.txt with routes or copied public files that your app actually provides. The not-found check should print 404.