p4ni.

Comparison

Cloudflare Pages vs Workers in 2026: Which One for a Static Site?

· 11 min read

On this page

For most of the 2020s, “host a static site on Cloudflare” had a one-word answer: Pages. Git integration, free bandwidth, preview deployments — it was the easy recommendation, and thousands of blog posts made it.

That answer is now out of date. Cloudflare recommends Workers for new projects, has said it is no longer investing in new features for Pages, and has spent the last two years closing the gap that made Pages attractive in the first place. When I set up this blog, Pages was still the reflex answer everywhere I looked; I went with Workers static assets instead, and nothing since has made me second-guess it.

This post is the comparison I wanted at the time: what each platform actually is in 2026, a feature-by-feature table, what migrating involves, and — because every honest comparison needs one — the cases where staying on Pages is perfectly reasonable.

The short version

Your situationUse
New static siteWorkers (static assets)
New site with some SSR routesWorkers + your framework’s adapter
Existing site on Pages, working finePages — migrate when convenient, not urgently
You need Cron Triggers, Queue consumers, gradual rollouts, or to define Durable ObjectsWorkers — Pages never got these

Is Pages deprecated?

No — and the distinction matters if you have a site running on it. Cloudflare has not announced an end-of-life date, has not stopped building or serving existing projects, and the docs still maintain Pages’ reference material. What it has said is that new feature work goes to Workers.

“Deprecated” would mean a clock is running. Nothing here is on a clock. The accurate word is frozen: Pages does today what it did two years ago, and will keep doing it, while everything new lands somewhere else. So if you came here worried about a migration deadline, there isn’t one — read the rest as a comparison, not an evacuation notice.

What happened to Pages

Pages existed because Workers, for years, couldn’t serve plain files. A Worker was a script; if you wanted to host a folder of HTML, you either wrote file-serving code and stuffed assets into KV, or you used the product built for it. Pages was that product: git-connected builds, a CDN for your output directory, and later Pages Functions for the dynamic bits.

Then Workers learned to do the one thing it couldn’t. Static assets let a Worker ship a directory of files that Cloudflare serves directly from its CDN — no Worker code required, and requests for those files are free and unmetered on every plan, including Free. The moment that landed, Pages stopped being the only way to host files and became a second, feature-frozen way.

Cloudflare has said it plainly — new projects should start on Workers, and feature investment goes there — and the docs back that up with an official migration guide plus a compatibility matrix for everything Pages did. The direction of travel is one platform, not two.

What each one actually is

Pages is a hosting product. You connect a git repository (or upload a folder), Cloudflare builds it, and the output is served from the CDN. Server-side code goes in Pages Functions — files in a functions/ directory that Cloudflare compiles into a Worker behind the scenes.

Workers with static assets inverts the framing: everything is a Worker, and a Worker can now carry a directory of files. For a fully static site the “Worker” is nothing but configuration — there’s no script, no cold start, no invocation cost. This blog’s deploy config is essentially this file; that, plus a custom-domain block, is the whole thing:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-site",
  "compatibility_date": "2026-07-27",
  "assets": {
    "directory": "./dist",
    "not_found_handling": "404-page"
  }
}

If you later need server-side routes, you add a main script and it becomes an ordinary Worker that happens to also serve files — with the full platform attached: D1, KV, R2, Queues, Cron Triggers, Durable Objects, the lot. That upgrade path, more than any single feature, is the argument for starting on Workers.

Feature comparison

PagesWorkers (static assets)
Static file servingFree, unmeteredFree, unmetered
File limits20,000 on Free / 100,000 on paid, 25 MiB each20,000 on Free / 100,000 on paid, 25 MiB each
Git-connected buildsYes (Pages CI, 500 builds/month on Free)Yes (Workers Builds, metered in build minutes)
Preview deploymentsPer-commit preview URLsPer-version preview URLs
Server-side codePages Functions (file-based routing)A regular Worker — no translation layer
Cron TriggersNoYes
Queue consumersNoYes
Durable ObjectsBind to existing ones onlyDefine and bind
Observability (Workers Logs, Logpush, Tail Workers)NoYes
Source maps in stack tracesNoYes
Email Workers, Rate Limiting, Image ResizingNoYes
Gradual deploymentsNoYes — shift traffic between versions
RollbacksYesYes
Custom branch aliasesYesNot yet
Custom domain on DNS hosted elsewhereYes (subdomains)No
Web Analytics beacon injected into your HTMLYes, per projectNo
New platform featuresNo longer shippingWhere everything ships first

Four rows deserve a comment.

Pages Functions vs a real Worker. Pages Functions always were Workers under the hood, but the translation layer leaked: some bindings arrived late or never, debugging happened one step removed from what actually ran, and framework adapters had to special-case the platform. On Workers there is no layer — what you write is what runs. Framework tooling has followed; adapters and C3 templates now target Workers first.

Gradual deployments. Pages deploys were all-or-nothing. Workers can split traffic between two versions by percentage, which turns “deploy and pray” into “deploy to 5% and watch”. For a static blog this is admittedly a luxury; for anything with server code it’s the difference between an incident and a non-event.

The beacon row. Pages injects beacon.min.js into your HTML at serve time when a project has Web Analytics enabled; Workers static asset responses never got one on my account. I measured eight hosts across two zones to pin that down, and it isn’t documented on either side. If you’re moving a site with a strict CSP, it’s one fewer third-party script to allow — though the Bot Fight Mode injection lands on both.

Observability. This is the row I’d weigh heaviest if you run any server code, and it’s the one people discover last. Workers Logs, Logpush, and Tail Workers are all Workers-only; on Pages Functions your debugging story is thinner, and source maps don’t apply, so a production stack trace points at bundled output rather than your source.

What it costs

Short answer for a static site: nothing, on either platform, and the pricing is not a tiebreaker. Requests to static assets are free and unlimited on both — on the Free plan too. A blog served entirely from files never touches a meter, whichever product ships it.

Costs start when server code runs, and at that point both platforms bill identically, because Pages Functions requests are billed as Workers requests:

Workers FreeWorkers Paid
Price$0From $5/month
Requests100,000/day (resets midnight UTC)10 million/month included, then $0.30/million
CPU time10 ms per invocation30 million CPU-ms/month included, then $0.02/million
Max CPU per invocation10 ms30 seconds by default, up to 5 minutes

The one place the platforms genuinely differ on cost is builds. Pages gives the Free plan a flat 500 builds a month, 1 at a time, with a 20-minute timeout — simple to reason about. Workers Builds meters build minutes instead, which is fine for a blog but worth checking against a busy repo before you commit.

That difference has an easy escape hatch, and it’s what this site does: build in GitHub Actions and deploy with wrangler deploy. The build never runs on Cloudflare’s side of any meter, and the choice between Pages and Workers stops touching your bill at all.

Where Pages is still fine — or better

The honest list is short, but it exists.

  • A working production site is a reason. Migration is real work with real cutover risk, and the payoff for a purely static site is mostly future-proofing. Cloudflare isn’t pushing a deadline either — nothing about how Pages serves your site changes while you wait.
  • Onboarding polish. Pages’ connect-a-repo flow has a reputation as one of the smoothest in the industry: pick a repo, pick a framework preset, done — no config file in the repo at all. Workers Builds has closed most of that gap by all accounts, but Workers does expect a wrangler.jsonc checked in.
  • Externally-managed DNS. A Workers custom domain requires the zone’s nameservers to be on Cloudflare. Pages could serve a custom domain via a CNAME from DNS hosted elsewhere — subdomains only, though; an apex domain needs Cloudflare nameservers even on Pages. If moving your nameservers is off the table, that alone decides it.
  • Build allowances at zero cost. Pages’ flat 500 builds a month is simpler to reason about than metered build minutes, as covered above — though building elsewhere makes the point moot.
  • Custom branch aliases. Pages gives every branch a stable preview hostname you can hand to a reviewer. Workers’ per-version preview URLs change each deploy; Cloudflare lists branch aliases as coming, but it isn’t there yet.

Apart from external DNS and branch aliases, you won’t find a capability on this list that Pages has and Workers lacks. The asymmetry runs almost entirely one direction, and it keeps widening.

What migrating actually involves

Less than you’d think, because both platforms serve the same build output. Your framework config, your HTML, your _headers and _redirects files — all unchanged. What moves is the deployment configuration:

# Before — wrangler.toml on Pages
name = "my-site"
pages_build_output_dir = "./dist"
// After — wrangler.jsonc on Workers
{
  "name": "my-site",
  "compatibility_date": "2026-07-27",
  "assets": { "directory": "./dist", "not_found_handling": "404-page" }
}

The checklist beyond that, straight from Cloudflare’s migration guide:

  1. 404 handling is explicit now. Pages auto-detected your 404 page; Workers wants not_found_handling: "404-page" stated.
  2. Environment variables don’t come along. Redeclare build-time variables where your build runs, and runtime secrets with wrangler secret put.
  3. Custom domains need Cloudflare nameservers, as above. Verify the Worker on its workers.dev URL first, then move the hostname — treat it as a cutover, not a parallel run.
  4. Local dev port changes. wrangler dev serves on 8787; wrangler pages dev used 8788. Update anything that hardcoded it.
  5. If you use Pages Functions, they don’t move as-is. A functions/ directory is compiled with wrangler pages functions build, and the output becomes your Worker’s main entry. Anything that must run before assets are served — auth checks, logging — also needs run_worker_first: true, since a Worker with assets serves files without invoking your code by default.

For the full Workers setup from zero — wrangler config, custom domain, the trailing-slash gotcha — I’ve written a step-by-step deploy guide using this blog as the example.

What you unlock after moving

The subtle benefit of being “just a Worker” is that every platform feature is one config block away, rather than on the far side of a product boundary.

Response headers are the first thing most sites reach for — a _headers file works as-is; that’s how the Content-Security-Policy on this site is handled, script hashes and all, with zero Worker code. Its sibling _redirects works the same way — this site’s 301s live in a plain-text file, no Worker code either. And when a site does outgrow that (per-route logic, nonces), the same deployment can grow a few lines of Worker code without moving anywhere. Small API endpoints are the next step: the GA4 stats endpoint that reports this site’s traffic is a tiny Worker on the same account, deployed with the same CLI.

And at the far end of the scale, the hybrid model: static pages served free from assets, with a server runtime only where the site actually needs one. My directory theme Almanac runs that way — static browsing pages, D1-backed search and submissions on the Worker side. I’ve written up how that stack goes together if you’re weighing something similar — and which side of that line your content belongs on (git, a hosted CMS, or D1) is a choice I’ve mapped separately. None of it required leaving the platform the static site started on — which is precisely the point of starting on Workers.

The bottom line

Pages isn’t bad; it’s finished. It still does what it always did, and an existing site has no emergency. But every argument that made Pages the default — free static hosting, git deploys, preview URLs — now applies to Workers equally, and everything Pages never got (Cron, Queues, Durable Objects, gradual rollouts, whatever ships next) is Workers-only.

New site: start on Workers and skip the migration entirely. Existing Pages site: move it the next time you’re touching the deploy setup anyway. The config diff is a dozen lines, and you end up on the platform where Cloudflare is actually building.