Guideadvanced

Cloudflare Workers for SEO: Dynamic Optimization at the Edge

Learn how to use Cloudflare Workers to implement SEO fixes, A/B test meta tags, inject structured data, and modify HTML responses without changing your origin server.

Rankwise Team·Updated Mar 30, 2026·3 min read

Cloudflare Workers let you intercept and modify HTTP responses at the edge — between your origin server and the user (or crawler). For SEO, this means you can fix title tags, inject structured data, add hreflang tags, and test meta descriptions without touching your backend code or waiting for a deploy cycle.

This guide covers practical Worker patterns for SEO teams.


Why Edge-Based SEO Matters

Speed of Implementation

Traditional SEO fixes require a developer, a code review, a deploy, and cache invalidation. A Cloudflare Worker can modify HTML in production within minutes. For SEO teams blocked by development queues, this is transformative.

Backend-Agnostic

Workers operate on the HTTP response regardless of your backend technology. WordPress, Shopify, Next.js, custom PHP — the Worker sees HTML and modifies it. This makes edge SEO viable for teams that can't easily modify their CMS or application code.

Reversibility

Every Worker change can be rolled back instantly by modifying or disabling the Worker route. No database migrations, no git reverts, no cache busting.


Setting Up Your First SEO Worker

Prerequisites

  • Cloudflare account with your domain proxied (orange cloud enabled)
  • Wrangler CLI installed (npm install -g wrangler)
  • Basic JavaScript/TypeScript knowledge

Project Structure

seo-worker/
  ├── src/
  │   └── index.ts
  ├── wrangler.toml
  └── package.json

Basic Worker Template

export default {
  async fetch(request: Request): Promise<Response> {
    const response = await fetch(request)

    // Only modify HTML responses
    const contentType = response.headers.get("content-type") || ""
    if (!contentType.includes("text/html")) {
      return response
    }

    // Use HTMLRewriter for efficient streaming transformation
    return new HTMLRewriter()
      .on("title", new TitleHandler())
      .transform(response)
  }
}

class TitleHandler {
  element(element: Element) {
    // Modify title tag content
    element.setInnerContent("New Title | Brand Name")
  }
}

SEO Worker Patterns

Pattern 1: Dynamic Title Tags

Modify title tags based on URL path, query parameters, or A/B test groups:

class DynamicTitleHandler {
  private path: string
  private titleMap: Record<string, string>

  constructor(path: string) {
    this.path = path
    this.titleMap = {
      "/products/widget": "Best Widget for Teams | 2026 Reviews & Pricing",
      "/blog/seo-guide": "SEO Guide: 15 Strategies That Work in 2026"
    }
  }

  element(element: Element) {
    const newTitle = this.titleMap[this.path]
    if (newTitle) {
      element.setInnerContent(newTitle)
    }
  }
}

Pattern 2: Inject Structured Data

Add JSON-LD structured data without modifying your templates:

class StructuredDataInjector {
  private schema: object

  constructor(schema: object) {
    this.schema = schema
  }

  element(element: Element) {
    const script = `<script type="application/ld+json">${JSON.stringify(this.schema)}</script>`
    element.append(script, { html: true })
  }
}

// Usage
const faqSchema = {
  "@context": "https://schema.org",
  "@type": "FAQPage",
  mainEntity: [
    {
      "@type": "Question",
      name: "What is edge SEO?",
      acceptedAnswer: {
        "@type": "Answer",
        text: "Edge SEO uses CDN workers to modify HTML for search optimization."
      }
    }
  ]
}

new HTMLRewriter()
  .on("head", new StructuredDataInjector(faqSchema))
  .transform(response)

Pattern 3: Hreflang Tag Injection

Add hreflang tags for international SEO without modifying every page template:

class HreflangInjector {
  private url: URL

  constructor(url: URL) {
    this.url = url
  }

  element(element: Element) {
    const path = this.url.pathname
    const domain = this.url.hostname

    const hreflangs = [
      { lang: "en", path: `/en${path}` },
      { lang: "es", path: `/es${path}` },
      { lang: "de", path: `/de${path}` },
      { lang: "x-default", path: path }
    ]

    const tags = hreflangs
      .map(
        ({ lang, path }) =>
          `<link rel="alternate" hreflang="${lang}" href="https://${domain}${path}" />`
      )
      .join("\n")

    element.append(tags, { html: true })
  }
}

Pattern 4: Canonical URL Fixes

Fix canonical tags programmatically when your CMS generates incorrect canonicals:

class CanonicalFixer {
  private correctCanonical: string

  constructor(url: URL) {
    // Remove trailing slashes, force lowercase, strip query params
    this.correctCanonical = `https://${url.hostname}${url.pathname.replace(/\/$/, "").toLowerCase()}`
  }

  element(element: Element) {
    if (element.getAttribute("rel") === "canonical") {
      element.setAttribute("href", this.correctCanonical)
    }
  }
}

Pattern 5: Meta Description A/B Testing

Test different meta descriptions to optimize click-through rate:

class MetaDescriptionTest {
  private variants: string[]

  constructor(variants: string[]) {
    this.variants = variants
  }

  element(element: Element) {
    if (element.getAttribute("name") === "description") {
      // Simple hash-based split (consistent per URL)
      const variantIndex =
        Math.abs(hashCode(element.getAttribute("content") || "")) %
        this.variants.length
      element.setAttribute("content", this.variants[variantIndex])
    }
  }
}

Performance Considerations

Worker Execution Time

Cloudflare Workers have a 30-second CPU time limit (50ms on the free plan). HTMLRewriter is streaming-based, so it handles large pages efficiently without buffering the entire response.

Caching Strategy

Workers execute before Cloudflare's cache by default. For SEO modifications that don't vary by user, cache the Worker's output:

const cacheKey = new Request(request.url, request)
const cache = caches.default

let response = await cache.match(cacheKey)
if (!response) {
  response = await fetch(request)
  response = new HTMLRewriter()
    .on("title", new TitleHandler())
    .transform(response)

  // Cache for 1 hour
  response = new Response(response.body, response)
  response.headers.set("Cache-Control", "public, max-age=3600")
  await cache.put(cacheKey, response.clone())
}
return response

Bot Detection

Serve different modifications to crawlers vs. users (carefully — cloaking violations apply):

const userAgent = request.headers.get("user-agent") || ""
const isBot = /googlebot|bingbot|yandex/i.test(userAgent)

// Only inject structured data for bots (to avoid client-side JS conflicts)
if (isBot) {
  return new HTMLRewriter()
    .on("head", new StructuredDataInjector(schema))
    .transform(response)
}

Warning: Only use bot detection for additive improvements (like structured data injection), never for showing fundamentally different content. Google considers content cloaking a violation.


Deployment and Monitoring

Wrangler Configuration

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

[[routes]]
pattern = "example.com/blog/*"
zone_name = "example.com"

Gradual Rollout

Start with a specific URL pattern (e.g., /blog/*) before expanding to the entire site. Monitor:

  • Google Search Console for indexing errors after deployment
  • Page speed metrics (Workers add minimal latency but verify)
  • Crawl stats in Search Console for any crawl rate changes

Logging and Debugging

Use console.log in Workers for debugging — logs appear in wrangler tail:

wrangler tail --format pretty

FAQ

Does a Cloudflare Worker add latency? Minimal — typically 1-5ms. Workers execute at the edge, geographically close to the user. The latency is negligible compared to origin server response time.

Can Google detect Worker-modified content? Google renders pages with JavaScript but also caches the initial HTML response. Worker modifications are part of the initial response, so Google sees them reliably — more reliably than client-side JavaScript modifications.

Is this considered cloaking? Not if you serve the same content to users and crawlers. Workers that modify HTML uniformly (same title tag, same structured data for everyone) are fine. Workers that show fundamentally different content to bots vs. users are cloaking.

Can I use Workers with Shopify, WordPress, or other managed platforms? Yes, as long as your domain is proxied through Cloudflare. Workers intercept the response after it leaves the origin, regardless of what generates it.

Part of the SEO Fundamentals topic

Newsletter

Stay ahead of AI search

Weekly insights on GEO and content optimization.