OtherAdvanced

Cloudflare Worker SEO Template

A ready-to-deploy Cloudflare Worker template for common edge SEO tasks: bulk redirects, schema injection, hreflang tags, and meta tag optimization.

Time to Complete
2-4 hours
Word Count
2,000-3,500 words
Sections
8
Difficulty
Advanced

Best Used For

Bulk Redirect Management

Handle thousands of redirect rules at the CDN edge without touching your origin server

Dynamic Schema Injection

Inject JSON-LD structured data into pages based on URL patterns

Hreflang Tag Generation

Automatically generate and inject hreflang tags for international sites

Meta Tag Optimization

Override or inject title tags and meta descriptions at the edge

Template Structure

1

Worker Project Setup

Initialize a Cloudflare Worker project with Wrangler CLI and configure routing

Example: wrangler init seo-worker && cd seo-worker
2

Redirect Map Configuration

Define redirect rules in a KV store or static map with source, destination, and status code

Example: Map of /old-path → /new-path with 301 status
3

HTML Response Modification

Intercept origin responses and modify HTML to inject or replace SEO elements

Example: Parse <head> section and insert JSON-LD before </head>
4

Schema Template Library

Define reusable JSON-LD templates for Article, Product, FAQ, and BreadcrumbList schemas

Example: Article schema with headline, author, datePublished, dateModified
5

Hreflang Injection Logic

Generate hreflang link tags based on URL patterns and a language/region mapping table

Example: Inject <link rel='alternate' hreflang='es' href='...'> for all supported locales
6

Bot Detection and Routing

Identify search engine crawlers by User-Agent and serve pre-rendered or modified content

Example: Check for Googlebot, Bingbot, GPTBot User-Agent strings
7

Testing and Deployment

Test the worker locally with Miniflare, validate SEO output, and deploy to production

Example: wrangler dev --local for testing, wrangler publish for deployment
8

Monitoring and Debugging

Set up logging, error tracking, and Search Console monitoring for edge SEO changes

Example: wrangler tail for real-time logs, structured error reporting

Example Outputs

Worker Project Setup

wrangler init seo-worker && cd seo-worker

Redirect Map Configuration

Map of /old-path → /new-path with 301 status

HTML Response Modification

Parse <head> section and insert JSON-LD before </head>

Schema Template Library

Article schema with headline, author, datePublished, dateModified

Hreflang Injection Logic

Inject <link rel='alternate' hreflang='es' href='...'> for all supported locales

Common Pitfalls

  • Test with Google's Rich Results Test after deploying schema injection
  • Monitor Search Console crawl stats for response time regressions
  • Use 301 (permanent) for SEO-critical redirects, 302 (temporary) for tests
  • Cache redirect maps in worker memory to avoid KV lookups on every request
  • Inject Organization and WebSite schema for AI knowledge graph signals
  • Add FAQ schema to key pages to increase AI citation probability

Optimization Tips

SEO Tips

  • Test with Google's Rich Results Test after deploying schema injection
  • Monitor Search Console crawl stats for response time regressions
  • Use 301 (permanent) for SEO-critical redirects, 302 (temporary) for tests
  • Cache redirect maps in worker memory to avoid KV lookups on every request

GEO Tips

  • Inject Organization and WebSite schema for AI knowledge graph signals
  • Add FAQ schema to key pages to increase AI citation probability
  • Ensure AI crawlers (GPTBot, ClaudeBot) are not blocked by bot detection logic

Example Keywords

cloudflare worker seo redirectedge seo cloudflare worker templateinject schema markup cloudflare workershreflang cloudflare worker

What This Template Covers

This template provides the code structure and configuration patterns for deploying common SEO operations as Cloudflare Workers. Each section includes a working pattern you can adapt to your specific URL structure and content architecture.

Edge SEO with Cloudflare Workers lets you manage redirects, inject structured data, handle hreflang, and optimize meta tags — all without modifying your origin server or waiting for deployment cycles.


Section 1: Worker Project Setup

Initialize the project

npm create cloudflare@latest seo-worker
cd seo-worker

Configure routing in wrangler.toml

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

# Route patterns - adjust to your domain
routes = [
  { pattern = "example.com/*", zone_name = "example.com" }
]

# KV namespace for redirect maps
[[kv_namespaces]]
binding = "REDIRECTS"
id = "your-kv-namespace-id"

Worker entry point structure

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url)

    // 1. Check redirects first (fastest exit)
    const redirect = await checkRedirect(url, env)
    if (redirect) return redirect

    // 2. Fetch from origin
    const response = await fetch(request)

    // 3. Modify HTML responses
    if (response.headers.get("content-type")?.includes("text/html")) {
      return modifyHTML(response, url, env)
    }

    return response
  }
}

Section 2: Bulk Redirect Management

Static redirect map (for smaller sets)

const REDIRECTS: Record<string, { target: string; status: number }> = {
  "/old-blog/post-one": { target: "/blog/post-one", status: 301 },
  "/legacy/about": { target: "/about", status: 301 },
  "/temp-promo": { target: "/deals", status: 302 }
}

function checkStaticRedirect(url: URL): Response | null {
  const rule = REDIRECTS[url.pathname]
  if (rule) {
    return Response.redirect(
      new URL(rule.target, url.origin).toString(),
      rule.status
    )
  }
  return null
}

KV-based redirect map (for thousands of rules)

async function checkKVRedirect(url: URL, env: Env): Promise<Response | null> {
  const rule = await env.REDIRECTS.get(url.pathname, "json")
  if (rule) {
    return Response.redirect(
      new URL(rule.target, url.origin).toString(),
      rule.status || 301
    )
  }
  return null
}

Pattern-based redirects (for URL structure changes)

function checkPatternRedirect(url: URL): Response | null {
  // Trailing slash normalization
  if (url.pathname.length > 1 && url.pathname.endsWith("/")) {
    return Response.redirect(
      url.origin + url.pathname.slice(0, -1) + url.search,
      301
    )
  }

  // Category URL migration: /category/slug → /blog/slug
  const categoryMatch = url.pathname.match(/^\/category\/(.+)$/)
  if (categoryMatch) {
    return Response.redirect(`${url.origin}/blog/${categoryMatch[1]}`, 301)
  }

  return null
}

Section 3: HTML Response Modification

The HTMLRewriter approach

Cloudflare Workers provides HTMLRewriter for efficient streaming HTML modification:

async function modifyHTML(
  response: Response,
  url: URL,
  env: Env
): Promise<Response> {
  return new HTMLRewriter()
    .on("head", new HeadElementHandler(url))
    .on('meta[name="description"]', new MetaDescriptionHandler(url))
    .on("title", new TitleHandler(url))
    .transform(response)
}

Inject elements into <head>

class HeadElementHandler {
  private url: URL

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

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

    // Inject hreflang tags
    const hreflangs = getHreflangsForURL(this.url)
    for (const tag of hreflangs) {
      element.append(tag, { html: true })
    }
  }
}

Section 4: Schema Template Library

Article schema

function articleSchema(url: URL, meta: PageMeta) {
  return {
    "@context": "https://schema.org",
    "@type": "Article",
    headline: meta.title,
    description: meta.description,
    url: url.toString(),
    datePublished: meta.publishedAt,
    dateModified: meta.updatedAt,
    author: {
      "@type": "Organization",
      name: meta.author || "Your Company"
    },
    publisher: {
      "@type": "Organization",
      name: "Your Company",
      logo: { "@type": "ImageObject", url: `${url.origin}/logo.png` }
    }
  }
}

FAQ schema

function faqSchema(faqs: { question: string; answer: string }[]) {
  return {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: faqs.map(faq => ({
      "@type": "Question",
      name: faq.question,
      acceptedAnswer: {
        "@type": "Answer",
        text: faq.answer
      }
    }))
  }
}
function breadcrumbSchema(url: URL) {
  const segments = url.pathname.split("/").filter(Boolean)
  return {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    itemListElement: segments.map((segment, index) => ({
      "@type": "ListItem",
      position: index + 1,
      name: segment.replace(/-/g, " ").replace(/\b\w/g, c => c.toUpperCase()),
      item: `${url.origin}/${segments.slice(0, index + 1).join("/")}`
    }))
  }
}

Section 5: Hreflang Injection

Language/region mapping

const LOCALE_MAP: Record<string, { lang: string; region?: string }[]> = {
  "/en/": [
    { lang: "en" },
    { lang: "es", region: "ES" },
    { lang: "fr", region: "FR" },
    { lang: "de", region: "DE" }
  ]
}

function getHreflangsForURL(url: URL): string[] {
  const tags: string[] = []
  const pathPrefix = Object.keys(LOCALE_MAP).find(p =>
    url.pathname.startsWith(p)
  )

  if (!pathPrefix) return tags

  const locales = LOCALE_MAP[pathPrefix]
  const basePath = url.pathname.replace(pathPrefix, "")

  for (const locale of locales) {
    const hreflang = locale.region
      ? `${locale.lang}-${locale.region}`
      : locale.lang
    const href = `${url.origin}/${locale.lang}/${basePath}`
    tags.push(`<link rel="alternate" hreflang="${hreflang}" href="${href}" />`)
  }

  // Add x-default
  tags.push(
    `<link rel="alternate" hreflang="x-default" href="${url.origin}/en/${basePath}" />`
  )

  return tags
}

Section 6: Bot Detection

Identify search engine crawlers

const BOT_USER_AGENTS = [
  "Googlebot",
  "Bingbot",
  "Slurp",
  "DuckDuckBot",
  "GPTBot",
  "ClaudeBot",
  "PerplexityBot",
  "Bytespider"
]

function isBot(request: Request): boolean {
  const ua = request.headers.get("user-agent") || ""
  return BOT_USER_AGENTS.some(bot => ua.includes(bot))
}

Serve pre-rendered content to bots

async function handleBotRequest(request: Request, env: Env): Promise<Response> {
  const url = new URL(request.url)

  // Check for pre-rendered version in KV
  const prerendered = await env.PRERENDER_CACHE.get(url.pathname)
  if (prerendered) {
    return new Response(prerendered, {
      headers: { "content-type": "text/html; charset=utf-8" }
    })
  }

  // Fall through to origin
  return fetch(request)
}

Important: Do not block legitimate AI crawlers (GPTBot, ClaudeBot) unless you specifically want to opt out of AI training data. These crawlers also power real-time retrieval for AI search engines.


Section 7: Testing and Deployment

Local testing with Wrangler

# Start local dev server
wrangler dev --local

# Test redirect
curl -I http://localhost:8787/old-path

# Test schema injection
curl http://localhost:8787/blog/my-post | grep "application/ld+json"

# Test with bot User-Agent
curl -H "User-Agent: Googlebot" http://localhost:8787/spa-page

Validate SEO output

Before deploying to production:

  1. Test redirects return correct status codes (301 vs 302)
  2. Validate injected JSON-LD with Google's Rich Results Test
  3. Check hreflang tags with hreflang validator tools
  4. Verify bot detection serves complete HTML

Deploy to production

# Deploy to Cloudflare
wrangler deploy

# Verify on production
curl -I https://yourdomain.com/old-path

Section 8: Monitoring

Key metrics to track

  • Worker execution time — Should be under 10ms for most operations
  • Error rate — Monitor for unhandled exceptions in worker logs
  • Redirect hit rate — Track which rules are actually being used
  • Search Console crawl stats — Watch for response time changes after deployment

Logging pattern

async function logRequest(request: Request, action: string, details: string) {
  console.log(
    JSON.stringify({
      timestamp: new Date().toISOString(),
      url: request.url,
      action,
      details,
      userAgent: request.headers.get("user-agent")
    })
  )
}

Search Console monitoring

After deploying edge SEO changes:

  1. Check the Crawl Stats report for response time increases
  2. Monitor the Coverage report for new crawl errors
  3. Review the Rich Results report for schema validation issues
  4. Watch for indexing anomalies in the Pages report

Common Mistakes

Redirect loops

Always check that redirect targets don't themselves redirect. A → B → A creates an infinite loop that search engines penalize.

Modifying non-HTML responses

Only run HTMLRewriter on responses with content-type: text/html. Running it on images, JSON, or CSS wastes execution time and can corrupt responses.

Over-fetching from KV

KV reads are fast but not free. Cache frequently accessed data in worker memory using the caches API or module-level variables.

Blocking AI crawlers accidentally

Bot detection logic often blocks all non-browser User-Agents. Ensure GPTBot, ClaudeBot, and PerplexityBot are allowed through if you want AI search visibility.

Frequently Asked Questions

How much does this cost on Cloudflare?

The Workers free tier includes 100,000 requests per day. The paid plan ($5/month) includes 10 million requests. For most SEO use cases, the free tier is sufficient during initial deployment.

Can I use this with non-Cloudflare CDNs?

The patterns translate to other edge platforms. AWS Lambda@Edge uses a similar request/response interception model. Fastly Compute and Vercel Edge Functions support comparable capabilities with different APIs.

Does edge HTML modification affect page speed?

Minimal impact. HTMLRewriter processes HTML in a streaming fashion, adding 1-5ms of latency. This is negligible compared to the 200-500ms saved by handling redirects at the edge instead of origin.

How do I roll back edge SEO changes?

Cloudflare Workers supports instant rollback to previous versions. Use wrangler rollback or roll back via the Cloudflare dashboard. Changes take effect globally within seconds.

Generate Content with This Template

Rankwise uses this template structure automatically. Create AI-optimized content in minutes instead of hours.

Try Rankwise Free
Newsletter

Stay ahead of AI search

Weekly insights on GEO and content optimization.