Cloudflare Pages vs Workers: which should you choose?
By Flavio Copes
Compare Cloudflare Pages and Workers for static sites and full-stack apps, with request diagrams, pricing examples, and advice on when to migrate.
Cloudflare Pages and Cloudflare Workers can both host your website. Both can serve static files. Both can run server code.
So which one should you pick when you create a project?
For a new project, I would start with Workers. That includes a plain static blog, a React app, and an app with a database. Cloudflare now recommends Workers Static Assets for new projects and focuses new features there. Pages continues to work. Cloudflare’s guidance makes this explicit.
For an existing Pages site that works well, I would first ask what a migration would improve. This blog runs on Pages. Moving a working site takes time, and a change in the recommended starting point doesn’t create a problem in your current deployment.
The details matter because the old advice, “Pages for websites, Workers for APIs,” leaves out much of what Workers can do now.
What Pages and Workers do
Cloudflare Pages gives you a website deployment workflow. You connect a Git repository, specify a build command and output directory, and Cloudflare publishes the result. You get preview deployments and a production domain.
You can add Pages Functions for server-side routes. Those functions run on the Workers runtime. A Pages project can handle form submissions, read a database, and render dynamic responses.
Cloudflare Workers lets you deploy application code with handlers for events such as HTTP requests. With Workers Static Assets, the same deployment can also contain your HTML, CSS, browser JavaScript, and images. You can even deploy only the files, with no Worker script at all. The static assets documentation covers this deployment model.
Think about a small recipe website. It has a home page, recipe pages, a stylesheet, and a search interface that runs in the browser.
Either product can host it. If we later add a private recipe collection, both have ways to run the server code. The choice affects how we configure and extend the project.
| What you need | Pages | Workers |
|---|---|---|
| Serve generated HTML, CSS, JS, and images | Yes | Yes, using Static Assets |
| Add HTTP endpoints | Pages Functions | A Worker fetch handler or framework routes |
| Build after a Git push | Pages Git integration | Workers Builds |
| Preview changes before production | Yes | Yes |
| Read D1, KV, or R2 from server code | Supported bindings | Supported bindings |
| Run a scheduled handler | Use a separate Worker | Configure a Cron Trigger |
| Host a Durable Object implementation | Use a separate Worker and bind to it | Define it in a Worker |
Pages supports bindings to Cloudflare services, including Durable Objects hosted in another Worker. Having a database alone doesn’t force you to migrate.
A decision chart for your project
I would make the decision in this order. Start with whether you already have a working deployment.
For a new project with a specific dependency on Pages, such as an existing deployment integration your team must use, Pages can still be a deliberate choice. Check that dependency rather than assuming every tool works with either product.
For everyone starting without that constraint, Workers gives you a straightforward place to begin and room to add server features later.
Static sites on Workers don’t need server code
A static site is a collection of files generated before someone visits.
Astro can read Markdown and produce an HTML file for every recipe. The build might query a CMS or process thousands of records. Once the build finishes, Cloudflare only needs to serve its output.
That distinction is useful: a build can be complicated while serving the site stays simple.
For an existing static project that builds into dist, the core Workers configuration in wrangler.jsonc is:
{
"name": "recipe-site",
"compatibility_date": "2026-10-20",
"assets": {
"directory": "./dist",
"not_found_handling": "404-page"
}
}
Use the current date when creating your project. The compatibility date selects runtime behavior; it isn’t a publishing date.
There is no main entry because there is no server script. For a static site with a 404.html page, 404-page makes the missing-page behavior explicit.
After building the site, deploy it with Wrangler:
npm run build
npx wrangler deploy
This assumes the project already has its build script and Wrangler installed. If you’re starting from scratch, follow the Workers Static Assets guide for the setup.
Workers hosting doesn’t mean your HTML has to be generated on every visit. A request for an existing static asset can be served directly.
What happens when a request arrives?
Now imagine the recipe site also has an API at /api/recipes.
For a Worker with static assets and the default routing behavior, a matching file is served first. When a request doesn’t match an asset, Cloudflare invokes the Worker script.
The illustration follows a static request, an API request, and a route that needs authorization. Each scene shows where code runs.
With default asset routing, /style.css can go directly to the static file. /api/recipes reaches your code if no static asset matches it.
You can change the order. For example, an application might need to check a session before returning a private HTML file. Workers supports assets.run_worker_first, including patterns that select which paths run code first. See the Worker routing documentation.
This has a security consequence. An authorization check in your Worker only protects requests that reach that check. If you upload private content as a directly accessible asset, the default asset-first path can serve it without running your authorization code.
I would keep private recipe data behind an authenticated server endpoint. If I needed to gate files, I would deliberately configure their request path and test an unauthenticated request to the file itself. Hiding a link in the interface doesn’t protect the URL.
Pages has its own routing configuration. Pages Functions can use a functions/ directory, and _routes.json controls which requests invoke them. A framework or Pages tooling may generate this file. Check the generated output, especially when using middleware that matches many routes. The Pages routing guide explains invocation rules.
When comparing deployments, check the path of a real request. The folder names in your repository don’t tell you whether every image request runs code.
A browser app can still use static hosting
A React or Vue application might show a dashboard, change views, filter records, and keep local state. All of that can run in the browser.
If the build produces browser files, you can deploy them as static assets. An external API may supply the data. You don’t need a server-rendering setup just because the interface is interactive.
For a single-page application, direct navigation needs special handling. Someone opening /recipes/saved must receive the app’s HTML even if there is no file at that path.
Workers supports not_found_handling: 'single-page-application' for this. It differs from the 404-page setting above: an unknown navigation can load the app instead of showing a missing-page response. See the SPA routing guide.
If the same deployment also hosts an API, make sure API requests reach it. Test a direct browser navigation to a nested app route, an API request, and a missing API endpoint. A response containing your home page HTML is usually wrong for an API client, even if its status is 200.
When Workers makes a bigger difference
The advantages become clearer as the application does more than respond to page requests.
Suppose the recipe app sends a weekly meal plan. You need something that starts on a schedule. A Workers project can have a scheduled handler triggered by Cron Triggers.
Then you add image processing after an upload. A Queue can carry work to a consumer, so the upload request doesn’t have to wait for all the processing.
A shared cooking session may need one authoritative place to coordinate participants. That’s a use case for a Durable Object.
Workers has handlers for different event types. You can build around those event handlers as the requirements appear. You don’t have to add them to a static site in advance.
An existing Pages frontend can call a separate Worker that owns these features. That can be a good boundary if background processing has a different release cycle.
For a small new app, I would usually keep the frontend and HTTP server together. I would introduce another deployment when it solves an actual ownership, scaling, or maintenance problem.
Databases and authentication
Pages Functions can access D1, KV, and R2 through bindings. Workers can too.
So “my site needs a database” is too broad to settle this choice.
A mostly static Pages site can query D1 through a Function. A new Workers app can use the same database from its server code. In both cases, the browser sends a request to your server endpoint. Your server code uses the binding and returns the permitted result.
Authentication works through that server boundary too. You need to validate the session and check permission before returning private data. Picking a deployment product doesn’t implement those checks for you.
The rendering choice is separate. A public recipe can be generated during a build, while a saved-recipes page reads the current user’s records on demand. You can combine static and dynamic routes in one application.
Which should you use with Astro or another framework?
Start with what your framework produces.
For a static Astro site, the output is a folder of files. You can host that folder on either product. You don’t need the Cloudflare server adapter just to serve generated HTML.
For on-demand rendering, your framework needs a deployment adapter that targets the platform. Follow its current guide instead of copying an old Pages tutorial and changing the deploy command.
Cloudflare’s Astro deployment guide covers static output and server rendering on Workers. It also explains prerendering selected routes when the rest of the app renders on demand.
The same check applies to other frameworks. A static export and a full-stack framework build are different outputs. A full-stack build can depend on generated routing, bindings, and a server entry point.
If you’re learning these pieces, the free Cloudflare Workers course walks through the runtime and deployment model. The free Astro course explains the framework side.
Git deployments, previews, and debugging
Pages is convenient because a Git push can trigger a build and deployment. Workers also has this through Workers Builds.
You can connect a repository and configure its build and deploy commands. You can also build in your own CI and deploy with Wrangler. Choosing Workers doesn’t require building an entire deployment system yourself.
Both products offer ways to preview changes. Check the workflow you need: reviewing a branch’s UI is different from testing database migrations or a webhook against isolated data.
A preview URL alone doesn’t isolate your database. Before testing a destructive action, check the preview’s bindings and secrets. My Worker Previews guide goes into this distinction.
For server-heavy applications, debugging tools also matter. Workers offers persisted Workers Logs and other observability tools that Pages doesn’t expose in the same way. Pages does support real-time logs. Cloudflare keeps a feature comparison in its migration guide.
If a production bug happens overnight and disappears before you can reproduce it, retained logs can be a practical reason to change how you deploy. My Workers observability guide shows how to inspect those requests.
How the costs compare
For normal static asset delivery, both Pages and Workers offer free, unlimited static asset requests. On Workers, a static request doesn’t have to invoke your Worker script. Workers asset billing documents that distinction.
The following illustration uses a hypothetical traffic sample. The bars count requests that run your code; they don’t estimate a bill.
In the mixed example, 900 requests go directly to static files and 100 invoke code. If we change the configuration so that every request runs middleware first, all 1,000 invoke code, even if the final response is a file.
Those extra invocations might be justified. Authentication or a response transformation can require them. The useful question is which paths need that behavior.
Pricing and limits below were checked in October 2026:
| Usage | What to account for |
|---|---|
| Direct static asset delivery | Free, unlimited requests on both products |
| Workers and Pages Functions on Free | A shared allowance of 100,000 dynamic requests per day, with a 10 ms CPU limit per invocation |
| Workers Paid | Starts at $5 per account per month, with included request and CPU usage; additional usage is billed |
| D1, R2, and other attached services | Their own allowances and usage charges |
| Cloudflare-hosted builds | Separate build limits from requests serving the site |
Pages Functions count toward Workers usage. Putting the frontend on Pages doesn’t give your server endpoints an independent free compute allowance. See the Pages Functions pricing and Workers pricing pages before estimating a bill.
Be careful if you enable the separate Workers Caching feature: the pricing page describes request charges for cache-served requests, including static assets. The direct asset-delivery example above assumes you haven’t enabled that feature.
For builds, Pages Free currently allows 500 builds per month. Workers Builds Free includes 3,000 build minutes per month. These measure different things: one counts builds, the other counts their duration. A two-minute build and a fifteen-minute build consume the Workers allowance at different rates. Check the Pages limits and Workers Builds limits for your plan.
Free static requests don’t remove deployment limits, file-size limits, or server runtime limits. Check those against the files and jobs your project will actually produce.
Should you migrate an existing Pages site?
I would write down the reason first.
“We need a queue consumer and want one application deployment” is a reason. So is “our framework’s current adapter targets Workers.” A working static site with a reliable deployment pipeline may have little to gain immediately.
This site is an example of why I would judge the whole application. Its generated articles work well on Pages, and it has existing Functions and bindings. Moving it would involve checking those routes and integrations, beyond uploading the same HTML somewhere else.
For a plain static site, a migration can be small. Point a Workers project’s assets.directory at the build output, configure missing-page behavior, and test it before moving the domain.
For a project with Functions, use Cloudflare’s Pages-to-Workers migration guide. Pages’ file-based Functions need to be adapted or compiled; a Workers project doesn’t automatically treat your functions/ folder as routes. Framework-generated builds need their framework’s migration instructions.
I would check these paths before switching traffic:
- A generated page, a CSS file, and an image.
- A direct visit to a nested route and a missing URL.
- A redirect and a response that needs custom headers.
- A public API route and a private route while logged out.
- A form submission, including its error response.
- Preview bindings, production bindings, and required secrets.
Redirects and headers deserve particular care. A static _headers rule doesn’t automatically become a header on a response generated by your server code. Check the actual responses from both paths.
I would also keep an uploaded server bundle out of the public asset directory. The migration guide calls this out for Pages advanced-mode _worker.js builds: move the script or exclude it from assets.
The default pages.dev and workers.dev URLs differ. If anyone relies on the old deployment URL, handle that separately from the custom domain. Changing a custom domain doesn’t redirect every old preview link.
What I would choose
For a new blog, documentation site, or portfolio, I would deploy generated files on Workers Static Assets. I would keep it entirely static until a feature needed server code.
For a new app with accounts, saved data, or on-demand HTML, I would use Workers with the framework’s supported adapter. Static files would stay on the direct asset path where possible.
For this existing Pages blog, I would keep the current deployment until there was a concrete reason to move. If it only needed a scheduled job, I would compare adding a small Worker with migrating the whole site.
Before creating your project, look at its build output and name the requests that need server code. That gives you the configuration to start with, and a short list of routes to test after deploying.
Want me to talk about your product? You can sponsor this site.
Related posts about cloudflare: