Start a project

Article · Web development · Cloudflare Workers · SEO · 12 min read

Making a Cloudflare Workers site searchable

A site served from a Cloudflare Worker can be as searchable as one served from a traditional host, provided the first HTTP response already contains the words a person would read. Search engines and AI crawlers are good at fetching a URL; they are much less reliable as a substitute for your server. If the Worker returns an empty shell and leaves the article to JavaScript, you have made every crawler do extra work, and some of them will not bother.

On this page 10 sections
  1. Workers can be excellent for search, if the HTML is real
  2. Generate HTML on the server, not only in the browser
  3. URL rules: host, slash, redirects and canonicals
  4. Cache HTML carefully
  5. Headers, sitemaps and robots.txt from the same origin
  6. Bot fight, WAF and the crawlers you still want in
  7. Preview hostnames that must stay noindex
  8. A launch and debug checklist
  9. Key takeaways
  10. Frequently asked questions

This is how I ship leej.uk. The pages are generated ahead of time, the Worker serves the HTML, and the same release includes the sitemap, robots.txt and the AI-facing index files. What follows is the method, not a general Cloudflare tutorial.

Workers can be excellent for search, if the HTML is real

A Worker is just a program that sits on a request and returns a response. That can be a complete HTML document, a redirect, a JSON file, or a cached copy of any of those. Search does not care that the program ran at the edge rather than on a VM, as long as the response looks like a normal website: a stable URL, a 200 for a real page, a 404 for a missing one, and the content in the HTML.

The usual ways a Worker site hides itself are not exotic:

If you avoid those, a Worker is a good place to host a content site. You control the headers, the redirects and the body in one place, and you can deploy an article and its sitemap entry together. That is the same discipline I use when I maintain an iOS app after launch, and when I take a build to the App Store: the thing people see and the metadata that describes it should ship in the same release.

Generate HTML on the server, not only in the browser

Crawlers start with the bytes in the HTTP response. Google can also render JavaScript, but rendering is delayed, more expensive, and easy to get wrong; other search engines and many AI tools will not run your front-end at all. The safe default is that the title, the headings, the article text, the internal links and the canonical tag are all present in the raw HTML.

A Worker can produce that HTML in two honest ways:

A single-page app that boots in the browser is the risky option. If you already have one, prerender the public pages at build time, or have the Worker render them, so a client without JavaScript still gets the words. Then check the difference between the raw response and the rendered DOM. The Rendered vs Raw HTML Diff is for that comparison: if the article text only appears after rendering, crawlers that never run your script will not see it.

A few details are easy to miss:

Relative URLs in your own scripts should be resolved against the site origin, not guessed. A small but sharp foot-gun is calling new URL('/path') without a base; in a Worker or a browser that can throw or resolve against the wrong origin. Prefer new URL('/path', 'https://example.com') or, in the browser, new URL('/path', location.origin).

URL rules: host, slash, redirects and canonicals

Pick one hostname, one protocol and one trailing-slash policy, and make every other variant a single redirect to that URL. On this site the canonical host is leej.uk over HTTPS. Whatever you choose, be boring and consistent.

A Worker is a good place to enforce that, because you see the request URL before anything else runs. A typical pattern:

  1. If the protocol is http, redirect 301 to https.
  2. If the host is www.example.com (or the other way around), redirect 301 to the canonical host, preserving the path and query you intend to keep.
  3. If the path is missing or has a surplus trailing slash, redirect 301 to the canonical path.
  4. Only then look up the page.

Do not chain these as three hops if you can avoid it. One redirect is enough, and crawlers will follow only so many. The Location header should be an absolute URL.

The canonical tag should then repeat the URL you redirected to, not the URL the request arrived on. If a tracking query is present, decide whether to strip it in the redirect or leave it on the request and omit it from the canonical. Either way, do not mint a second indexable URL for ?utm_source=….

Preview and developer hostnames are a special case, covered later: they should not redirect to production on their own (that surprises you while you are testing), but they must not be indexed.

Cache HTML carefully

Workers sit in front of a very effective cache. That is useful for a CSS file with a content hash in the name, and dangerous for an HTML document that you might edit this afternoon.

A conservative split:

The Cache-Control Explainer will decode a header before you ship it. Two mistakes I watch for:

If you use the Cache API inside the Worker, put a version key in the cache key and bump it on deploy, or delete the entries you have made stale. “It is at the edge” is not a reason to serve last week’s heading. A service worker in the browser is a separate cache again; don’t debug one by changing the other.

Headers, sitemaps and robots.txt from the same origin

Search and AI tools look for a handful of well-known files on the canonical host, and they look at the headers on each page. Serve them from the same origin as the HTML, in the same release.

Each public page should have:

The site as a whole should have:

The Indexability Checker is a useful pass over a URL: status, canonical, robots and the obvious contradictions. The contradiction I see most on Worker sites is a sitemap entry for a URL that then noindexes, or a robots.txt Disallow that hides a path still listed in the sitemap.

Generate the sitemap from the same list of pages the Worker can actually serve. If a page is added by dropping a file into the release, the sitemap should be built from that list, not from a second, handwritten index that will drift.

Bot fight, WAF and the crawlers you still want in

Cloudflare’s Bot Fight mode, WAF custom rules, rate limits and challenge pages are there to stop abuse. They will also stop a crawler if you are not specific. A blocked Googlebot cannot invent a page from a title tag it never received, and an AI tool that honours a challenge page will see the challenge, not the article.

The principle is: block the traffic you mean to block, and let the rest through with ordinary rate limits. I will not give you a ready-made attack configuration, and I will not pretend a particular product toggle is always safe. What I do on a small content site is narrower:

User-agent tokens and IP lists change. Revisit the rules when you change policy, and after a Cloudflare or crawler announcement, rather than treating a screenshot of the dashboard as permanent.

If a page is public, the first response should be the page. A JavaScript challenge in front of every URL is a decision to leave some crawlers out. That can be a reasonable choice for a private app; it is usually the wrong choice for an article you want found.

Preview hostnames that must stay noindex

Workers give you preview URLs for free: *.workers.dev, environment subdomains, and any preview. host you add yourself. Those URLs are useful, and they are toxic if Google indexes them.

Treat every non-canonical hostname as noindex, nofollow, and keep them out of the sitemap. A Worker can do this by host:

const CANONICAL_HOST = "leej.uk";

function isPreview(url) {
  return url.hostname !== CANONICAL_HOST;
}

function headersFor(url, base) {
  const headers = new Headers(base);
  if (isPreview(url)) {
    headers.set("X-Robots-Tag", "noindex, nofollow");
  }
  return headers;
}

Also:

The same rule applies to the default workers.dev route if the project still has one. If you do not need it, disable the public route. If you need it for yourself, noindex it.

A launch and debug checklist

Before you point a domain at a Worker, and again after a release that changes routing or headers:

If something looks wrong in Search Console, start with the live URL inspection and the raw HTML, not with a rewrite of the whole Worker. Most failures I have had to fix were a stale cache, a preview host, or a missing trailing-slash redirect — the boring parts, not the compute.

Key takeaways

If you want a second pair of eyes on a Worker site, tell me about it.

Frequently asked questions

Can a Cloudflare Worker site rank in Google?

Yes, if it behaves like any other well-made site: real HTML in the first response, stable URLs, a sitemap, and no accidental noindex or challenge page in front of the content. The fact that a Worker produced the response does not, on its own, help or hurt ranking.

Should HTML be rendered in the Worker or in the browser?

Prefer HTML in the first response, whether that is a static file or a document the Worker built. Browser-only rendering makes you depend on every crawler’s JavaScript pipeline. If you already have a client-rendered app, prerender the public pages or render them in the Worker.

How do I set canonicals and redirects on Workers?

Enforce one host, protocol and slash policy with a 301 in the Worker, then put the matching absolute URL in the page’s canonical tag. Avoid redirect chains. Preview hosts should not be canonicalised to production during testing, but they should send noindex.

Does the Cloudflare bot fight clash with SEO crawlers?

It can. Bot Fight, WAF rules and challenge pages will block any client they think is a bot, including ones you wanted to allow. Review the settings, test with documented crawler user-agents, and keep public articles free of a JavaScript wall.

How should a Worker cache HTML without serving stale pages?

Use a short max-age or no-cache with validation for HTML, purge or version the cache on deploy, and don’t store personalised or error responses under a public page’s cache key. Hashed assets can have a long max-age because their URL changes with the file.

Written by Lee Johnson. Published 10 November 2026.

All articles · Swift A–Z · Apps

Questions, or an app in mind?

Let's bring your ideas to life. Whether you're envisioning a game-changing Apple Watch app or a transformative iOS experience, I'm here to make it happen.

Start a project