Tutorial · cloudflare · Published 2026-08-16 · 4 min read

Build a redirect rule with Cloudflare Workers

How to redirect legacy paths with a Cloudflare Worker: a small fetch handler, the pattern for prefix and regex redirects, and when a Single Redirect beats it.

When a Worker beats a redirect rule

Cloudflare's built-in redirect options cover the common cases: a Single Redirect matching a path, a Bulk Redirect list for thousands of entries, and a Redirect Rule for query-aware juggling. A Worker is the right tool only when the logic cannot be expressed as a static map:

The default answer for most sites is still the built-in rule. This article is for the long tail where a Worker is genuinely the smallest honest option.

The redirect handler

A Worker redirect is a fetch handler that returns a 301/302 with a Location. The simplest shape:

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === "/legacy-page") {
      return Response.redirect("https://www.example.com/new-page/", 301);
    }
    return fetch(request);
  },
};

new URL(request.url) gives you the pathname, searchParams, host, and protocol of the incoming request. Response.redirect sets the Location header and a status code for you; for a permanent move use 301, for a temporary or A/B test use 302.

A path-table redirect

For a set of exact paths, keep the map in a constant and look it up:

const redirects = {
  "/old-about": "/about/",
  "/old-contact": "/contact/",
  "/old-services/": "/services/",
};

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const target = redirects[url.pathname];
    if (target) return Response.redirect(target, 301);
    return fetch(request);
  },
};

A map entry that does not end in / matches exactly, so appending a trailing slash consistently is still your discipline. The redirect map article explains the SEO side, which states the trailing-slash rule as part of why you standardize.

Redirect when parts of the URL need to carry into the destination

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === "/search") {
      const q = url.searchParams.get("q") || "";
      return Response.redirect(`https://www.example.com/s?q=${encodeURIComponent(q)}`, 301);
    }
    return fetch(request);
  },
};

Query strings are preserved only because you forward them explicitly. new URL(request.url) gives you the searchParams; anything you want in the destination must be in the Location you build.

From pattern to deploy

  1. Set the worker in the dashboard or wrangler deploy, and add a route so the worker only runs on the paths you want (routing /* and condition pathname in JS is redundant and costs a request per miss, which is wasteful).
  2. Use the Workers playground first: it edits and runs your code before you tie it to production traffic.
  3. After each deploy, verify with curl from outside: curl -I https://example.com/old should return 301 Location: ....

Verification table

TrafficExpected Location
example.com/oldhttps://www.example.com/about/ with 301
example.com/old?q=xhttps://www.example.com/about/?q=x with 301
example.com/otherpasses through to the origin

When the redirect is one route, prefer a built-in rule over a worker. When it is thousands of routes or dynamic derivation, keep the worker tiny and the map data in a KV or the constant, which is the Workers basics pattern.

Need a website built, fixed, optimised, migrated or replaced?

This technical resource is written by CSMBAC, a small design and development studio. If you would rather hand the problem to a professional, the website service page explains how we build enquiry-ready websites.

Explore website services