Skip to content

perf(seo): send ETags on edge cached pages and answer 304 - #636

Merged
harlan-zw merged 1 commit into
mainfrom
perf/html-validators
Oct 13, 2026
Merged

harlan-zw merged 1 commit into
mainfrom
perf/html-validators

Conversation

@harlan-zw

Copy link
Copy Markdown
Collaborator

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

skilld's HTML carried no ETag and no Last-Modified, so every recrawl downloaded every page again. The Nuxt SEO crawler recorded no unchanged skilld page in seven days, against about 60% on the other sites it crawls.

A page under an edgeCache rule now gets a weak ETag: W/"<build id>-<hash of the rendered HTML>". A deploy or any change to the HTML gives a new ETag. The hash leaves out Sentry's sentry-trace and baggage meta tags, because they carry a new trace id on every render. A request that sends the ETag back in If-None-Match gets an empty 304. The 304 keeps the route's cache headers, so a Workers Cache entry that revalidates stays fresh. The Worker still renders the page on a miss or a revalidation, because the hash needs the HTML.

No ETag goes on:

  • a request with a cookie or an Authorization header
  • a page that sets a cookie
  • a page whose cloudflare-cdn-cache-control lets no shared cache store it

That leaves /, Skill pages with their repository and owner hubs, /skills/trending, /skills/demos with its demo pages, /cli and /developers. Browsers still get cache-control: private, no-store. Google does not document whether Googlebot keeps an ETag from a no-store response.

No page sends Last-Modified. A Skill page shows stars, ages and related Skills, so no one date bounds what it renders. If that date missed a change, a cache would answer If-Modified-Since with a wrong 304. A request that sends only If-Modified-Since still gets the whole page.

The homepage keeps its render time in the payload (render:now), so each render of / has its own ETag, and the Worker never answers 304 for /.

pnpm production:edge-cache now checks two more things on each page it covers after a deploy:

  • The stored page carries an ETag, and a request that sends it back gets a 304.
  • After a conditional request on a cold entry, the next plain request still gets the whole page. A stored 304 would serve an empty page to every later visitor.

The Workers Cache docs say the cache revalidates with the Worker through If-None-Match. They do not say how the cache answers a client's conditional request. These two checks are the first production proof of that path.

Architecture: a crawler's conditional GET reaches Workers Cache; on a miss the Nuxt renderer calls the new html-validators plugin, which asks decideHtmlValidators and answers with an ETag or an empty 304

🤖 AI disclosure: Harlan Agent Kit modified this description. My AI open-source policy.

Edge cached HTML carried no validator, so every recrawl downloaded every
page again. The Nuxt SEO crawler saw no unchanged page in seven days, and
Workers Cache replaced each stale page with a full render.

A page under an edgeCache rule now gets a weak ETag from the build id and
a hash of its HTML. Sentry's per-render trace meta tags are left out of the
hash. A request that sends the ETag back gets an empty 304 that keeps the
route's cache headers. Requests with credentials and pages the edge may
not store get no ETag.

No page sends Last-Modified. A Skill page shows stars, ages and related
Skills, so no one date bounds it, and a cache would answer
If-Modified-Since with a wrong 304.

The post-deploy edge cache check now proves the 304, and proves that a
conditional request on a cold entry never leaves a 304 for the next visitor.
@harlan-zw harlan-zw added the harlan-agent-review Pull request triage requires an adversarial Review. Adding this label manually always forces one. label Oct 13, 2026
@harlan-github-agent harlan-github-agent Bot added harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. harlan-agent-running An Agent holds a Task on this issue or pull request right now. and removed harlan-agent-review Pull request triage requires an adversarial Review. Adding this label manually always forces one. labels Oct 13, 2026
@harlan-github-agent

harlan-github-agent Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

🤖 MERGED

Harlan Agent Kit posted this automated review. It is not Harlan's personal review or approval. AI open source policy. Last updated: 2026-10-13 09:49 UTC.

GitHub merged this pull request.

No material findings were recorded.

The pull request closed.

@harlan-github-agent harlan-github-agent Bot added harlan-agent-running An Agent holds a Task on this issue or pull request right now. and removed harlan-agent-running An Agent holds a Task on this issue or pull request right now. labels Oct 13, 2026
@harlan-zw
harlan-zw merged commit deec8d4 into main Oct 13, 2026
8 checks passed
@harlan-github-agent harlan-github-agent Bot removed the harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. label Oct 13, 2026
harlan-zw added a commit that referenced this pull request Oct 13, 2026
Every deploy since #636 failed the edge cache check with `no-etag`. The
Worker sends the ETag, but Cloudflare removes it from an HTML 200 while a
zone feature that rewrites HTML is on, such as Email Obfuscation or
Automatic HTTPS Rewrites. A 304 has no body, so it keeps the ETag.

The check now asks for a 304 with `If-None-Match: *` when the page has no
ETag, reads the ETag there, and runs the 304 and stored-page checks with
it. A stripped ETag becomes a warning and a GitHub annotation. The deploy
still fails when no response carries an ETag, or when a 304 replaces the
stored page.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant