Web Development

The No-Nonsense Guide to Migrating from Pages to Workers

Ready to level up your Cloudflare project? Our no-nonsense guide walks you through migrating from Pages to Workers for ultimate control and dynamic power.

D

Daniel Carter

Senior Developer Advocate specializing in serverless architecture and edge computing solutions.

7 min read19 views

Cloudflare Pages is a dream for deploying static sites. You connect a Git repository, push your code, and—*poof*—your site is live on a global network. It’s simple, fast, and incredibly powerful for what it does. But what happens when your project outgrows "simple"? What if you need more dynamic logic, custom middleware, or fine-grained control over every single request?

That’s when you start eyeing Cloudflare Workers. Workers are the powerhouse of the Cloudflare ecosystem, a serverless platform that lets you run JavaScript (and Wasm) at the edge, intercepting and modifying requests before they even hit an origin server. The move from Pages to Workers can feel like trading in a go-kart for a Formula 1 car—it’s a massive upgrade in power, but it also comes with a steeper learning curve.

This guide is here to flatten that curve. We're cutting through the noise to give you a straightforward, no-nonsense path for migrating your project from Cloudflare Pages to Cloudflare Workers. We'll cover why you’d make the switch, what to prepare, and a step-by-step process to get you across the finish line with confidence.

Why Migrate? Pages vs. Workers Showdown

Before you start moving files around, it’s crucial to understand the fundamental differences. Pages is an opinionated, integrated platform for static sites with light serverless functions. Workers is an unopinionated, code-first environment for... well, almost anything.

Feature Cloudflare Pages Cloudflare Workers
Primary Use Case Static site hosting (Jamstack) Full-stack applications, APIs, middleware, request/response modification
Configuration Convention-based (e.g., /functions directory) Code-based (wrangler.toml file and your script's entry point)
Routing File-system based for assets and functions Completely custom. You control all routing in your code.
Static Assets Handled automatically out-of-the-box Requires explicit handling (e.g., Workers Sites or a binding to an R2 bucket)
Middleware Limited (_middleware.js) Unlimited. The entire Worker is essentially middleware.
Control Simple and streamlined Absolute, granular control over every request and response

You should migrate if you find yourself needing:

  • Complex routing logic that doesn't map neatly to a file system.
  • Advanced middleware chains for things like A/B testing, authentication, or custom logging on every request.
  • To run a full-stack application entirely on the edge, without a traditional origin server.
  • Direct integration with other Cloudflare products like R2, D1, or Queues in a more flexible way.

The Pre-Migration Checklist

A smooth migration is all about preparation. Don't start coding until you've gone through this checklist.

1. Inventory Your Assets

List all your static assets: HTML, CSS, JavaScript bundles, images, fonts, etc. In Pages, these are just files in your project. In a Worker, you'll need to decide how to serve them. Your `public` or `dist` folder is your starting point.

2. Understand Your Functions

Review every file in your /functions directory. What does each one do? Is it a simple API endpoint? Does it handle form submissions? These will need to be re-written as routes within your single Worker script.

3. Set Up Your Local Environment

You can't migrate without Wrangler, the command-line tool for Cloudflare's developer platform. If you don't have it, install it now:

npm install -g wrangler

You'll also need Node.js and npm installed on your machine.

4. Plan Your Routing

Advertisement

How will you map incoming requests to different logic? For a small project, a simple `if/else` or `switch` statement on the URL pathname might suffice. For anything more complex, consider a lightweight router like `itty-router`, which is designed specifically for Workers.

The Migration Process: A Step-by-Step Guide

Alright, let's get our hands dirty. We'll take this one step at a time.

Step 1: Initialize Your Worker

Create a new directory for your project and run the Wrangler initialization command:

mkdir my-worker-project && cd my-worker-project
wrangler init

Wrangler will create a few files, most importantly:

  • wrangler.toml: Your Worker's configuration file. This is where you define its name, compatibility dates, and any bindings to other services.
  • src/index.js (or .ts): The entry point for your Worker. All requests will be handled by the `fetch` handler exported from this file.

Step 2: Serving Static Assets (The Right Way)

This is the biggest mindset shift from Pages. Your Worker doesn't automatically serve files. The best way to replicate the Pages experience is with Workers Sites.

1. Create a public folder in your project root and move all your static assets (from your old Pages project's build output, e.g., `dist` or `build`) into it.

2. Configure your wrangler.toml to use Workers Sites. Add this section to the file:

[site]
bucket = "./public" # or whatever your asset folder is named

Now, when you run wrangler dev, Wrangler will automatically upload your static assets and make them available to your Worker. The standard Worker template doesn't include the asset handling logic, so you'll need to import it from `@cloudflare/kv-asset-handler`. Your src/index.js should look something like this to get started:

import { getAssetFromKV } from '@cloudflare/kv-asset-handler';
import manifestJSON from '__STATIC_CONTENT_MANIFEST';
const assetManifest = JSON.parse(manifestJSON);

export default {
  async fetch(request, env, ctx) {
    try {
      // First, try to serve a static asset
      return await getAssetFromKV(
        {
          request,
          waitUntil: ctx.waitUntil.bind(ctx),
        },
        {
          ASSET_NAMESPACE: env.__STATIC_CONTENT,
          ASSET_MANIFEST: assetManifest,
        }
      );
    } catch (e) {
      // If the asset is not found, you can then proceed to your dynamic logic
      // For now, let's just return a 404
      let pathname = new URL(request.url).pathname;
      return new Response(`Asset not found at ${pathname}`, { status: 404 });
    }
  },
};

This code attempts to find a matching static asset for every request. If it fails (throws an error), you know the request is for a dynamic route, which we'll handle next.

Step 3: Migrating Pages Functions to Worker Logic

Let's say you had a Pages Function at /functions/api/user.js that returned some JSON. To replicate this, you'll add routing logic inside the `catch` block from the previous step.

Using `itty-router` makes this clean and scalable. First, install it: npm install itty-router.

Now, modify your src/index.js to include the router:

import { Router } from 'itty-router';
import { getAssetFromKV } from '@cloudflare/kv-asset-handler';
import manifestJSON from '__STATIC_CONTENT_MANIFEST';
const assetManifest = JSON.parse(manifestJSON);

// Create a new router
const router = Router();

// Our old /api/user route
router.get('/api/user', () => {
  const user = { id: 1, name: 'John Doe', email: 'john.doe@example.com' };
  return new Response(JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' },
  });
});

// Catch-all for everything else - a 404
router.all('*', () => new Response('Not Found.', { status: 404 }));

export default {
  async fetch(request, env, ctx) {
    try {
      // Try for a static asset first
      return await getAssetFromKV(
        { request, waitUntil: ctx.waitUntil.bind(ctx) },
        { ASSET_NAMESPACE: env.__STATIC_CONTENT, ASSET_MANIFEST: assetManifest }
      );
    } catch (e) {
      // If it's not a static asset, pass it to the router
      return router.handle(request);
    }
  },
};

See how clean that is? You can now easily translate every file from your old /functions directory into a route in your `router` object.

Step 4: Handling Redirects and Headers

Did you have a _redirects or _headers file in your Pages project? These also need to be converted into code.

For redirects: Add a route that returns a Response.redirect().

// Replicating a line from _redirects: /old-path /new-path 301
router.get('/old-path', () => Response.redirect('https://your-site.com/new-path', 301));

For headers: You can create a middleware function with your router to add headers to certain paths, or simply add them to your `Response` objects as needed.

// Replicating a rule from _headers for /assets/*
router.get('/assets/*', (request) => {
  // In a real app, you would fetch the asset here
  // For this example, we assume it's handled by the KV asset handler
  // and this route is for adding headers to dynamic content under /assets
  const response = new Response('Some asset content');
  response.headers.set('X-Custom-Header', 'My-Value');
  response.headers.set('Cache-Control', 'max-age=31536000');
  return response;
});

Deployment and Post-Migration Sanity Checks

Once you've migrated your assets and logic, it's time to go live.

1. Deploying with Wrangler

The moment of truth. Run the deploy command:

wrangler deploy

Wrangler will bundle your code, upload your static assets, and deploy your Worker to Cloudflare's global network. It will give you a *.workers.dev URL to test with.

2. Updating Your DNS and Custom Domain

In the Cloudflare dashboard, go to your domain, navigate to the "Workers & Pages" tab, and add a route. For example, you can route your-domain.com/* to your newly deployed Worker. This tells Cloudflare to send all traffic for your site through the Worker instead of the Pages project. You can then safely disconnect the custom domain from your Pages project.

3. Testing Everything

Don't skip this step! Systematically check:

  • Does your homepage load correctly?
  • Are all CSS, JS, and images loading? Check the browser's Network tab.
  • Do your API endpoints work as expected? Test them with `curl` or a tool like Postman.
  • Are your redirects working?
  • Check the browser console for any errors.

Conclusion: Was It Worth It?

Migrating from Pages to Workers is an investment. You trade the simplicity of a convention-over-configuration platform for the raw power and flexibility of a code-first environment. The initial setup is more involved, requiring you to manually handle things like static assets and routing that Pages does for you automatically.

But the payoff is immense. Your application is no longer constrained. You have complete control to build whatever you can imagine, right at the edge. By following this guide, you've not only moved your project but also built a solid, scalable foundation for its future. Welcome to the world of limitless possibilities with Cloudflare Workers.

Tags

You May Also Like