← Back to Course Index

Module 5.3 — Edge SEO

Phase 5 — Advanced Specialist Topics

Edge SEO is the practice of implementing SEO changes — redirects, HTTP headers, HTML injection, hreflang, canonicals — at the CDN or edge network layer, before a request ever reaches your origin server. It lets you ship fixes that would otherwise be stuck in a development backlog for weeks, and it gives you a deployment surface that non-developers on the SEO team can control independently.

This module teaches you exactly what edge workers are, why they matter for SEO, what they can and cannot do, and how to write real workers using Cloudflare Workers — the most widely available and well-documented edge platform.


1. What Is "the Edge"?

A traditional request/response cycle looks like this:

User → DNS → Origin Server → HTML response → User

A CDN (Content Delivery Network) inserts itself into that path to cache and serve assets from a data centre that is geographically close to the user. The edge refers to those CDN nodes — sometimes called Points of Presence (PoPs) — sitting between the internet and your origin.

Edge workers (or edge functions) let you run a small JavaScript program at that node. Instead of just serving a cached file, the edge can now inspect and transform requests and responses before they continue their journey. No origin server involved in many cases.

Key edge platforms you will encounter:

Cloudflare Workers is the platform we use throughout this module because it has a generous free tier, excellent documentation, a local development tool (wrangler), and is the platform most commonly referenced in real-world Edge SEO discussions.


2. Why Edge SEO Exists (and When It Matters)

At any sufficiently complex organisation, there is a gap between what an SEO practitioner needs and how quickly engineering can deploy it. Consider these scenarios:

In all of these cases, you can implement the fix at the edge today, independently, without touching the origin server or waiting on a deployment queue. This is the primary value proposition of Edge SEO.

Edge SEO is not a replacement for fixing the root cause. It is a powerful tactical layer — a scaffolding you use while proper fixes are built, or a permanent home for logic that genuinely belongs at the infrastructure level (like global redirect management).


3. What You Can Do at the Edge (SEO Use Cases)

3.1 Redirects

The most common edge SEO use case. Instead of managing redirects in .htaccess, Nginx config, or a plugin, you centralise them in a worker. This is especially powerful for:

Example: a simple 301 redirect in a Cloudflare Worker:

export default {
  async fetch(request) {
    const url = new URL(request.url);

    // Redirect a specific old path to its new equivalent
    if (url.pathname === '/old-product-category/widget-blue') {
      return Response.redirect(
        'https://example.com/products/widget-blue',
        301
      );
    }

    // Otherwise pass the request through to the origin
    return fetch(request);
  }
};

For large redirect maps, store the mapping in a Cloudflare KV store (key-value storage at the edge) so you can update redirects without redeploying the worker itself:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const destination = await env.REDIRECT_MAP.get(url.pathname);

    if (destination) {
      return Response.redirect(destination, 301);
    }

    return fetch(request);
  }
};

The KV namespace REDIRECT_MAP is populated via the Cloudflare dashboard or the wrangler CLI — no code redeploy needed when you add new redirects.

3.2 HTTP Header Injection

Many SEO-critical signals live in HTTP headers rather than HTML. The edge is the ideal place to set or modify them:

export default {
  async fetch(request) {
    const response = await fetch(request);
    const url = new URL(request.url);

    // Clone the response so headers are mutable
    const newHeaders = new Headers(response.headers);

    // Add X-Robots-Tag: noindex to all /staging/* paths
    if (url.pathname.startsWith('/staging/')) {
      newHeaders.set('X-Robots-Tag', 'noindex, nofollow');
    }

    return new Response(response.body, {
      status: response.status,
      headers: newHeaders
    });
  }
};

3.3 HTML Transformation (Injecting into the Response Body)

This is the most powerful — and most complex — edge SEO technique. Using the HTMLRewriter API (a Cloudflare-specific streaming HTML parser), you can modify the response HTML on the fly without buffering the entire response in memory.

Use cases:

// Inject a canonical tag into the <head> of every response
export default {
  async fetch(request) {
    const response = await fetch(request);
    const url = new URL(request.url);

    // Build the canonical URL (e.g., strip query strings)
    const canonical = `https://example.com${url.pathname}`;

    return new HTMLRewriter()
      .on('head', {
        element(element) {
          element.append(
            `<link rel="canonical" href="${canonical}">`,
            { html: true }
          );
        }
      })
      .transform(response);
  }
};

A more surgical approach — replace an existing canonical rather than add a second one — uses .on('link[rel="canonical"]', ...) to select the specific element and mutate its href attribute.

return new HTMLRewriter()
  .on('link[rel="canonical"]', {
    element(element) {
      element.setAttribute('href', canonical);
    }
  })
  .transform(response);

3.4 Hreflang Injection

Hreflang is one of the most difficult things to manage in a legacy CMS. Edge injection is an excellent solution when you cannot modify the CMS templates. The pattern:

  1. Maintain a mapping of pathname → hreflang alternatives (in a KV store or a JSON file fetched from a URL)
  2. In the worker, look up the current URL's hreflang set
  3. Inject the full set of <link rel="alternate" hreflang="..."> tags into <head> using HTMLRewriter
// Simplified hreflang injection example
const hreflangMap = {
  '/about': [
    { lang: 'en', url: 'https://example.com/about' },
    { lang: 'de', url: 'https://example.de/ueber-uns' },
    { lang: 'fr', url: 'https://example.fr/a-propos' },
    { lang: 'x-default', url: 'https://example.com/about' }
  ]
};

export default {
  async fetch(request) {
    const response = await fetch(request);
    const url = new URL(request.url);
    const alternates = hreflangMap[url.pathname];

    if (!alternates) return response;

    const tags = alternates
      .map(({ lang, url: href }) =>
        `<link rel="alternate" hreflang="${lang}" href="${href}">`
      )
      .join('\n');

    return new HTMLRewriter()
      .on('head', {
        element(el) { el.append(tags, { html: true }); }
      })
      .transform(response);
  }
};

3.5 A/B Testing and Personalisation (SEO-Safe)

Edge workers can serve different variants to users without JavaScript-based flicker. However, there are strict SEO rules here:


4. Setting Up Cloudflare Workers: A Practical Walkthrough

4.1 Prerequisites

# Install wrangler globally
npm install -g wrangler

# Log in to your Cloudflare account
wrangler login

4.2 Create a New Worker Project

wrangler init my-seo-worker
cd my-seo-worker

This scaffolds a project with a wrangler.toml configuration file and a src/index.js entry point.

A minimal wrangler.toml for a site-proxying worker:

name = "my-seo-worker"
main = "src/index.js"
compatibility_date = "2024-01-01"

[vars]
SITE_ORIGIN = "https://your-origin.example.com"

4.3 Develop Locally

# Start a local development server
wrangler dev

wrangler dev emulates the Cloudflare Workers runtime locally. Requests to localhost:8787 are processed by your worker, which then proxies to your configured origin. You can test HTML transformation and redirects without touching production.

4.4 Deploy

# Deploy to Cloudflare's edge network
wrangler deploy

After deploying, configure a route in the Cloudflare dashboard (Workers → your worker → Routes) to attach the worker to your domain — e.g., example.com/* to catch all requests.

4.5 KV Namespaces for Dynamic Data

# Create a KV namespace
wrangler kv:namespace create "REDIRECT_MAP"

# Write a key-value pair
wrangler kv:key put --namespace-id=YOUR_NAMESPACE_ID "/old-page" "https://example.com/new-page"

Bind it in wrangler.toml:

[[kv_namespaces]]
binding = "REDIRECT_MAP"
id = "YOUR_NAMESPACE_ID"

5. The HTMLRewriter API in Depth

HTMLRewriter is a streaming HTML parser and transformer that is unique to Cloudflare Workers. It processes the response body as a stream — meaning it does not load the entire page into memory — and allows you to attach handlers to CSS-selector-matched elements.

The API surface:

Handler methods available on matched elements:

Example — remove duplicate canonical tags, then inject the correct one:

let canonicalCount = 0;

return new HTMLRewriter()
  .on('link[rel="canonical"]', {
    element(el) {
      canonicalCount++;
      // Remove all existing canonicals
      el.remove();
    }
  })
  .on('head', {
    element(el) {
      // Append a single, correct canonical
      el.append(
        `<link rel="canonical" href="${canonicalUrl}">`,
        { html: true }
      );
    }
  })
  .transform(response);

6. Limits and Gotchas

Edge SEO is powerful, but it comes with important constraints you must understand before deploying anything to production.

6.1 Performance Overhead

Every edge worker adds latency. A worker that only does a redirect lookup adds microseconds. A worker that fetches from a KV store, proxies to the origin, and transforms the entire HTML response adds more. Measure the impact with WebPageTest before and after. Workers running HTMLRewriter on large pages should be audited for TTFB impact.

6.2 Do Not Cache Stale, Incorrect Output

If a worker injects the wrong canonical and that response is then cached by the CDN, you have now spread the wrong canonical to every user until the cache expires. Always test workers thoroughly in wrangler dev before deploying, and be cautious about caching transformed responses.

6.3 It Is Not a Permanent Substitute for Root-Cause Fixes

The edge is an excellent tactical layer. It is not a reason to leave broken CMS output alone indefinitely. Document every edge workaround, track the corresponding engineering ticket, and retire the worker code when the root cause is fixed. Unmaintained workers that nobody understands become technical debt.

6.4 Cloaking Risk

Never use an edge worker to serve different content to Googlebot than to real users. The standard test is: if a human and a crawler send identical requests, they should receive identical responses. Geolocation-based personalisation that changes language or currency is acceptable if Googlebot receives a consistent version and hreflang is correctly declared.

6.5 Worker Execution Limits

Cloudflare Workers have CPU time limits (typically 10ms on the free plan, 30ms on paid, extendable). Complex transforms on very large HTML documents can approach these limits. Profile with wrangler dev --inspect to verify your worker completes well within budget.

6.6 Response Streaming and Buffering

HTMLRewriter is a streaming transformer — it does not buffer the full response body. However, if you need to make decisions based on the entire content of the response (e.g., parse JSON body), you must buffer it first with response.text() or response.json(). Buffering large responses consumes memory and adds latency — use it only when necessary.


7. Edge SEO vs Other Approaches: When to Use Each


8. Hands-On Exercises

Exercise A — Redirect Worker

Create a Cloudflare Worker that reads a redirect map from a KV namespace. Populate it with at least 10 old/new URL pairs. Deploy to a test domain and verify using curl -I that each old URL returns a 301 with the correct Location header.

# Verify a 301 in the terminal
curl -I https://yourtestsite.com/old-url

Exercise B — Header Injection Worker

Build a worker that adds X-Robots-Tag: noindex, nofollow to all responses on paths matching /drafts/* and /preview/*. Verify using the Network tab in DevTools that the header is present on matching paths and absent on others.

Exercise C — Canonical Injection with HTMLRewriter

Deploy a worker on a test page that:

  1. Removes any existing <link rel="canonical"> tags from the <head>
  2. Injects a single, correct canonical derived from the request URL (stripped of query parameters)

Verify by using View Source (or curl) that the raw HTML contains exactly one canonical tag and it has the correct href.

Exercise D — Hreflang Injection

For a set of 5 URL pairs (an English page and its French equivalent), build the hreflang map in a worker and inject the full hreflang set into each page's <head>. Validate the output with Screaming Frog's hreflang analyser or the hreflang tag checker at hreflang.org.


9. Module Milestone

You have reached the milestone for this module when you can do all of the following without referring to notes:


10. Further Reading and Resources