Skip to content

Content-hash asset URLs for immutable cache, add preconnect hints #243

Description

@vivek7405

Problem

Every served app module (.js/.ts) and public/ asset is capped at public, max-age=3600 because its URL is un-versioned (fileResponse/jsModuleResponse/tsResponse in dev.js). The framework already computes a build fingerprint (data-webjs-build/X-Webjs-Build) for reload detection but never weaves it into asset URLs, so a returning user revalidates dozens of module/asset requests hourly. The dev.js:871 comment explicitly states immutable is unsafe because URLs are un-versioned, citing a real regression after a core version bump. A second asset-delivery gap is connection setup: the metadata API can declare preload but not preconnect or dns-prefetch (ssr.js:967), so an unpinned app fetching every vendor package cross-origin from ga.jspm.io pays full DNS+TLS+TCP setup serially after the importmap resolves, with no first-class hint for fonts/CDN/analytics origins either.

Design / approach

At serve time, append a ?v= query (from the existing build id or a per-file hash) to module/public URLs in the importmap, modulepreload, and href/src, and serve those with the standard Cache-Control: immutable. Add native / from metadata, auto-emitting one to the resolved vendor origin. No bundler, just URL rewriting and standard link hints.

Web-standards fit: Versioning is a query string the browser caches natively; preconnect is a standard link rel. The framework rewrites URLs and emits link tags, with no build step.

Prior art: Next.js content-hashed _next/ chunks with max-age=31536000, immutable; Rails Sprockets/Propshaft digested paths; Vite [hash] filenames.

Acceptance criteria

  • A prod serve mode maps each servable module and public/ asset to a fingerprinted URL, with importmap, modulepreload, and href/src rewritten to the fingerprinted form
  • Fingerprinted responses carry Cache-Control: public, max-age=31536000, immutable; un-fingerprinted fallbacks keep the 1h max-age
  • A deploy changing module bytes changes the URL so the stale-immutable regression at dev.js:871 cannot recur (test re-serves after a simulated version bump)
  • The mechanism is no-build-compatible (serve-time from the build id or per-file digest) and respects webjs.elide plus vendor-pin invariants
  • Dev mode stays no-cache
  • metadata accepts preconnect and dnsPrefetch rendered as link rel hints, and the framework auto-emits a crossorigin preconnect to the resolved vendor CDN origin for an unpinned app (deduped; a same-origin pinned app emits none)
  • 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: perf-assets. 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

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions