Tevpro insights

Deploying Sanity Studio to Cloudflare Workers with GitHub Actions

How Tevpro deploys a self-hosted Sanity Studio to a dedicated Cloudflare Worker with GitHub Actions, Wrangler, CI gates, and the exact commit CI tested.

SanityNext.jsCloudflareInfrastructure
GitHub Sanity Cloudflare

If your team wants to host Sanity Studio without adding its editor bundle to the public website, a dedicated Cloudflare Worker is a practical boundary. Tevpro uses that pattern: GitHub Actions waits for CI on main, checks out the commit CI tested, builds the Studio assets, and deploys them with Wrangler. Sanity remains the Content Lake and API layer.

The deployment split

This guide is for teams deciding whether to deploy Sanity Studio beside a public Next.js application or as a separate authoring surface. Tevpro keeps the Studio and public application as separate Workers so each can deploy independently while using the same Sanity project and dataset.

Sanity Studio deployment flow
Mermaid

Cloudflare hosts the Studio shell and its static assets. Sanity remains the content platform behind it: the Studio connects to the Sanity project, dataset, Content Lake, and APIs. Sanity documents the Studio configuration that connects the app to a project and dataset here.

Use narrow workflow triggers

CI runs when a change reaches main. The Studio deployment starts only after that CI run succeeds and checks out the exact commit CI tested. This avoids a deployment racing ahead of validation or deploying a newer commit that CI did not evaluate.

deploy-studio-worker.yml
yaml
name: Deploy Sanity Studio Worker
 
on:
  workflow_dispatch:
  workflow_run:
    workflows: [CI]
    types: [completed]
    branches: [main]
 
concurrency:
  group: sanity-studio-production-${{ github.ref }}
  cancel-in-progress: true
 
env:
  NODE_VERSION: "22"
  DEPLOY_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
  CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID || secrets.CLOUDFLARE_ACCOUNT_ID }}
  SANITY_STUDIO_PROJECT_ID: ${{ vars.SANITY_STUDIO_PROJECT_ID }}
  SANITY_STUDIO_DATASET: ${{ vars.SANITY_STUDIO_DATASET }}
  SANITY_STUDIO_PREVIEW_URL: ${{ vars.SANITY_STUDIO_PREVIEW_URL }}
 
jobs:
  deploy:
    if: >-
      github.event_name == 'workflow_dispatch' ||
      (github.event.workflow_run.conclusion == 'success' &&
      github.event.workflow_run.event == 'push' &&
      github.event.workflow_run.head_branch == 'main')
    runs-on: [self-hosted, Linux, X64, tevpro]
    steps:
      - uses: actions/checkout@v6
        with: {ref: ${{ env.DEPLOY_SHA }}}
      - uses: nubjs/setup-nub@v0
        with: {node-version: ${{ env.NODE_VERSION }}, cache: false}
      - run: npm ci --include=optional --prefer-offline --no-audit --fund=false
      - run: npm run build --workspace=studio
      - working-directory: studio
        run: nubx wrangler whoami
      - working-directory: studio
        run: nubx wrangler deploy --message "Studio @ ${{ env.DEPLOY_SHA }}"

This is a sanitized production pattern, not a universal template. The key detail is DEPLOY_SHA: the deploy job checks out the exact commit CI tested, rather than whatever commit has reached main by the time the job starts. Match runner labels, package-manager commands, and environment configuration to your own deployment model.

Use CI credentials, not interactive login

Our workflow passes a Cloudflare API token and account ID through GitHub environment configuration, then checks authentication before deployment. Cloudflare recommends an API token and account ID for non-interactive CI jobs. The token belongs in GitHub secrets. The account ID can be an environment variable or secret, depending on the team’s access model.

Protect the Studio with Cloudflare Access

We protect the self-hosted Studio with Cloudflare Access before it reaches Sanity. Authorized staff must complete the organization’s edge identity check, then Sanity applies its own project and dataset authorization.

The access policy and identity-provider configuration are operational controls, not part of the public implementation recipe. We keep those details in internal runbooks. The deployment workflow receives only the credentials needed to publish the Worker. It does not carry end-user access credentials.

The Studio access path

GitHub Actions builds and deploys the isolated Studio Worker.

Cloudflare applies the Access policy to the self-hosted Studio origin.

The editor completes the organization’s Access policy before the Studio application loads.

After that edge check, Sanity authenticates the editor and authorizes access to the configured project and dataset.

The self-hosted Studio origin is allowed to communicate with Sanity using credentialed CORS. We keep the exact allowed-origin configuration in internal operations documentation.

The public takeaway is straightforward: protect the authoring interface at the edge, keep deployment credentials separate from editor identity, and let Sanity enforce content-level authorization. Exact Access policy conditions belong in internal runbooks.

Treat Worker bindings as deployment configuration

The Studio Worker configuration is deliberately small: Wrangler runs a worker entry point and serves the built dist directory through an ASSETS binding with SPA fallback. The Studio gets its project ID, dataset, and preview URL from the deployment environment. It does not need the public site’s R2 cache, redirect KV, or application secrets.

studio/wrangler.jsonc
json
{
  "$schema": "../node_modules/wrangler/config-schema.json",
  "name": "tevpro-sanity-studio",
  "main": "worker.ts",
  "compatibility_date": "2026-08-08",
  "workers_dev": true,
  "observability": {"enabled": true},
  "assets": {
    "directory": "dist",
    "binding": "ASSETS",
    "not_found_handling": "single-page-application"
  }
}

Put release gates before deployment

The deployment starts only after the CI workflow succeeds for a push to main. Before Wrangler deploys, the job validates Cloudflare and Studio configuration, installs a clean dependency tree, builds the Studio static assets, and runs wrangler whoami. The pull request workflow separately type-checks and builds the Studio, then runs wrangler deploy --dry-run.

The public Next.js app has its own Cloudflare Worker and deployment workflow. The Studio workflow is isolated so a content-model or Studio change can ship without rebuilding the visitor-facing application. That separation also keeps deployment failures legible: an editor app failure is not disguised as a site release failure.

What to verify before calling it production-ready

  • The GitHub environment contains the required Cloudflare and Sanity values, and the API token has only the Worker permissions the workflow needs.
  • The workflow builds the exact frontend package that owns the Worker, including the OpenNext output.

The Studio Worker has the expected ASSETS binding, SPA fallback, and environment values for its Sanity project, dataset, preview URL, and SEOFields license.

The workflow checks out the CI-tested commit, and a manual dispatch remains available for a deliberate recovery deployment.

A post-deploy check confirms the Studio origin loads, editors can authenticate, and Presentation mode reaches the intended public site environment.

The takeaway

Tevpro uses two Cloudflare Worker deployments under one repository: the public Next.js app and an isolated Sanity Studio Worker. GitHub Actions gates the Studio release on CI and deploys the tested commit with Wrangler. The approach gives teams a clear operational boundary between the public site and the authoring application.

Sources

Why work with us

Why Tevpro?

Whether you’re a startup with a bold product idea or an established company seeking a stronger delivery partner, Tevpro delivers results. Our expert consultants specialize in building secure, scalable applications that simplify operations and drive real ROI.

FAQ

Questions about the deployment boundary

The decisions that keep the deployment understandable and recoverable.

Yes. Tevpro builds Sanity Studio as a static application and deploys it to a dedicated Cloudflare Worker. Sanity continues to provide the Content Lake and APIs, while Cloudflare serves the authoring application.

Deployment review

Need a deployment path your team can operate?

Share the application, CMS, hosting constraints, and release process. We will help map a practical delivery path.

We will use this only to follow up on your request.