Deploying to Cloudflare Workers
Deploy to Cloudflare Workers when your site needs a server at request time: SSR, API routes, redirects, or the Tina visual-editing route. Every push to your production branch rebuilds and redeploys.
Astro
Push your project to GitHub, then deploy it with Cloudflare's git-based builds or straight from your machine.
Configure for Cloudflare
- Add the Cloudflare adapter with
npx astro add cloudflare. It installs@astrojs/cloudflareand setsadapter: cloudflare()inastro.config.mjs. - Add a root
wrangler.jsoncthat turns onnodejs_compat, which the visual-editing route needs fornode:async_hooks:
{"name": "your-worker-name","compatibility_date": "2026-06-01","compatibility_flags": ["nodejs_compat"]}
Using the tina-astro-starter? Both are already done. Itsastro.config.mjspicks the adapter from the host (Cloudflare's builds setWORKERS_CI, which selects@astrojs/cloudflareamong the supported adapters), and thewrangler.jsoncships in the repo, so you never runastro addor edit the config.
Deploy with git-based builds
In the Cloudflare dashboard, go to Workers & Pages | Create | Workers | Import a repository, connect GitHub, and pick your repo. Set:
- Build command:
pnpm run build - Deploy command:
npx wrangler deploy
There's no build-output field to set. A Worker gets its static assets from the adapter's generated config, not a dashboard directory, and Cloudflare authenticates the deploy for you.
Deploy from the Wrangler CLI
Put your TinaCloud credentials in a local .env, then build and deploy:
pnpm run buildnpx wrangler deploy
Using the starter? On your own machine no CI variable is set, so the build falls back to the Node adapter. Force Cloudflare:DEPLOY_ADAPTER=cloudflare pnpm run build.
Environment variables
tinacms build needs your TinaCloud credentials at build time. Add them wherever you build: for git-based builds, under the Worker's Settings | Build | Variables and Secrets; for a CLI deploy, in your local .env. Grab both credentials from your TinaCloud project.
PUBLIC_TINA_CLIENT_IDis your client ID. Astro uses thePUBLIC_prefix, not Next.js'sNEXT_PUBLIC_.TINA_TOKENis a content token.SITE_URLis your production URL, e.g.https://your-worker.your-account.workers.dev. Workers injects no deploy URL, so without it your sitemap, RSS, and OpenGraph tags fall back tolocalhost.
These are build-time only. tinacms build bakes the client ID and token into the generated client, so there's nothing to add again as a runtime variable.
Editing branch
TinaCMS reads and writes content on the branch resolved in tina/config.ts, which comes from the host's git environment variable. On Cloudflare that's WORKERS_CI_BRANCH (Workers Builds) or CF_PAGES_BRANCH (Pages). Add them to the chain:
const branch =process.env.GITHUB_BRANCH ||process.env.VERCEL_GIT_COMMIT_REF ||process.env.WORKERS_CI_BRANCH ||process.env.CF_PAGES_BRANCH ||process.env.HEAD ||"main";
Without them the branch falls back to main, so a preview or non-main deploy would edit main while the site builds from your branch.
Using the starter? Itsconfig.tsalready reads both.
Pin the SESSION KV namespace
The @astrojs/cloudflare adapter adds a SESSION KV binding (Astro's session store) even when your site doesn't use sessions. Your first deploy creates the namespace automatically, but Cloudflare doesn't write its ID back to your repo, so the next git-based deploy tries to create it again and fails:
✘ a namespace with this account ID and title already exists [code: 10014]
Editor saves trigger a redeploy, so you would hit this on your second save. Pin the namespace ID in your root wrangler.jsonc so every deploy reuses it. Create one with:
npx wrangler kv namespace create SESSION
or copy the ID your first deploy already made from Workers & Pages | KV. Then add it to the config:
{"name": "your-worker-name","compatibility_date": "2026-06-01","compatibility_flags": ["nodejs_compat"],"kv_namespaces": [{ "binding": "SESSION", "id": "<your-namespace-id>" }]}
Check visual editing
After a deploy, open /admin on your Worker's URL, edit a post, and save. The save commits to GitHub, which triggers a redeploy. Then open a content page and click an editable region: it should highlight and open its form in the sidebar. That exercises the on-demand /tina-island/* route running inside the Worker. If it errors, confirm nodejs_compat is set.
Next.js
Use the OpenNext Cloudflare adapter to generate open-next.config.ts and wrangler.jsonc. In the dashboard (Workers & Pages | Create | Workers | Import a repository), set:
- Build command:
npx opennextjs-cloudflare build - Deploy command:
npx opennextjs-cloudflare deploy
Add your build-time credentials under Settings | Build | Variables and Secrets:
NEXT_PUBLIC_TINA_CLIENT_IDis your client ID.TINA_TOKENis a content token.
If your OpenNext SSR reads these at request time, add them under Settings | Variables and Secrets too.
Finish TinaCloud setup
Your first deploy gives you a domain like https://<worker-name>.<account-name>.workers.dev. Set it (or your custom domain) as the Site URL in your TinaCloud project so the editor loads against the right origin.
Troubleshooting
"JavaScript heap out of memory" during the build. TinaCloud indexing can exceed Node's default heap. Set a build-time NODE_OPTIONS variable to --max-old-space-size=4096.