Edge SEO is the practice of modifying HTML responses at the CDN layer to implement SEO optimizations. Instead of changing your application code, you intercept responses between your server and users (or crawlers) and transform them on the fly.
This guide covers implementation across major edge platforms, from initial setup to production deployment.
When to Use Edge SEO
Edge SEO is the right approach when:
- You can't modify the origin — Legacy CMS, locked-down e-commerce platforms, or third-party hosted sites
- Development queues are long — SEO fixes need to ship faster than the engineering sprint cycle allows
- You want to test before committing — A/B test meta tags, structured data, or content changes before making them permanent
- You need global consistency — Hreflang tags, canonical URLs, and redirects that must work consistently across all edge locations
Edge SEO is the wrong approach when:
- Changes are simple and your team can deploy quickly
- The modification requires access to application state (user data, session info)
- You need to modify content that's rendered client-side (SPAs without SSR)
Platform Comparison
| Feature | Cloudflare Workers | AWS Lambda@Edge | Vercel Edge Middleware |
|---|---|---|---|
| Execution location | 300+ edge locations | CloudFront POPs | Vercel edge network |
| Cold start | None (V8 isolates) | 50-200ms (Lambda) | None (V8 isolates) |
| HTML transformation | HTMLRewriter API | Manual string/DOM parsing | Manual or library |
| Max execution time | 30s (paid) | 30s (viewer), 60s (origin) | 30s |
| Cost | $5/mo + $0.50/M requests | $0.60/M requests | Included in plan |
| Best for | Any origin | AWS-hosted sites | Next.js sites |
Implementation: Cloudflare Workers
Cloudflare's HTMLRewriter API makes it the most ergonomic platform for edge SEO. HTMLRewriter streams through the HTML, applying transformations without buffering the entire document.
Setup
npm create cloudflare@latest seo-worker
cd seo-worker
Core Pattern
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url)
const response = await fetch(request)
// Skip non-HTML responses
if (!response.headers.get("content-type")?.includes("text/html")) {
return response
}
return new HTMLRewriter()
.on("title", new TitleOptimizer(url))
.on('meta[name="description"]', new MetaOptimizer(url))
.on("head", new SchemaInjector(url))
.on('link[rel="canonical"]', new CanonicalFixer(url))
.transform(response)
}
}
Deploy to Specific Routes
# wrangler.toml
[[routes]]
pattern = "example.com/products/*"
zone_name = "example.com"
[[routes]]
pattern = "example.com/blog/*"
zone_name = "example.com"
Implementation: Vercel Edge Middleware
For Next.js sites on Vercel, Edge Middleware runs before the request reaches your application.
Setup
Create middleware.ts in your project root:
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Add headers for SEO
response.headers.set("X-Robots-Tag", "index, follow")
return response
}
export const config = {
matcher: ["/blog/:path*", "/products/:path*"]
}
HTML Rewriting in Vercel
Vercel Edge Middleware doesn't have Cloudflare's HTMLRewriter, but you can use the @worker-tools/html-rewriter polyfill or handle rewrites at the Next.js level with server components and middleware-injected headers.
For meta tag modifications, use Next.js generateMetadata with data from edge-accessible sources (KV stores, edge config).
Implementation: AWS Lambda@Edge
For sites behind CloudFront, Lambda@Edge runs at CloudFront edge locations.
Setup
// Origin Response trigger — modifies HTML after origin returns it
exports.handler = async event => {
const response = event.Records[0].cf.response
const headers = response.headers
// Only process HTML
const contentType = headers["content-type"]?.[0]?.value || ""
if (!contentType.includes("text/html")) {
return response
}
// For HTML modification, you need to read and modify the body
// Lambda@Edge can modify headers easily but body modification
// requires the response body to be available
let body = response.body || ""
// Inject structured data before </head>
const schema = JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
name: "Page Title"
})
body = body.replace(
"</head>",
`<script type="application/ld+json">${schema}</script></head>`
)
response.body = body
response.bodyEncoding = "text"
return response
}
Note: Lambda@Edge has a 1MB response body limit for origin-response triggers. For larger pages, consider using CloudFront Functions (limited to header modifications) or Cloudflare Workers instead.
Common Edge SEO Implementations
Redirect Management
Handle redirects at the edge for near-zero latency:
const redirectMap: Record<string, string> = {
"/old-page": "/new-page",
"/legacy/product": "/products/updated"
}
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url)
const redirect = redirectMap[url.pathname]
if (redirect) {
return Response.redirect(new URL(redirect, url.origin).toString(), 301)
}
return fetch(request)
}
}
Robots.txt Modification
Dynamically modify robots.txt based on environment:
if (url.pathname === "/robots.txt") {
const response = await fetch(request)
let body = await response.text()
// Block staging from crawlers
if (url.hostname.includes("staging")) {
body = "User-agent: *\nDisallow: /"
}
return new Response(body, {
headers: { "content-type": "text/plain" }
})
}
Internal Link Injection
Add contextual internal links to content pages:
class InternalLinkInjector {
element(element: Element) {
// Append related links section before closing main content
const links = `
<section class="related-content">
<h3>Related Resources</h3>
<ul>
<li><a href="/guides/seo-audit">Complete SEO Audit Guide</a></li>
<li><a href="/tools/keyword-research">Keyword Research Tool</a></li>
</ul>
</section>
`
element.append(links, { html: true })
}
}
Testing and Validation
Local Testing
Cloudflare Workers:
wrangler dev --local
# Access at http://localhost:8787
Verify with curl:
curl -s http://localhost:8787/test-page | grep -E '<title>|canonical|ld\+json'
Staging Validation
Before deploying to production:
- Deploy Worker to a staging route (
staging.example.com/*) - Run Screaming Frog or Sitebulb against staging
- Check Google's Rich Results Test with the staging URL
- Validate structured data with Schema.org validator
- Compare rendered HTML between origin and Worker-modified responses
Production Monitoring
After deployment, monitor:
- Google Search Console → Coverage report for new indexing errors
- Crawl stats → Ensure crawl rate hasn't changed
- Rich results → Verify structured data is being parsed
- Core Web Vitals → Confirm no performance regression
FAQ
Can edge SEO break my site? Yes, if implemented incorrectly. Always test on a staging route first. Common mistakes: malformed HTML injection, breaking existing structured data, and accidentally stripping important tags.
How do I handle dynamic content with edge SEO? Edge Workers can access KV stores, databases (via HTTP), and APIs. Store SEO configuration in a KV namespace and read it per-request for dynamic modifications.
What about client-side rendered SPAs? Edge SEO modifies the initial HTML response. For SPAs that render content client-side, you need SSR or prerendering before edge SEO can modify the content. Edge SEO works best with server-rendered HTML.
Is edge SEO compatible with CDN caching? Yes, but you need to consider cache keys. If your Worker modifies responses differently based on user agent or other headers, include those in the cache key to avoid serving incorrect cached responses.