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.
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.
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:
hreflang tags in the <head> — the system simply does not support it.X-Robots-Tag: noindex header needs to be added to a staging subdomain, but the sysadmin is unavailable.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).
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:
/page → /page/ or vice versa, applied universally)www to non-www or vice versa)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.
Many SEO-critical signals live in HTTP headers rather than HTML. The edge is the ideal place to set or modify them:
X-Robots-Tag — to noindex non-HTML resources (PDFs, images) or entire URL patterns (staging subdomains)Canonical — the Link: <url>; rel="canonical" HTTP header, an alternative to the HTML <link rel="canonical"> tagCache-Control — control how CDN and browsers cache pages for performance and freshnessStrict-Transport-Security) — security signal that also cements HTTPSX-Edge-Worker: active to confirm your worker is runningexport 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
});
}
};
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:
<link rel="canonical"> tags<link rel="alternate" hreflang> tags when the CMS cannot produce them<meta name="robots"><title> or <meta name="description"> output from a legacy CMSloading="lazy" to below-the-fold images sitewide// 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);
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:
<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);
}
};
Edge workers can serve different variants to users without JavaScript-based flicker. However, there are strict SEO rules here:
Vary header appropriately or ensure the test is transparent to crawlers (both variants accessible).wrangler CLI — Cloudflare's official developer tool# Install wrangler globally
npm install -g wrangler
# Log in to your Cloudflare account
wrangler login
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"
# 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.
# 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.
# 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"
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:
.on(selector, handler) — attach a handler to elements matching a CSS selector.onDocument(handler) — handle document-level events (doctype, end).transform(response) — returns a new Response with the transformations appliedHandler methods available on matched elements:
element.getAttribute(name) — read an attribute valueelement.setAttribute(name, value) — set an attributeelement.removeAttribute(name) — remove an attributeelement.prepend(content, { html: true }) — insert before the element's childrenelement.append(content, { html: true }) — insert after the element's childrenelement.before(content, { html: true }) — insert before the element itselfelement.after(content, { html: true }) — insert after the element itselfelement.replace(content, { html: true }) — replace the element entirelyelement.remove() — remove the element from the documentExample — 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);
Edge SEO is powerful, but it comes with important constraints you must understand before deploying anything to production.
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.
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.
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.
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.
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.
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.
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
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.
Deploy a worker on a test page that:
<link rel="canonical"> tags from the <head>
Verify by using View Source (or curl) that the raw HTML contains exactly one canonical tag and it has the correct href.
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.
You have reached the milestone for this module when you can do all of the following without referring to notes:
wrangler, develop locally, and deploy to a live domain