# External Scripts Caching Guide


**Last Updated:** 2025-11-20

## Overview

This guide explains how to add new external JavaScript and CSS resources to the Ordio website while ensuring proper caching and performance optimization.

## Quick Start

When adding a new external script:

1. Add the script tag with proper attributes
2. Add URL to service worker cache
3. Test cache behavior
4. Document the addition

## Step-by-Step Guide

### 1. Add Script Tag

Add the script tag to the appropriate file (usually `v2/base/head.php` or `v2/base/footer.php`):

```html
<script
  src="https://example.com/script.js"
  async
  defer
  crossorigin="anonymous"
  onload="console.log('Script loaded')"
></script>
```

**Required Attributes**:

- `async`: Loads script asynchronously (non-blocking)
- `defer`: Defers script execution until DOM is ready
- `crossorigin="anonymous"`: Enables CORS for cross-origin requests

**Optional Attributes**:

- `integrity`: Subresource Integrity hash (if available)
- `onload`: Callback when script loads

### 2. Add to Service Worker Cache

Edit `v2/js/service-worker.js`:

#### Add to Resource List

Add the URL to `EXTERNAL_RESOURCES_TO_CACHE` array:

```javascript
const EXTERNAL_RESOURCES_TO_CACHE = [
  // ... existing resources ...
  "https://example.com/script.js", // Add your script here
];
```

#### Add URL Pattern (if needed)

If the script URL follows a pattern (e.g., versioned URLs), add a regex pattern to `EXTERNAL_PATTERNS`:

```javascript
const EXTERNAL_PATTERNS = [
  // ... existing patterns ...
  /^https:\/\/example\.com\/.*\.js$/, // Add your pattern here
];
```

#### Update Cache Version

Update the cache name version to invalidate old cache:

```javascript
const CACHE_NAME = "ordio-external-resources-v2"; // Increment version
```

### 3. Test Cache Behavior

#### Using Browser DevTools

1. Open Chrome DevTools (F12)
2. Go to **Application** tab
3. Check **Service Workers** section:
   - Verify service worker is registered
   - Check status is "activated and running"
4. Go to **Cache Storage** section:
   - Expand `ordio-external-resources-v1`
   - Verify your script URL is cached
5. Go to **Network** tab:
   - Reload page
   - Check your script request:
     - First load: Should show "200" or "304" status
     - Subsequent loads: Should show "(from ServiceWorker)" or "(from disk cache)"

#### Using Audit Scripts

Run the audit script to check cache headers:

```bash
python3 scripts/audit-resource-caching.py
```

This will check if the external resource has proper cache headers (though we can't control them).

### 4. Document the Addition

Update relevant documentation:

- Add to `docs/performance/caching-strategy.md` if it's a critical resource
- Update this guide if it's a common pattern
- Add comments in code explaining why the script is needed

## Examples

### Example 1: Analytics Script

```html
<!-- In footer.php -->
<script
  src="https://analytics.example.com/tracker.js"
  async
  defer
  crossorigin="anonymous"
></script>
```

```javascript
// In service-worker.js
const EXTERNAL_RESOURCES_TO_CACHE = [
  // ... existing ...
  "https://analytics.example.com/tracker.js",
];

const EXTERNAL_PATTERNS = [
  // ... existing ...
  /^https:\/\/analytics\.example\.com\/.*\.js$/,
];
```

### Example 2: Conditional Loading

For scripts that should only load conditionally (e.g., based on user consent):

```javascript
// In footer.php
function loadAnalyticsScript() {
  if (window.analyticsScriptLoaded) {
    return; // Already loaded
  }

  var script = document.createElement("script");
  script.src = "https://analytics.example.com/tracker.js";
  script.async = true;
  script.defer = true;
  script.crossOrigin = "anonymous";
  script.onload = function () {
    window.analyticsScriptLoaded = true;
  };
  document.head.appendChild(script);
}

// Call when needed
if (userConsent.analytics) {
  loadAnalyticsScript();
}
```

Still add to service worker cache - it will cache when first loaded.

### Example 3: Versioned Script

For scripts with version numbers in URL:

```javascript
// In service-worker.js
const EXTERNAL_PATTERNS = [
  // ... existing ...
  /^https:\/\/cdn\.example\.com\/library\/v\d+\.\d+\.\d+\/script\.js$/,
];
```

This pattern will match any version (e.g., `v1.0.0`, `v2.1.3`).

## Best Practices

### 1. Always Use Async/Defer

```html
<!-- Good -->
<script src="script.js" async defer></script>

<!-- Bad -->
<script src="script.js"></script>
```

### 2. Use Crossorigin for External Scripts

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

<!-- Bad -->
<script src="https://example.com/script.js"></script>
```

### 3. Add Subresource Integrity When Available

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

### 4. Prevent Duplicate Loading

```javascript
if (!window.scriptLoaded && !document.getElementById("script-loader")) {
  // Load script
}
```

### 5. Handle Load Errors

```javascript
script.onerror = function () {
  console.error("Failed to load script");
  // Fallback or error handling
};
```

## Troubleshooting

### Script Not Caching

**Symptoms**: Script always loads from network, never from cache.

**Solutions**:

1. Verify URL is in `EXTERNAL_RESOURCES_TO_CACHE`
2. Check URL pattern matches in `EXTERNAL_PATTERNS`
3. Verify service worker is active (check DevTools)
4. Clear browser cache and reload

### Script Loading Twice

**Symptoms**: Script loads multiple times, causing errors.

**Solutions**:

1. Add duplicate check before loading
2. Use `window.scriptLoaded` flag
3. Check for existing script element: `document.getElementById('script-id')`

### CORS Errors

**Symptoms**: Console shows CORS errors when loading script.

**Solutions**:

1. Add `crossorigin="anonymous"` attribute
2. Verify origin server allows CORS
3. Use `no-cors` mode in service worker if needed

### Service Worker Not Updating

**Symptoms**: Changes to service worker not taking effect.

**Solutions**:

1. Update `CACHE_NAME` version
2. Unregister old service worker in DevTools
3. Hard refresh page (Ctrl+Shift+R)
4. Check service worker update interval (default: 1 hour)

## Testing Checklist

Before deploying a new external script:

- [ ] Script tag has `async` and `defer` attributes
- [ ] Script tag has `crossorigin="anonymous"` attribute
- [ ] URL added to `EXTERNAL_RESOURCES_TO_CACHE`
- [ ] URL pattern added to `EXTERNAL_PATTERNS` (if needed)
- [ ] Cache version updated in service worker
- [ ] Tested in browser DevTools
- [ ] Verified cache behavior (first load vs subsequent loads)
- [ ] Checked for console errors
- [ ] Tested on multiple browsers (Chrome, Firefox, Safari)
- [ ] Documented the addition

## Common Patterns

### HubSpot Script

```html
<script
  type="text/javascript"
  id="hs-script-loader"
  async
  defer
  src="https://js-eu1.hs-scripts.com/145133546.js"
  crossorigin="anonymous"
  onload="window.hubspotScriptLoaded = true;"
></script>
```

### Google Tag Manager

```html
<script
  async
  defer
  src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"
  crossorigin="anonymous"
></script>
```

### CDN Library (jsPDF)

```html
<script
  src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"
  async
  defer
  crossorigin="anonymous"
></script>
```

## Maintenance

### Regular Updates

- Review cached resources quarterly
- Remove unused scripts from cache
- Update cache version when removing resources
- Monitor cache storage usage

### Monitoring

- Check service worker registration rate
- Monitor cache hit rates
- Track cache storage usage
- Review error logs for cache-related issues

## 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)
- [Subresource Integrity - MDN](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
- [Caching Strategy Documentation](../performance/caching-strategy.md)
