Problem
webjs computes strong ETags for static/JS/vendor responses (fileResponse dev.js:1379, jsModuleResponse dev.js:1413, vendor.js:1416) but nothing in the server ever reads If-None-Match/If-Modified-Since and returns 304, so the emitted ETag is dead for conditional GET: every CDN/browser revalidation after the max-age window re-downloads byte-identical bytes. Page responses are worse: htmlResponse (ssr.js:172) and streamingHtmlResponse carry no validator at all and no fresh_when/stale? affordance, so a cacheable page (metadata.cacheControl public) always re-renders and re-sends the full body on revalidation.
Design / approach
Read the inbound If-None-Match (a native HTTP conditional-request header) and return an empty 304 when the already-computed ETag matches, for asset/core/vendor/module responses, and add a hashed ETag to non-no-store HTML (suppressed for per-user/streamed responses). Pure HTTP semantics over the existing digestHex.
Web-standards fit: Conditional GET is an HTTP standard; this only reads a standard header and returns the standard 304, reusing the hash already computed.
Prior art: RFC 7232 conditional requests; Rails fresh_when/stale? and etag_with_template_digest; Remix/Express ETag-on-HTML.
Acceptance criteria
Filed from the production-readiness audit (webjs vs Next.js / Remix / Rails / Turbo / Lit). Theme: caching. Priority: P1. Kept to webjs identity: no-build, progressive enhancement, web-components-first, AI-first, batteries-included, close to web standards.
Problem
webjs computes strong ETags for static/JS/vendor responses (fileResponse dev.js:1379, jsModuleResponse dev.js:1413, vendor.js:1416) but nothing in the server ever reads If-None-Match/If-Modified-Since and returns 304, so the emitted ETag is dead for conditional GET: every CDN/browser revalidation after the max-age window re-downloads byte-identical bytes. Page responses are worse: htmlResponse (ssr.js:172) and streamingHtmlResponse carry no validator at all and no fresh_when/stale? affordance, so a cacheable page (metadata.cacheControl public) always re-renders and re-sends the full body on revalidation.
Design / approach
Read the inbound If-None-Match (a native HTTP conditional-request header) and return an empty 304 when the already-computed ETag matches, for asset/core/vendor/module responses, and add a hashed ETag to non-no-store HTML (suppressed for per-user/streamed responses). Pure HTTP semantics over the existing digestHex.
Web-standards fit: Conditional GET is an HTTP standard; this only reads a standard header and returns the standard 304, reusing the hash already computed.
Prior art: RFC 7232 conditional requests; Rails fresh_when/stale? and etag_with_template_digest; Remix/Express ETag-on-HTML.
Acceptance criteria
Filed from the production-readiness audit (webjs vs Next.js / Remix / Rails / Turbo / Lit). Theme: caching. Priority: P1. Kept to webjs identity: no-build, progressive enhancement, web-components-first, AI-first, batteries-included, close to web standards.