Skip to content

Apply conditional GET (ETag/304) to pages, assets, and modules #240

@vivek7405

Description

@vivek7405

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

  • A request with a matching If-None-Match returns 304 with no body for asset, core, vendor, and app-module responses whose ETag is already emitted
  • htmlResponse attaches an ETag derived from the body for non-no-store (cacheable) page responses and returns 304 on a matching If-None-Match
  • ETag is suppressed or not validated for no-store/per-user responses so cache hits cannot leak across sessions
  • Streaming (Suspense) responses are exempted or handled correctly (cannot ETag a pre-flush stream)
  • Unit tests assert a 304 on a conditional module re-request, ETag presence on a cacheable page, 304 on matching If-None-Match, and no ETag on a no-store page
  • Tests cover the new behaviour at the applicable layer(s)
  • Docs / AGENTS.md / CONVENTIONS.md updated if the public surface changed

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.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions