# Cache Busting Guide

**Last Updated:** 2026-03-14

## Overview

This guide explains how to properly implement cache busting for CSS and JavaScript files to prevent browser caching issues that cause broken styling.

## Problem

When CSS/JS files are updated but browsers serve cached versions, users see broken styling. Hard refresh (Cmd+Shift+R) fixes it temporarily, but the root cause is missing version parameters on asset URLs.

## Solution

All CSS/JS includes must include version parameters (`?v=timestamp`) that change when files are modified.

## Implementation Pattern

### Standard Pattern

```php
<?php
$css_path = $_SERVER['DOCUMENT_ROOT'] . '/dist/output.min.css';
$css_version = file_exists($css_path) ? filemtime($css_path) : time();
?>
<link rel="stylesheet" href="/dist/output.min.css?v=<?php echo $css_version; ?>">
```

### For Files in v2/ Directory

```php
<link rel="stylesheet" href="/v2/css/tools-pages.min.css?v=<?php echo filemtime(__DIR__ . '/../css/tools-pages.min.css'); ?>">
```

### With Fallback

```php
<?php
$css_file = __DIR__ . '/../css/file.min.css';
$css_version = file_exists($css_file) ? filemtime($css_file) : time();
?>
<link rel="stylesheet" href="/v2/css/file.min.css?v=<?php echo $css_version; ?>">
```

## Critical Files

These files must always be versioned:

- `/dist/output.min.css` - Main Tailwind CSS output
- `/src/critical.css` - Critical above-the-fold CSS
- All files in `/v2/css/` and `/v2/js/`

## Validation

### Before Deployment

Run the audit script:

```bash
python3 v2/scripts/dev-helpers/audit-unversioned-assets.py
```

This will:
- Scan all PHP files in `v2/base/`, `v2/pages/`, `v2/components/`
- Find CSS/JS includes without version parameters
- Report file paths, line numbers, and URLs

### Exit Codes

- `0` - No issues found
- `1` - Issues found (fix before deploying)

## Cache Headers

### Current Strategy

`.htaccess` sets:
```
Cache-Control: public, max-age=31536000, must-revalidate
```

- **Long cache** (1 year) for performance
- **must-revalidate** allows revalidation when query strings change
- Query string changes force fresh fetch

### Why Not `immutable`?

The `immutable` directive was removed because:
- It prevented browsers from revalidating even when query strings changed
- This caused cache busting to fail
- `must-revalidate` allows revalidation while maintaining long cache

## Common Patterns

### Pattern: Include CSS in Head

```php
<?php
$css_path = $_SERVER['DOCUMENT_ROOT'] . '/dist/output.min.css';
$css_version = file_exists($css_path) ? filemtime($css_path) : time();
?>
<link rel="stylesheet" href="/dist/output.min.css?v=<?php echo $css_version; ?>">
```

### Pattern: Include JS Before Closing Body

```php
<?php
$js_path = __DIR__ . '/../js/script.min.js';
$js_version = file_exists($js_path) ? filemtime($js_path) : time();
?>
<script src="/v2/js/script.min.js?v=<?php echo $js_version; ?>"></script>
```

### Pattern: Noscript Fallback

```php
<?php
$css_path = __DIR__ . '/../css/file.min.css';
$css_version = file_exists($css_path) ? filemtime($css_path) : time();
?>
<noscript>
    <link rel="stylesheet" href="/v2/css/file.min.css?v=<?php echo $css_version; ?>">
</noscript>
```

## Testing

### Browser Testing

1. **Make a CSS change** (e.g., change a color)
2. **Load page normally** (soft refresh)
3. **Verify change appears** without hard refresh
4. **Check Network tab** - CSS should load with new `?v=` parameter

### DevTools Verification

1. Open DevTools → Network tab
2. Reload page (normal refresh, not hard refresh)
3. Check CSS/JS files:
   - Should have `?v=timestamp` parameter
   - Should show "200" status (not "304 Not Modified" if version changed)
   - Check "Cache-Control" header: `public, max-age=31536000, must-revalidate`

## Troubleshooting

### Issue: Changes Not Appearing

**Symptoms**: CSS/JS changes don't appear without hard refresh

**Causes**:
1. Missing version parameter on asset URL
2. Version parameter not updating (filemtime() issue)
3. Browser cache not respecting query string changes

**Solutions**:
1. Add version parameter: `?v=<?php echo filemtime(...); ?>`
2. Verify file exists: Use `file_exists()` check
3. Check `.htaccess`: Ensure `must-revalidate` (not `immutable`)

### Issue: Audit Script Finds Issues

**Solution**: Fix all reported files before deploying

**Example Fix**:
```php
// Before (wrong)
<link rel="stylesheet" href="/dist/output.min.css">

// After (correct)
<?php
$css_path = $_SERVER['DOCUMENT_ROOT'] . '/dist/output.min.css';
$css_version = file_exists($css_path) ? filemtime($css_path) : time();
?>
<link rel="stylesheet" href="/dist/output.min.css?v=<?php echo $css_version; ?>">
```

## Related Documentation

- `docs/performance/caching-strategy.md` - Complete caching strategy
- `docs/development/DEV_TOOLING.md` - Development tools including audit script
- `.cursor/rules/performance.mdc` - Performance rules and best practices

## Pre-Deployment Checklist

- [ ] Run `python3 v2/scripts/dev-helpers/audit-unversioned-assets.py`
- [ ] Fix all reported unversioned assets
- [ ] Verify critical files (`/dist/output.min.css`, `/src/critical.css`) are versioned
- [ ] Test in browser (soft refresh should show changes)
- [ ] Check DevTools Network tab for correct cache headers
