Table of contents
- Quick summary
- What a wasted crawl looks like from the server side
- How the 195 probes were run
- Result one: only 51.8% could say nothing changed
- Result two: the files agents re-read most often are the least protected
- Result three: 63 MB of re-crawl became 14 MB
- Thirteen sites answer correctly everywhere, sixteen answer nowhere
- What the crawler operators actually document about caching
- Our own log, and why it does not let us off the hook
- Where AIScan fits, and where it doesn't
- Fixing it on WordPress, Shopify and the static stacks
- Branch on what your own headers said
- Ten minutes, four commands
- The next thing to run
Verified 5 September 2026. All measurements in this article were taken on that date.
Every AI crawler that visits your site asks the same handful of questions over and over. Where is your robots.txt. Has your sitemap moved. Is there an llms.txt yet. Those files change perhaps once a month. The crawlers check them daily, and on most sites they get the entire file back every single time, because the server never told them how to ask "has this changed?"
We probed four discovery surfaces on 60 well-known sites on 5 September 2026: the homepage, /robots.txt, /sitemap.xml and /llms.txt. Of the 195 responses that came back 200, only 101 answered a conditional request with a 304. The other 94 re-sent the whole thing.

Quick summary
| If you want to | Do this | What it costs | What it saves |
|---|---|---|---|
| Stop AI crawlers re-downloading unchanged files | Emit an ETag on every static file and honour If-None-Match | One CDN setting on most stacks | 77.5% of re-crawl bytes in our sample |
| Find out whether you already do | curl -sI https://yoursite.com/robots.txt | grep -i 'etag|last-modified' | 5 seconds | Tells you which half you are in |
| Check the rest of your discovery layer | Run a free scan at AIScan, covering D1, D2, C2 and E1 | No account needed | The surfaces crawlers ask for |
| Fix it on WordPress | ThinkRank for the files, plus a caching layer for the headers | One plugin | Consistent headers across robots.txt, sitemap and llms.txt |
According to Google's Search Central blog, published 9 December 2024, the share of Googlebot fetches that could be served from cache has been falling for a decade: "10 years ago about 0.026% of the total fetches were cacheable, which is already not that impressive; today that number is 0.017%."
The finding: 68.2% of the responses we measured shipped a cache validator, but only 51.8% honoured one. The gap between those two numbers is servers that advertise a validator and then ignore it.
What this does not claim: a 304 does not reduce the number of requests a crawler makes. It reduces what each one costs.
What a wasted crawl looks like from the server side
HTTP has had an answer to this since long before AI crawlers existed. RFC 9110, the current HTTP Semantics standard, says in its own words at Section 13 that "conditional GET requests are the most efficient mechanism for HTTP cache updates."
The mechanism has two halves. On the first fetch, your server sends an ETag (an opaque version string) or a Last-Modified date. On the next fetch, a well-behaved client sends that value back in If-None-Match or If-Modified-Since. If nothing changed, the server replies 304 Not Modified with no body at all.
RFC 9110 Section 15.4.5 defines what that means: "there is no need for the server to transfer a representation of the target resource because the request indicates that the client, which made the request conditional, already has a valid representation."
Both halves have to work. A server that sends no validator gives the client nothing to send back. A server that sends one and then answers 200 anyway has wasted the round trip. In our sample, both failure modes are common, and the second one is more common than most people expect.
Google's post is unusually direct about which validator to prefer, stating verbatim that "Google's crawling infrastructure supports heuristic HTTP caching as defined by the HTTP caching standard, specifically through the ETag response- and If-None-Match request header, and the Last-Modified response- and If-Modified-Since request header. We strongly recommend using ETag because it's less prone to errors and mistakes (the value is not structured unlike the Last-Modified value). And, if you have the option, set them both."
How the 195 probes were run
The sample is 60 public sites chosen to span news, documentation, developer platforms, ecommerce storefronts and site builders. It is not random and it is not the whole web. It is deliberately weighted toward sites that AI crawlers actually fetch a lot, which makes it a generous sample rather than a harsh one.
For each site, four URLs were requested twice with a browser user agent:
- A plain
GET, recording the status, the byte count, and anyETagorLast-Modifiedin the response. - A second
GETcarrying whichever validators came back, inIf-None-MatchandIf-Modified-Since.
Both passes ran within the same minute, so a 200 on the second pass means the server did not honour the condition rather than that the content genuinely changed. Redirects were followed. Anything that did not return 200 on the first pass was excluded from the percentages, which removes four homepages behind bot protection, thirteen sites with no sitemap at the conventional path, and the twenty-five sites that publish no llms.txt.
Three limits worth stating. The sample is 60 sites, so a percentage carries roughly plus or minus six points at this size. A single origin can behave differently for a crawler user agent than for a browser one. And a CDN edge node can answer differently from another edge node, so a 200 on the second pass is evidence rather than proof.
Result one: only 51.8% could say nothing changed
| Surface | Returned 200 | Shipped a validator | Answered 304 | Honoured it, of those that could |
|---|---|---|---|---|
/robots.txt | 57 of 60 | 75.4% | 59.6% | 79.1% |
/llms.txt | 35 of 60 | 74.3% | 60.0% | 80.8% |
| Homepage | 56 of 60 | 67.9% | 48.2% | 71.1% |
/sitemap.xml | 47 of 60 | 55.3% | 40.4% | 73.1% |
| All four | 195 of 240 | 68.2% | 51.8% | 75.9% |
The homepage number is the one that surprised us. Eighteen of the 56 homepages that answered 200 carried neither an ETag nor a Last-Modified, including stripe.com, docs.stripe.com, nextjs.org, wordpress.org, notion.so, slack.com and linear.app. A crawler visiting any of those has no way to ask whether anything moved. It can only download.
Eleven more shipped a validator and then answered 200 to a request carrying it. That set includes developer.chrome.com and web.dev, both operated by Google, and www.cloudflare.com, whose own product line is built on making crawl traffic cheaper.
Result two: the files agents re-read most often are the least protected
Sitemaps are the worst-performing surface in the sample and also the largest. The 47 sitemaps that answered 200 came to 24.6 MB on the first pass, and 21 of them ship no validator at all.
That combination matters more than the homepage result, because a sitemap is the file a crawler is most entitled to poll frequently. It exists to tell a crawler what changed. The sitemaps.org protocol says in its own words that a sitemap "provides hints for web crawlers to do a better job of crawling your site", and a hint file that costs half a megabyte to re-read is working against itself.
llms.txt performs better than the homepage, at 60.0%, which is a genuinely encouraging number for a file that did not exist two years ago. The reason is mostly accidental: llms.txt is usually a static file on a CDN, and static files get validators for free. The exceptions are instructive. stripe.com/llms.txt is 65,026 bytes and ships no validator. docs.stripe.com/llms.txt is 90,052 bytes and ships none either. mailchimp.com/llms.txt is 524,818 bytes, ships a validator, and answers 200 to a conditional request anyway.
The best result in the whole sweep also came from this surface. www.twilio.com/llms.txt is 2,330,360 bytes, the largest single file we measured, and it answers 304 correctly. Every conditional re-fetch of that file transfers nothing.
Result three: 63 MB of re-crawl became 14 MB
Adding up both passes across all 195 successful responses:
| Pass | Bytes transferred |
|---|---|
| First fetch (cold) | 62,986,998 |
| Second fetch (conditional) | 14,149,596 |
| Difference | 48,837,402 (77.5%) |
One conditional re-crawl of 60 sites moved 77.5% fewer bytes than a cold one. On the homepage surface alone the figure was 34,869,193 down to 9,085,624, a 73.9% reduction.
Multiply that by the number of AI crawlers now in the field. Our own inventory of AI crawler user agents documents 33 operator-published crawler tokens, and five operators now run a three-way split between training, search indexing and live user-triggered fetching. Each of those is a separate client with its own cache, asking the same questions of the same files.
The honest caveat: a 304 still costs a TCP connection, a TLS handshake and a request. It does not stop a crawler visiting. What it removes is the payload, the origin compute that generated the payload, and in most cases the cache-fill work behind it.
Thirteen sites answer correctly everywhere, sixteen answer nowhere
Of the sites that served at least three of the four surfaces, thirteen returned 304 on every one: developer.mozilla.org, docs.python.org, docs.docker.com, kubernetes.io, about.gitlab.com, ghost.org, netlify.com, svelte.dev, vuejs.org, webflow.com, www.digitalocean.com, www.framer.com and llmstxt.org.
Sixteen returned 304 on none of them: developer.chrome.com, web.dev, docs.stripe.com, stripe.com, docusaurus.io, sentry.io, slack.com, techcrunch.com, wordpress.org, www.bbc.com, www.chubbiesshorts.com, www.cloudflare.com, www.elastic.co, www.mejuri.com, www.postgresql.org and www.shopify.com.
The split does not track company size or technical sophistication. It tracks how the site is served. The all-correct group is dominated by documentation sites and static hosts, where the origin is a file server and validators come from the filesystem. The all-wrong group is dominated by application frameworks rendering pages per request, where a validator has to be computed deliberately and usually is not.
Framer's presence in the correct column is consistent with what we measured on that platform previously. Every optimised Framer page also serves a real text/markdown twin under Accept: text/markdown, verified on 4 September 2026. Platforms that get one part of the machine-readable layer right tend to get the others right too.
What the crawler operators actually document about caching
Google documents this thoroughly. Nobody else does.
| Operator | Cache validators in its crawler documentation | What it does document |
|---|---|---|
ETag, If-None-Match, Last-Modified, If-Modified-Since, worked request/response examples | Full conditional-request support, with a stated preference for ETag | |
| OpenAI | Zero mentions across the served page | robots.txt directives, per-bot independence, IP ranges |
| Anthropic | Zero mentions across the served page | robots.txt, Crawl-delay, IP-block warning |
| Perplexity | Zero mentions across the served page | PerplexityBot and Perplexity-User separation |
Those three zero counts were taken on 5 September 2026 by fetching each operator's crawler page and searching the served HTML for ETag, Last-Modified, If-None-Match and If-Modified-Since. Body text was present in each case, so the absence is real rather than a rendering artifact. What Anthropic's page does say, in its own words, is that "we aim for minimal disruption by being thoughtful about how quickly we crawl the same domains and respecting Crawl-delay where appropriate."
That is a rate-limiting answer to a redundancy problem. Crawl-delay slows a crawler down. A 304 makes each visit nearly free. They are not substitutes, and only one of them is in the HTTP standard.
The practical consequence: an operator that does not document conditional-request support may still send one, because most crawler frameworks do it by default. But you cannot rely on the documentation to tell you. The only way to know is to look at your own logs for If-None-Match and If-Modified-Since headers and count how many of your responses were 304.
Our own log, and why it does not let us off the hook
We log AI crawler hits to aiscan.site and we pulled the whole table for this article. Between 25 August and 4 September 2026 it holds 120 requests from 9 distinct bot names, and every one of them returned HTTP 200. Not a single 304.
Aggregate only. No individual scanned URL or visitor is named, which is the same privacy rule we adopted for our 473-site readiness study.
The breakdown is less dramatic than the headline suggests, and reporting it honestly matters more than reporting it loudly:
| Request type | Hits | Distinct paths | Distinct bots |
|---|---|---|---|
MCP endpoint calls (/api/mcp, /mcp) | 70 | 2 | 1 |
| Scan report pages | 42 | 41 | 7 |
| Other API paths | 5 | 1 | 1 |
| Content pages | 3 | 3 | 1 |
Fifty-eight percent of the traffic is one agent calling our MCP server, which is a tool invocation rather than a crawl, and correctly returns fresh data every time. The 42 content fetches spread across 41 distinct paths, so at our current scale AI crawlers are barely repeating themselves at all. Our own log does not demonstrate crawl waste.
What it does demonstrate is the other half of the problem. We checked our own headers on 5 September 2026:
| URL | Size | ETag | Last-Modified |
|---|---|---|---|
aiscan.site/ | 245,447 B | absent | absent |
aiscan.site/robots.txt | 1,192 B | absent | absent |
aiscan.site/llms.txt | 20,020 B | absent | absent |
aiscan.site/sitemap-pages.xml | 21,566 B | absent | absent |
None of those four can ever return a 304, because none of them offers anything to condition on. We score 100 on our own rubric and we are in the failing half of our own study. That is on the fix list.
One more result we are reporting with a caveat rather than a headline: our ai_referral_visits table, which is meant to record visits arriving from AI assistants, holds zero rows for the same period. An untested counter reading zero is not evidence of zero referrals, so we are treating it as an instrumentation question until it is verified.
Where AIScan fits, and where it doesn't
The fastest way to check the discovery layer this article is about is one command:
npx aiscan-cli yoursite.com
Or paste the URL at aiscan.site. Free, no account required. The checks that matter here:
| Check | What it grades | Relevant to this article |
|---|---|---|
| D1 robots.txt present and sane | Whether the file exists and parses | The most-refetched file on your site |
| D2 XML sitemap | Whether a sitemap is declared and reachable | The largest of the four surfaces |
| C2 /llms.txt | Whether the file exists and is valid Markdown | Increasingly polled, often large |
| E1 Correct 404 handling | Whether missing paths hard-404 | A soft 404 makes every probe look successful |
AIScan does not grade cache validators at all. We ran the full rubric against our corpus of 484 sites on rubric version 2026.08.2 on 5 September 2026: all 21 checks are listed, and not one of them looks at ETag, Last-Modified or 304 behaviour. Neither does Cloudflare's 22-check agent-readiness rubric. If you want this measured today you have to measure it yourself, which is why the commands below are complete without us.
If you would rather check by hand, two commands answer it for any URL:
# 1. Does the server offer a validator?
curl -sI https://yoursite.com/robots.txt | grep -i 'etag\|last-modified'
# 2. Does it honour one? Paste the ETag value from step 1.
curl -s -o /dev/null -w '%{http_code}\n' \
-H 'If-None-Match: "PASTE-ETAG-HERE"' \
https://yoursite.com/robots.txt
# 304 = correct. 200 = the validator is decorative.
Fixing it on WordPress, Shopify and the static stacks
WordPress. ThinkRank is the sensible starting point, because every file this article measures comes out of a single plugin: robots.txt, robots meta, the XML sitemap, llms.txt and schema, rather than three plugins each claiming ownership of the same robots.txt. Switching is cheap too, since it imports what you already configured in Rank Math, Yoast, AIOSEO or SEOPress. Generating the files consistently is the half that a plugin controls. The headers are the other half and belong to your host or CDN: WordPress serves a static robots.txt and sitemap.xml with filesystem validators automatically, but a plugin-generated virtual file often ships neither, so verify after you switch. Rank Math and Yoast both handle sitemaps well and have larger template libraries; neither consolidates the AEO and GEO files the way ThinkRank does. More detail on the platform is on our WordPress guides page.
Shopify. You do not control the storefront's response headers, so the validator half is not yours to fix. The file half is, and StoreSEO is the natural choice: it generates llms.txt from products, collections, pages and articles, has an agents.md editor, and covers the schema and AEO surfaces Shopify's own rollout leaves alone. It is rated 5.0 from 735 reviews on the Shopify App Store as of 5 September 2026, with the Built for Shopify badge. Yoast SEO for Shopify and Avada are reasonable alternatives if schema is your only concern; neither generates llms.txt today. Our Shopify platform page collects the store-specific guides.
Next.js and other frameworks. Route handlers return whatever headers you set and nothing more. A route.ts that generates robots.txt or llms.txt should hash its own output and return ETag, then compare against If-None-Match and return 304. Static files under public/ usually get validators from the host, which is why moving a generated file to a build artifact often fixes this by accident. Our Next.js platform page has the framework-specific notes.
Static hosts and CDNs. Cloudflare, Netlify, Vercel and Fastly all emit ETag on static assets by default. If your static file is not returning 304, the usual cause is a rewrite or a function intercepting the path. Check whether the file is genuinely static before changing any settings.
If llms.txt is the file you are fixing, our llms.txt generator writes a spec-shaped file, and the eleven mistakes we found across 39 real files covers what usually goes wrong with the content itself.
Branch on what your own headers said
| What you found | What it means | What to do |
|---|---|---|
No ETag, no Last-Modified | Crawlers cannot ask. Every visit is a full download. | Enable ETag at the CDN or host. Cheapest fix on this list. |
| Validator present, conditional request returns 200 | The header is decorative. A rewrite or function is likely intercepting. | Trace the path. Static files rarely do this; generated routes usually do. |
| Validator present, returns 304 | Working correctly. | Check the other three surfaces. Most sites are inconsistent across them. |
| Homepage 304s but sitemap does not | The common pattern in our sample. | Sitemaps are the largest files and the worst performers. Fix that one first. |
/llms.txt returns 200 to a conditional request and is over 100 KB | Every poll re-sends the whole file. | Serve it as a static asset rather than from a route handler. |
Ten minutes, four commands
- Check
/robots.txtfor a validator, then send it back and read the status code. - Repeat for
/sitemap.xml. In our sample this is the surface most likely to fail. - Repeat for
/llms.txtif you publish one, and note its size while you are there. - Repeat for the homepage. Expect this one to fail on any framework rendering per request.
- Grep your access log for
If-None-MatchandIf-Modified-Since, then count what share of your responses to those requests were 304. - Run a scan for the rest of the discovery layer and read D1, D2, C2 and E1.
The next thing to run
Start with the file that costs the most to re-send. In this sample that is almost always the sitemap, and on documentation-heavy sites it is llms.txt. Two curl commands tell you which half of the 51.8% you are in, and on most stacks the fix is a CDN toggle rather than a code change.
Then run a scan at AIScan and read the four checks this article is about: D1 robots.txt, D2 XML sitemap, C2 llms.txt and E1 correct 404 handling. Those four are on the discoverability and content dimension pages, with the fix instructions for each. The rest of the platform walkthroughs are in our guides.
And if your llms.txt turns out to be the expensive one, the six discovery surfaces and what each is actually for explains which files a crawler is entitled to poll and how often.
Frequently asked questions
I added an ETag and my server still returns 200 to a conditional request. What is wrong?
Something between the client and your file is regenerating the response. The usual causes are a rewrite rule, a serverless function or a route handler answering the path instead of the static file, or a CDN that strips If-None-Match before it reaches the origin. Test the origin directly, bypassing the CDN. If the origin returns 304 and the edge returns 200, the edge is dropping the request header. If the origin also returns 200, your application is not comparing the incoming validator against the one it generates.
Does returning 304 make AI crawlers visit my site less often?
No, and that is worth being clear about. A 304 does not reduce the number of requests. It removes the response body, the origin compute that would have generated it, and the bandwidth. A crawler that fetched your sitemap daily will still fetch it daily; each fetch just costs a few hundred bytes instead of half a megabyte. If you want fewer visits, Crawl-delay and robots.txt rules are the levers, and only some operators honour the first one.
My sitemap returns no ETag and I cannot change the server. What now?
Check whether the sitemap is genuinely static first. In our 5 September 2026 sample, 21 of the 47 sitemaps that answered 200 shipped no validator, and nearly all of them were generated per request by an application rather than served from disk. If you can write the sitemap to a static file at build time and serve it from the same host as your other assets, most CDNs will add an ETag automatically. If the sitemap has to stay dynamic, adding a Last-Modified header based on your most recent content change is usually a smaller code change than computing a content hash.
Should I use ETag or Last-Modified?
Google's Search Central blog recommends ETag, stating verbatim that it is "less prone to errors and mistakes (the value is not structured unlike the Last-Modified value)", and adds that if you have the option you should set both. Last-Modified has a strict date format and a one-second resolution, so two edits in the same second are indistinguishable. ETag is an opaque string, usually a hash, with no such limits.
AI crawlers are overwhelming my origin. Will conditional requests fix that?
Partly. They remove the expensive half of each request, which for a large sitemap or llms.txt is nearly all of it. They do not remove the connection, the TLS handshake or the request itself, so a crawler making thousands of requests per minute will still cost you connections. Conditional requests are the right first move because they are cheap and standards-based. Rate limiting and explicit robots.txt rules are the second move, and blocking is the third.
Do OpenAI, Anthropic and Perplexity crawlers actually send conditional requests?
Their published documentation does not say. On 5 September 2026 we fetched each operator's crawler page and searched the served HTML for ETag, Last-Modified, If-None-Match and If-Modified-Since. All three returned zero matches, while Google's crawling documentation covers all four headers with worked request and response examples. Most crawler frameworks send conditional requests by default, so the absence of documentation is not evidence that they do not. The only reliable answer comes from your own access log: grep it for If-None-Match and count how many of your responses were 304.
curl shows my llms.txt returning 304, but my logs still show full downloads. Why?
Different clients hold different cached validators, and a client that has never fetched the file has nothing to condition on. The first fetch by any new crawler is always a full download, and with several operators now running separate training, search and live-fetch bots, you have more independent first fetches than you used to. Check whether the full downloads carry an If-None-Match header at all. If they do not, the crawler is not conditioning the request, and no server change will help.
Does AIScan check any of this?
Not today. We ran the full rubric across 484 sites on rubric version 2026.08.2 on 5 September 2026, and none of the 21 checks looks at ETag, Last-Modified or 304 behaviour. Cloudflare's 22-check agent-readiness rubric has no equivalent either. What AIScan does grade is whether the files exist and are reachable: D1 for robots.txt, D2 for the XML sitemap, C2 for llms.txt and E1 for correct 404 handling. The two curl commands in this article cover the caching half until a check exists.
Related guides
The complete AI readiness setup for Squarespace in 2026
Squarespace hands you a finished website and a finished discovery layer at the same time. The robots.txt is written for you, the sitemap is generated for you, and the RSS feed already exists and is…
How to publish a valid llms.txt on Docusaurus
A Docusaurus site can publish a completely valid llms.txt and still score zero on it. The reason is one config value. On GitHub Pages, the platform's most common deployment target, a project site…
The complete AI readiness setup for Framer in 2026
A brandnew Framer site scores better on an agent readiness scan than a new site on almost any other hosted builder, and that is exactly what makes the remaining gaps hard to see. Framer prerenders…
llms.txt vs robots.txt vs sitemap.xml in 2026: Six Files, Six Jobs
Verified 4 September 2026. Every figure below was measured or fetched on that date. Three files keep getting compared as if they were competing for the same job: robots.txt, sitemap.xml and llms.txt.…
