Migrate from Pages
You can deploy full-stack applications, including front-end static assets and back-end APIs, as well as server-side rendered pages (SSR), with Cloudflare Workers.
Like Pages, requests for static assets on Workers are free, and Pages Functions invocations are charged at the same rate as Workers, so you can expect a similar cost structure.
Unlike Pages, Workers has a distinctly broader set of features available to it, (including Durable Objects, Cron Triggers, and more comprehensive Observability). A complete list can be found at the bottom of this page. Workers will receive the focus of Cloudflare's development efforts going forwards, so we therefore are recommending using Cloudflare Workers over Cloudflare Pages for any new projects ↗.
Migrating from Cloudflare Pages to Cloudflare Workers is often a straightforward process. The following are some of the most common steps you will need to take to migrate your project.
If your Pages project uses a popular framework, most frameworks already have adaptors available for Cloudflare Workers. Switch out any Pages-specific adaptors for the Workers equivalent and follow any guidance that they provide.
If your project doesn't already have one, create a Wrangler configuration file (either wrangler.jsonc
, wrangler.json
or wrangler.toml
) in the root of your project. The two mandatory fields are:
-
Set this to the name of the Worker you wish to deploy to. This can be the same as your existing Pages project name, so long as it conforms to Workers' name restrictions (e.g. max length).
-
If you were already using Pages Functions, set this to the same date configured there. Otherwise, set it to the current date.
Where you previously would configure a "build output directory" for Pages (in either a Wrangler configuration file or in the Cloudflare dashboard), you must now set the assets.directory
value for a Worker project.
Before, with Cloudflare Pages:
{ "name": "my-pages-project", "pages_build_output_dir": "./dist/client/"}
name = "my-pages-project"pages_build_output_dir = "./dist/client/"
Now, with Cloudflare Workers:
{ "name": "my-worker", "compatibility_date": "2025-04-01", "assets": { "directory": "./dist/client/" }}
name = "my-worker"compatibility_date = "2025-04-01"
[assets]directory = "./dist/client/"
Pages would automatically attempt to determine the type of project you deployed. It would look for 404.html
and index.html
files as signals for whether the project was likely a Single Page Application (SPA) or if it should serve custom 404 pages.
In Workers, to prevent accidental misconfiguration, this behavior is explicit and must be set up manually.
For a Single Page Application (SPA):
{ "name": "my-worker", "compatibility_date": "2025-04-01", "assets": { "directory": "./dist/client/", "not_found_handling": "single-page-application" }}
name = "my-worker"compatibility_date = "2025-04-01"
[assets]directory = "./dist/client/"not_found_handling = "single-page-application"
For custom 404 pages:
{ "name": "my-worker", "compatibility_date": "2025-04-01", "assets": { "directory": "./dist/client/", "not_found_handling": "404-page" }}
name = "my-worker"compatibility_date = "2025-04-01"
[assets]directory = "./dist/client/"not_found_handling = "404-page"
Pages would automatically exclude some files and folders from being uploaded as static assets such as node_modules
, .DS_Store
, and .git
. If you wish to also avoid uploading these files to Workers, you can create an .assetsignore
file in your project's static asset directory.
**/node_modules**/.DS_Store**/.git
If you use a full-stack framework powered by Pages Functions, ensure you have updated your framework to target Workers instead of Pages.
If you use Pages Functions with an "advanced mode" _worker.js
file, you must first ensure this script doesn't get uploaded as a static asset. Either move _worker.js
out of the static asset directory (recommended), or create an .assetsignore
file in the static asset directory and include _worker.js
within it.
_worker.js
Then, update your configuration file's main
field to point to the location of this Worker script:
{ "name": "my-worker", "compatibility_date": "2025-04-01", "main": "./dist/client/_worker.js", // or some other location if you moved the script out of the static asset directory "assets": { "directory": "./dist/client/" }}
name = "my-worker"compatibility_date = "2025-04-01"main = "./dist/client/_worker.js"
[assets]directory = "./dist/client/"
If you use Pages Functions with a folder of functions/
, you must first compile these functions into a single Worker script with the wrangler pages functions build
command.
npx wrangler pages functions build --outdir=./dist/worker/
pnpm wrangler pages functions build --outdir=./dist/worker/
yarn wrangler pages functions build --outdir=./dist/worker/
Although this command will remain available to you to run at any time, we do recommend considering using another framework if you wish to continue to use file-based routing. HonoX ↗ is one popular option.
Once the Worker script has been compiled, you can update your configuration file's main
field to point to the location it was built to:
{ "name": "my-worker", "compatibility_date": "2025-04-01", "main": "./dist/worker/index.js", "assets": { "directory": "./dist/client/" }}
name = "my-worker"compatibility_date = "2025-04-01"main = "./dist/worker/index.js"
[assets]directory = "./dist/client/"
If you authored a _routes.json
file in your Pages project, or used middleware in Pages Functions, you must pay close attention to the configuration of your Worker script. Pages would default to serving your Pages Functions ahead of static assets and _routes.json
and Pages Functions middleware allowed you to customize this behavior.
Workers, on the other hand, will default to serving static assets ahead of your Worker script, unless you have configured assets.run_worker_first
. This option is required if you are, for example, performing any authentication checks or logging requests before serving static assets.
{ "name": "my-worker", "compatibility_date": "2025-04-01", "main": "./dist/worker/index.js", "assets": { "directory": "./dist/client/", "run_worker_first": true }}
name = "my-worker"compatibility_date = "2025-04-01"main = "./dist/worker/index.js"
[assets]directory = "./dist/client/"run_worker_first = true
If you wish to, you can start a new Worker script from scratch and take advantage of all of Wrangler's and the latest runtime features (e.g. WorkerEntrypoint
s, TypeScript support, bundling, etc.):
import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint { async fetch(request) { return new Response("Hello, world!"); }}
import { WorkerEntrypoint } from "cloudflare:workers";
export default class extends WorkerEntrypoint { async fetch(request: Request) { return new Response("Hello, world!"); }}
{ "name": "my-worker", "compatibility_date": "2025-04-01", "main": "./worker/index.ts", "assets": { "directory": "./dist/client/" }}
name = "my-worker"compatibility_date = "2025-04-01"main = "./worker/index.ts"
[assets]directory = "./dist/client/"
Pages automatically provided an ASSETS
binding to access static assets from Pages Functions. In Workers, the name of this binding is customizable and it must be manually configured:
{ "name": "my-worker", "compatibility_date": "2025-04-01", "main": "./worker/index.ts", "assets": { "directory": "./dist/client/", "binding": "ASSETS" }}
name = "my-worker"compatibility_date = "2025-04-01"main = "./worker/index.ts"
[assets]directory = "./dist/client/"binding = "ASSETS"
If you had customized placement, or set a compatibility date or any compatibility flags in your Pages project, you can define the same in your Wrangler configuration file:
{ "name": "my-worker", "compatibility_date": "2025-04-01", "compatibility_flags": ["nodejs_compat"], "main": "./worker/index.ts", "placement": { "mode": "smart" }, "assets": { "directory": "./dist/client/", "binding": "ASSETS" }}
name = "my-worker"compatibility_date = "2025-04-01"compatibility_flags = [ "nodejs_compat" ]main = "./worker/index.ts"
[placement]mode = "smart"
[assets]directory = "./dist/client/"binding = "ASSETS"
Variables and bindings can be set in your Wrangler configuration file and are made available in your Worker's environment (env
). Secrets can uploaded with Wrangler or defined in the Cloudflare dashboard for production and .dev.vars
for local development.
If you are using Workers Builds, ensure you also configure any variables relevant to the build environment there. Unlike Pages, Workers does not share the same set of runtime and build-time variables.
Where previously you used wrangler pages dev
and wrangler pages deploy
, now instead use wrangler dev
and wrangler deploy
. Additionally, if you are using a Vite-powered framework, our new Vite plugin may be able offer you an even simpler development experience.
If you are using Pages' built-in CI/CD system, you can swap this for Workers Builds by first connecting your repository to Workers Builds and then disabling automatic deployments on your Pages project.
Pages automatically creates a preview environment for each project, and can be indepenedently configured.
To get a similar experience in Workers, you must:
-
Ensure preview URLs are enabled (they are on by default).
{"name": "my-worker","compatibility_date": "2025-04-01","main": "./worker/index.ts","assets": {"directory": "./dist/client/"},"preview_urls": true}name = "my-worker"compatibility_date = "2025-04-01"main = "./worker/index.ts"preview_urls = true[assets]directory = "./dist/client/" -
Enable non-production branch builds in Workers Builds.
Optionally, you can also protect these preview URLs with Cloudflare Access.
_headers
and _redirects
files are supported natively in Workers with static assets. Ensure that, just like for Pages, these files are included in the static asset directory of your project.
Where previously you were offered a pages.dev
subdomain for your Pages project, you can now configure a personalized workers.dev
subdomain for all of your Worker projects. You can configure this subdomain in the Cloudflare dashboard, and opt-in to using it with the workers_dev
option in your configuration file.
{ "name": "my-worker", "compatibility_date": "2025-04-01", "main": "./worker/index.ts", "workers_dev": true}
name = "my-worker"compatibility_date = "2025-04-01"main = "./worker/index.ts"workers_dev = true
If your domain's nameservers are managed by Cloudflare, you can, like Pages, configure a custom domain for your Worker. Additionally, you can also configure a route if you only wish to some subset of paths to be served by your Worker.
Once you have validated the behavior of Worker, and are satisfied with the development workflows, and have migrated all of your production traffic, you can delete your Pages project in the Cloudflare dashboard or with Wrangler:
npx wrangler pages project delete
pnpm wrangler pages project delete
yarn wrangler pages project delete
This compatibility matrix compares the features of Workers and Pages. Unless otherwise stated below, what works in Pages works in Workers, and what works in Workers works in Pages. Think something is missing from this list? Open a pull request ↗ or create a GitHub issue ↗.
Legend
✅: Supported
⏳: Coming soon
🟡: Unsupported, workaround available
❌: Unsupported
-
Middleware can be configured via the
run_worker_first
option, but is charged as a normal Worker invocation. We plan to explore additional related options in the future. ↩ -
To use Durable Objects with your Cloudflare Pages project, you must create a separate Worker with a Durable Object and then declare a binding to it in both your Production and Preview environments. Using Durable Objects with Workers is simpler and recommended. ↩
-
Workers Builds supports enabling non-production branch builds, though does not yet have the same level of configurability as Pages does. ↩
-
Workers supports popular frameworks, many of which implement file-based routing. Additionally, you can use Wrangler to compile your folder of
functions/
into a Worker to help ease the migration from Pages to Workers. ↩ -
As in 4, Wrangler can compile your Pages Functions into a Worker. Or if you are starting from scratch, everything that is possible with Pages Functions can also be achieved by adding code to your Worker or by using framework-specific plugins for relevant third party tools. ↩
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Products
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark