Skip to main content

Asset Caching Guide

Learn how Tracks-generated applications handle HTTP caching for optimal performance.

Overview​

Tracks uses content-addressed asset serving via hashfs to enable aggressive browser caching while ensuring users always get the latest assets when content changes.

How It Works​

Content-Addressed URLs​

When you reference assets in your templates:

<link rel="stylesheet" href={ "/assets/" + assets.CSSURL() }/>
<script src={ "/assets/" + assets.JSURL() } defer></script>

The assets.CSSURL() and assets.JSURL() functions return hashed filenames:

  • app.css becomes app.abc123def.css
  • app.js becomes app.xyz789abc.js

The hash is derived from the file content. When you change the CSS or JS, the hash changes, and browsers fetch the new version.

Cache Headers​

Tracks sets appropriate Cache-Control headers based on the URL pattern:

Hashed assets (URLs containing content hash):

Cache-Control: public, max-age=31536000, immutable

This tells browsers and CDNs:

  • public - Can be cached by shared caches (CDNs, proxies)
  • max-age=31536000 - Cache for 1 year (maximum practical duration)
  • immutable - Never revalidate; the content will never change at this URL

Why immutable? Since the hash is derived from content, if the content changes, the URL changes. The browser will never request the old URL again because templates reference the new URL.

ETag Support​

hashfs automatically handles ETags:

  1. Sets ETag header using the content hash
  2. Handles If-None-Match conditional requests
  3. Returns 304 Not Modified when appropriate

This means even non-hashed assets benefit from conditional request support.

Cache Middleware​

The cache middleware (internal/http/middleware/cache.go) detects hashed assets using a regex pattern:

var hashPattern = regexp.MustCompile(`\.[a-f0-9]{8,}\.[^.]+$`)

This matches filenames like:

  • app.abc12345.css
  • htmx.def67890abc.js
  • vendor.1234abcd5678.min.js

Compression​

Tracks automatically compresses assets using gzip. The compression middleware handles:

  • text/html
  • text/css
  • text/javascript
  • application/javascript
  • application/json
  • image/svg+xml

Combined with caching, this means:

  1. First request: Compressed response, cached for 1 year
  2. Subsequent requests: Served from browser cache (no network request)

Browser Behavior​

Normal Navigation​

When users click links or navigate normally, browsers use cached assets without revalidation (thanks to immutable).

Hard Refresh (Ctrl+F5)​

Hard refresh bypasses cache and fetches fresh. The immutable directive doesn't prevent this - it's an explicit user action.

Back/Forward Navigation​

Browser history navigation uses cached content for fast page transitions.

CDN Compatibility​

The cache headers work with all major CDNs:

CDNSupport
CloudflareFull - respects immutable
FastlyFull
CloudFrontFull
Vercel EdgeFull

For CDN deployment, no additional configuration is needed.

Customization​

Adding Custom Assets​

To add custom assets with caching:

  1. Place files in internal/assets/web/
  2. Use assets.AssetURL("path/to/file.ext") in templates
<img src={ "/assets/" + assets.AssetURL("images/logo.png") } alt="Logo"/>

The hash is automatically computed and included in the URL.

Non-Hashed Assets​

Some assets shouldn't use content hashing (e.g., favicon.ico, robots.txt). These:

  • Don't get immutable caching
  • Still benefit from ETag-based conditional requests
  • Should be placed directly in the assets directory

Verifying Cache Behavior​

Test cache headers with curl:

# Check headers for a hashed asset
curl -I http://localhost:8080/assets/css/app.abc123.css

# Expected output includes:
# Cache-Control: public, max-age=31536000, immutable

Or use browser dev tools:

  1. Open Network tab
  2. Load a page
  3. Check the Cache-Control header on asset requests
  4. Refresh and verify assets show "from disk cache"