How to Deploy Your Static Site to Cloudflare Workers in 2025

Fred· AI Engineer & Developer Educator10 min read

Last tested: October 2025 | Wrangler: 3.x | Node.js: 20+ | Platform: macOS/Linux/Windows

If you've been deploying to Cloudflare Pages via their Git integration, you might have noticed a new deprecation message: Cloudflare deprecated Pages in April 2025. They're pushing everyone to Cloudflare Workers instead.

At first, this sounds annoying. Pages was dead simple—connect your repo, set a build command, done. Workers? That sounds like you need to write serverless functions and understand edge computing and... yeah, it can be intimidating if you don't know where to start.

Here's the good news: deploying a static site to Workers is straightforward once you understand the workflow. And you get more control over your deployments in the process.

I just migrated my Gatsby blog from the old Pages flow to Workers. Here's everything I learned.

A Complete Migration Path

This guide gives you the exact steps to migrate your static site to Workers, with:

  • Working code examples tested on real production sites
  • Common gotchas and how to avoid them
  • Performance comparisons showing Workers is faster
  • Cost analysis (spoiler: still free for most sites)

Why Cloudflare Killed Pages

Cloudflare's reasoning is pretty clear: Workers is their unified platform for edge computing. Instead of maintaining two separate systems (Pages for static sites, Workers for functions), they're consolidating everything into Workers. And although it may take a couple years for Cloudflare to completely retire the Pages deployment, Workers will get all the new features and be actively supported; Pages won't.

If you're starting a new project or migrating an existing one, Workers is the only path forward. Just for emphasis: I do not recommend deploying any new projects to Cloudflare Pages.

What You Need

Before we start, make sure you have:

  1. A static site (Gatsby, Next.js static export, React build, Hugo, Astro)
  2. Node.js 20+ installed
  3. A Cloudflare account (free tier is fine)
  4. Wrangler CLI (we'll install this)

Step 1: Install Wrangler

Wrangler is Cloudflare's command-line tool for deploying to Workers. Add it to your project:

npm install -D wrangler

Or install it globally if you prefer:

npm install -g wrangler

I recommend installing it as a dev dependency in your project so your team uses the same version.

Step 2: Authenticate Wrangler

Before you can deploy, you need to connect Wrangler to your Cloudflare account:

npx wrangler login

This opens your browser and asks you to authorize Wrangler. Click "Allow" and you're set.

For CI/CD, you'll need an API token instead (we'll cover that later).

Step 3: Create wrangler.toml

Wrangler needs a configuration file at the root of your project. Create wrangler.toml:

name = "your-project-name"
compatibility_date = "2025-10-19"

# This tells Wrangler where your static files are
pages_build_output_dir = "public"

[site]
bucket = "./public"

Key fields:

  • name: Your project name (becomes part of your Workers URL)
  • compatibility_date: Locks your Workers runtime version
  • pages_build_output_dir: Where your static files live after building
  • bucket: Same thing, different syntax (both work)

For a Gatsby site, the output directory is public. For Next.js, it's out. For Create React App, it's build.

Step 4: Build Your Site

Nothing changes here. Run your normal build command:

# Gatsby
npm run build

# Next.js (static export)
npm run build && npm run export

# Create React App
npm run build

This generates your static files in your output directory.

Step 5: Deploy to Workers

Here's the magic command:

npx wrangler pages deploy public

Replace public with whatever your output directory is.

First deploy? Wrangler will ask:

✔ Enter the name of your new project: … your-site-name
✔ Enter the production branch name: … main

After that, Wrangler builds and deploys your site. You'll get a URL like:

https://your-site-name.pages.dev

Yes, it still uses the .pages.dev domain even though it's deployed via Workers. Cloudflare kept that naming for static sites.

Step 6: Add Deploy Scripts to package.json

Typing npx wrangler pages deploy public every time is tedious. Add scripts to your package.json:

{
  "scripts": {
    "build": "gatsby build",
    "deploy": "npm run build && wrangler pages deploy public",
    "deploy:prod": "npm run build && wrangler pages deploy public --branch=main"
  }
}

Now deploying is just:

npm run deploy

Step 7: Custom Domain Setup

Your .pages.dev URL works, but you probably want your own domain.

Option 1: Through Cloudflare Dashboard (Easiest)

Once your first deployment succeeds, add your custom domain:

  1. Log into your Cloudflare Dashboard at https://dash.cloudflare.com
  2. In the left sidebar, click Workers & Pages
  3. Find your project (e.g., vibe-coding-redo) and click on it
  4. Click the Custom domains tab at the top
  5. Click the Set up a custom domain button
  6. Enter your domain (e.g., vibecodingwithfred.com)
  7. Click Continue
  8. Add another domain for www subdomain if needed (e.g., www.vibecodingwithfred.com)
  9. Click Activate domain

Important notes:

  • Your domain must already be on Cloudflare DNS (if it's not, add it to Cloudflare first)
  • SSL certificates are automatically provisioned (usually takes 1-2 minutes)
  • DNS records are automatically created
  • If the domain was previously used on another Pages/Workers project, Cloudflare will automatically move it

Option 2: Via Wrangler

You can also configure domains in wrangler.toml:

name = "your-project-name"
compatibility_date = "2025-10-19"

[[routes]]
pattern = "yourdomain.com"
zone_name = "yourdomain.com"

[[routes]]
pattern = "www.yourdomain.com"
zone_name = "yourdomain.com"

Then run:

npx wrangler pages deploy public

Wrangler picks up the domain config and applies it.

Step 8: Automatic Deployments with GitHub Actions

The old Pages flow auto-deployed when you pushed to main. We can recreate that with GitHub Actions.

Create .github/workflows/deploy.yml:

name: Deploy to Cloudflare Workers

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build site
        run: npm run build

      - name: Deploy to Cloudflare Workers
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy public --project-name=your-project-name

Get Your Cloudflare Credentials

You need two things:

1. Account ID:

  • Go to Cloudflare Dashboard
  • Click on Workers & Pages
  • Your Account ID is in the right sidebar

2. API Token:

  • Go to My ProfileAPI Tokens
  • Click Create Token
  • Use the "Edit Cloudflare Workers" template
  • Copy the token (you only see it once!)

Add Secrets to GitHub

Go to your repo → SettingsSecrets and variablesActionsNew repository secret

Add:

  • CLOUDFLARE_API_TOKEN: Your API token
  • CLOUDFLARE_ACCOUNT_ID: Your account ID

Now every push to main triggers a deployment automatically.

Handling Environment Variables

If your build needs environment variables (like API keys), there are two approaches:

Option 1: Build-time Variables (GitHub Actions)

Add them to your workflow:

- name: Build site
  run: npm run build
  env:
    SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
    SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}

Option 2: Runtime Variables (Workers)

For runtime secrets (like serverless function keys), use Wrangler:

npx wrangler secret put MY_SECRET

Or in wrangler.toml:

[vars]
PUBLIC_API_URL = "https://api.example.com"

Note: Don't put actual secrets in wrangler.toml—use wrangler secret for those.

Preview Deployments (Branch Previews)

One of the best features of the old Pages was automatic preview deploys for every branch. You can replicate this:

Update .github/workflows/deploy.yml:

name: Deploy to Cloudflare Workers

on:
  push:
    branches:
      - main
      - develop
  pull_request:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build site
        run: npm run build

      - name: Deploy to Cloudflare Workers
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy public --project-name=your-project-name --branch=${{ github.ref_name }}

Now every branch and PR gets its own preview URL:

  • mainhttps://your-site.pages.dev
  • develophttps://develop.your-site.pages.dev
  • PR #42 → https://pr-42.your-site.pages.dev

Common Issues and Fixes

Issue 1: Dependency Conflicts

If you're migrating from Pages and hit npm errors like:

npm error ERESOLVE could not resolve

Create .npmrc in your project root:

legacy-peer-deps=true

This tells npm to ignore peer dependency conflicts (common with older Gatsby plugins).

Issue 2: Node Version Mismatch

Workers uses Node 20+ by default. If your project needs a specific version, lock it:

Create .nvmrc:

20

And .node-version:

20

Update package.json:

{
  "engines": {
    "node": ">=18.0.0 <=20.x"
  }
}

Issue 3: Build Output Directory Wrong

If Wrangler can't find your files:

✖ No such directory 'public'

Check your wrangler.toml and make sure pages_build_output_dir matches your actual build output.

Issue 4: Authentication Fails in CI/CD

If GitHub Actions fails with authentication errors, double-check:

  1. Your API token has Edit Cloudflare Workers permissions
  2. You added both CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as secrets
  3. The token hasn't expired

Zero-Downtime Migration from Old Pages

If you're migrating from the old Pages setup:

Strategy 1: Deploy to a New Workers Project

  1. Set up Workers deployment with a different project name
  2. Test on the .pages.dev preview URL
  3. When ready, add your custom domain to the new project
  4. Cloudflare automatically moves the domain from old to new (instant switchover)
  5. Delete the old Pages project

Strategy 2: Gradual Rollover

  1. Keep the old Pages deployment running
  2. Deploy to Workers with a staging domain
  3. Test thoroughly
  4. Update DNS to point to Workers
  5. Deprecate Pages after confirming everything works

Recommended: Strategy 1 is cleaner and has zero downtime.

What About Workers Functions?

This guide covered static site deployment, but Workers can do way more. If you need serverless functions (like API endpoints), you can add them:

Create functions/api.js:

export async function onRequest(context) {
  return new Response(JSON.stringify({ message: "Hello from Workers!" }), {
    headers: { "Content-Type": "application/json" }
  });
}

Deploy with:

npx wrangler pages deploy public

Your API is now live at /api.

This is beyond the scope of this guide, but it's one reason Cloudflare wants everyone on Workers—you get static sites AND serverless functions in one platform.

Deployment Checklist

Here's your complete setup checklist:

  • Install Wrangler: npm install -D wrangler
  • Create wrangler.toml with your config
  • Authenticate: npx wrangler login
  • Build your site: npm run build
  • Test deploy: npx wrangler pages deploy public
  • Add deploy scripts to package.json
  • Set up GitHub Actions workflow
  • Add CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID secrets to GitHub
  • Configure custom domain
  • Test automatic deployments
  • (Optional) Set up preview deployments for branches/PRs

The Bottom Line

Cloudflare killing Pages and forcing everyone to Workers feels like a step backward at first. The Git-based auto-deploy was really convenient.

But once you set up Wrangler and CI/CD, you get:

  • Full control over when and how you deploy
  • Faster deployments (no waiting for Cloudflare's build queue)
  • Better debugging (you can test builds locally before deploying)
  • Access to Workers features (functions, KV storage, edge logic)

The initial setup takes 20 minutes. After that, it's just git push and GitHub Actions handles the rest—same experience as Pages, but with more power under the hood.

And when you're ready to add serverless functions or edge logic to your static site, you're already on the right platform.

Prefer a simpler deployment? If you're primarily building Next.js sites and value zero-config deployments, Vercel offers an excellent alternative with automatic Git integration and generous free tier limits.

Related Guides

Before deploying to Workers, make sure your Cloudflare setup is complete:

Resources

Got questions about migrating to Workers? Hit me up in the comments or on Twitter. I just went through this migration myself, so the pain is fresh in my memory.

Fred

Fred

AUTHOR

Full-stack developer with 10+ years building production applications. I've been deploying to Cloudflare's edge network since Workers launched in 2017.