# performance Full Instructions

## External Script Loading

### Best Practices

When adding external JavaScript or CSS resources:

1. **Always use async/defer attributes**:

   ```html
   <script src="https://example.com/script.js" async defer></script>
   ```

2. **Add crossorigin="anonymous" for CORS**:

   ```html
   <script src="https://example.com/script.js" crossorigin="anonymous"></script>
   ```

3. **Use Subresource Integrity (SRI) when possible**:

   ```html
   <script
     src="https://cdn.example.com/script.js"
     integrity="sha384-..."
     crossorigin="anonymous"
   ></script>
   ```

4. **Add to Service Worker cache**:
   - Add URL to `EXTERNAL_RESOURCES_TO_CACHE` in `v2/js/service-worker.js`
   - Add URL pattern to `EXTERNAL_PATTERNS` if needed
   - Update cache version when adding new resources

### Critical External Scripts

These scripts are critical and must be loaded on all pages:

- HubSpot: `js-eu1.hs-scripts.com/145133546.js`
- Google Tag Manager: `googletagmanager.com/gtm.js`

### Non-Critical External Scripts

These can be loaded conditionally:

- jsPDF: Only on pages with PDF export
- html2canvas: Only on pages with canvas rendering
- Analytics scripts: Based on user consent

## Cache Headers

### Own Assets

All own JavaScript and CSS files should have proper cache headers via `.htaccess`:

- **Static assets**: `Cache-Control: public, max-age=31536000, immutable`
- **Dynamic content**: `Cache-Control: public, max-age=3600, must-revalidate`

### Versioning

Always use query string versioning for cache busting:

```php
<script src="/v2/js/script.js?v=<?php echo filemtime(__DIR__ . '/../js/script.js'); ?>"></script>
```

### External Resources

External resources cache headers are controlled by third parties. Use service worker for client-side caching.

## Service Worker Maintenance

### Adding New External Resources

1. Add URL to `EXTERNAL_RESOURCES_TO_CACHE` array in `v2/js/service-worker.js`
2. Add URL pattern to `EXTERNAL_PATTERNS` array if needed
3. Update `CACHE_NAME` version (e.g., `v1` → `v2`) to invalidate old cache
4. Test cache behavior in browser DevTools

### Service Worker Updates

- Service worker auto-updates every hour
- New versions activate automatically
- Page reloads when new service worker is ready
- Old cache is cleaned up automatically

## Asset Minification

### Required Minification

**CRITICAL**: All CSS and JavaScript files in `v2/css/` and `v2/js/` MUST be minified before deployment.

**Files that require minification:**

- `v2/css/comparison-pages.css` → `comparison-pages.min.css`
- `v2/css/tools-pages.css` → `tools-pages.min.css`
- `v2/css/templates-pages.css` → `templates-pages.min.css`
- `v2/css/product-pages.css` → `product-pages.min.css`
- `v2/css/testimonials-page.css` → `testimonials-page.min.css`
- `v2/js/utm-tracking.js` → `utm-tracking.min.js`
- `v2/js/shiftops-pdf-generator.js` → `shiftops-pdf-generator.min.js`
- `v2/js/lead-capture-triggers.js` → `lead-capture-triggers.min.js`
- `v2/js/comparison-pages.js` → `comparison-pages.min.js`
- `v2/js/branchen-components.js` → `branchen-components.min.js`
- `v2/js/testimonials-page.js` → `testimonials-page.min.js`

### When to Run Minification

**ALWAYS run minification after:**

1. Editing any CSS file in `v2/css/` (except `.min.css` files)
2. Editing any JavaScript file in `v2/js/` (except `.min.js` files)
3. Before committing changes to CSS/JS files
4. Before deploying to production

**Command:**

```bash
npm run minify
```

### Minification Workflow

1. **Edit source files** (`*.css` or `*.js` in `v2/css/` or `v2/js/`)
2. **Run minification**: `npm run minify` (or `make pre-deploy` for full pre-deploy)
3. **Verify minified files** were created/updated
4. **Commit both** source and minified files

### File References

**ALWAYS reference minified versions in PHP files:**

- Use `.min.css` for CSS files
- Use `.min.js` for JavaScript files
- **CRITICAL**: Use dynamic versioning: `?v=<?php echo filemtime(__DIR__ . '/../css/file.min.css'); ?>`
- **CRITICAL**: All CSS/JS includes MUST have version parameters for cache busting

**Example:**

```php
<link rel="stylesheet" href="/v2/css/comparison-pages.min.css?v=<?php echo filemtime(__DIR__ . '/../css/comparison-pages.min.css'); ?>">
<script src="/v2/js/utm-tracking.min.js?v=<?php echo filemtime(__DIR__ . '/../js/utm-tracking.min.js'); ?>"></script>
```

**Critical Files Pattern** (for files in document root):

```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; ?>">
```

**Cache Busting Requirements:**

- ✅ **REQUIRED**: All CSS/JS includes must have `?v=` version parameter
- ✅ **REQUIRED**: Use `filemtime()` with `file_exists()` check and `time()` fallback
- ✅ **REQUIRED**: Critical files (`/dist/output.min.css`, `/src/critical.css`) must be versioned
- ❌ **NEVER**: Include CSS/JS without version parameters (causes caching issues)
- **Validation**: Run `python3 v2/scripts/dev-helpers/audit-unversioned-assets.py` before deployment

### Adding New Files to Minification

When adding new CSS/JS files that should be minified:

1. **Add to minification script**: Edit `minify-assets.js`

   - Add CSS files to `cssFiles` array
   - Add JS files to `jsFiles` array
   - Order by size (largest impact first)

2. **Update references**: Change all references from `.css`/`.js` to `.min.css`/`.min.js`

3. **Run minification**: `npm run minify`

4. **Verify**: Check that `.min.css`/`.min.js` files are created

### Minification Script

**Location**: `minify-assets.js`

**What it does:**

- Minifies CSS using `cssnano` (via PostCSS)
- Minifies JavaScript using `terser`
- Creates `.min.css` and `.min.js` files
- Reports size reductions

**Expected savings:**

- CSS: 30-50% size reduction
- JavaScript: 20-75% size reduction (varies by file)

## Testing

### Before Deploying

1. **Run minification**: `npm run minify` or `make pre-deploy` (if CSS/JS files were modified)
2. **Run cache busting audit**: `python3 v2/scripts/dev-helpers/audit-unversioned-assets.py` (check for unversioned CSS/JS)
3. Run `scripts/audit-resource-caching.py` to check cache headers
4. Run `scripts/test-cache-headers.py` to verify `.htaccess` configuration
5. Test in browser DevTools:
   - Network tab: Check cache headers
   - Application tab: Verify service worker registration
   - Cache Storage: Inspect cached resources
   - Verify minified files are loading (check file names in Network tab)
   - Verify version parameters (`?v=`) are present on CSS/JS files

### Cache Behavior

- First load: Resources fetched from network
- Subsequent loads: Served from cache (304 responses)
- After 7 days: Cache revalidated in background

## Performance Targets

- **Cache Hit Rate**: > 80% for external scripts
- **Cache Duration**: 7 days for external scripts, 1 year for own assets
- **Service Worker Registration**: < 100ms
- **Cache Storage**: Monitor usage, keep under 50MB

## Troubleshooting

### Service Worker Not Registering

- Check HTTPS is enabled (required for service workers)
- Verify file path is correct (`/v2/js/service-worker.js`)
- Check browser console for errors
- Verify service worker file exists and is accessible

### Resources Not Caching

- Verify URL matches pattern in service worker
- Check CORS headers (use `no-cors` mode if needed)
- Verify service worker is active (check DevTools)
- Check cache storage limits

### Cache Not Updating

- Clear browser cache
- Unregister service worker in DevTools
- Hard refresh page (Ctrl+Shift+R)
- Check cache version in service worker

## Page-Specific Optimizations

### Testimonials Page Patterns

**File:** `v2/pages/static_customers_new.php`

#### Scroll Event Optimization

Always throttle scroll listeners using `requestAnimationFrame` pattern:

```javascript
function scrollProgress() {
  return {
    scrollPos: 0,
    ticking: false,
    init() {
      const handleScroll = () => {
        if (!this.ticking) {
          requestAnimationFrame(() => {
            this.scrollPos = window.scrollY;
            this.ticking = false;
          });
          this.ticking = true;
        }
      };
      window.addEventListener("scroll", handleScroll, { passive: true });
    },
  };
}
```

**Benefits:**

- Reduces scroll event processing by ~90%
- Smoother scrolling performance
- Consistent with other pages in codebase

#### Intersection Observer for Animations

Use Intersection Observer to pause animations when not visible:

```javascript
function initLogoSliderObserver() {
  const logoSlider = document.querySelector(".logo-slider");
  if (!logoSlider) return;

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          entry.target.style.animationPlayState = "running";
        } else {
          entry.target.style.animationPlayState = "paused";
        }
      });
    },
    { rootMargin: "50px", threshold: 0.1 }
  );

  observer.observe(logoSlider);
}
```

**Benefits:**

- Reduces CPU/GPU usage when animations are off-screen
- Better battery life on mobile devices
- Improved overall page performance

#### CSS Containment

Apply CSS containment to improve rendering performance:

```css
.testimonials-grid {
  contain: layout style;
}

.testimonial-card {
  contain: layout style paint;
}

.testimonial-qa-content {
  contain: layout style paint;
}
```

**Benefits:**

- Isolates layout calculations
- Reduces reflow/repaint scope
- Improves rendering performance

#### Image Format Optimization

Always use WebP format for images when available:

```html
<!-- Before -->
<img src="/html/images/ref/logo.png" alt="Logo" />

<!-- After -->
<img
  src="/html/images/ref/logo.webp"
  alt="Logo"
  loading="lazy"
  decoding="async"
/>
```

**Benefits:**

- 30-50% smaller file sizes
- Faster page loads
- Better Core Web Vitals scores

#### LCP Image Optimization

For above-fold images (LCP candidates), use eager loading:

```html
<img
  src="/html/images/hero.webp"
  alt="Hero image"
  loading="eager"
  fetchpriority="high"
  decoding="async"
  width="1280"
  height="720"
/>
```

**Benefits:**

- Faster Largest Contentful Paint (LCP)
- Better Core Web Vitals scores
- Improved SEO rankings

#### Will-Change Optimization

Only apply `will-change` during active animations:

```css
/* Apply only during transitions */
.testimonial-qa-content[x-transition] {
  will-change: max-height, opacity;
}

/* Remove after transition */
.testimonial-qa-content:not([x-transition]) {
  will-change: auto;
}
```

**Benefits:**

- Reduced GPU memory usage
- Better browser optimization
- Prevents unnecessary compositing layers

## Documentation

- Full documentation: `docs/performance/caching-strategy.md`
- Developer guide: `docs/guides/EXTERNAL_SCRIPTS_CACHING.md`
- **Minification guide**: `docs/guides/ASSET_MINIFICATION.md`
- **Testimonials page optimization**: `docs/performance/testimonials-page-optimization-report.md`
- Service worker code: `v2/js/service-worker.js`
- Registration code: `v2/base/head.php` (lines 108-148)
- Minification script: `minify-assets.js`

## References

- [Service Worker API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
- [Cache API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Cache)
- [HTTP Caching - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)
