Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion blocks/fragment/fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { decorateMain } from '../../scripts/scripts.js';
import {
loadSections,
} from '../../scripts/aem.js';
import { fetchWithCacheBusting } from '../../scripts/utils.js';

/**
* Loads a fragment.
Expand All @@ -19,7 +20,9 @@ export async function loadFragment(path) {
if (path && path.startsWith('/')) {
const root = getRootPath().replace(/\/$/, '');
const url = `${root}${path}.plain.html`;
const resp = await fetch(url);

// Use cache-busting utility for UE support
const resp = await fetchWithCacheBusting(url);
if (resp.ok) {
const main = document.createElement('main');
main.innerHTML = await resp.text();
Expand Down
2 changes: 1 addition & 1 deletion blocks/product-list-page/product-list-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default async function decorate(block) {
alias: product.sku,
imageProps: {
...defaultImageProps,
style: 'aspect-ratio: 1; object-fit: contain; height: auto;'
style: 'aspect-ratio: 1; object-fit: contain; height: auto;',
},
wrapper: anchorWrapper,
params: {
Expand Down
128 changes: 128 additions & 0 deletions docs/cache-busting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Universal Editor Cache-Busting

## Problem

When authors edit fragments in Universal Editor (UE) and publish changes, they don't see updates immediately on pages that embed those fragments. This happens because:

1. Author edits and publishes fragment at `/fragments/header`
2. Fragment content is cached by the browser
3. Author opens a page that embeds the fragment
4. Page loads the **cached** version via `loadFragment()`
5. Author sees stale content, creating confusion

This caching issue is **specific to authoring in Universal Editor** - production sites should use normal browser caching for performance.

## Solution

Two utilities in `/scripts/utils.js` provide automatic cache-busting in UE:

### 1. `isUniversalEditor()`
Detects if running in UE or localhost context:

```javascript
export function isUniversalEditor() {
return window.location.hostname.includes('.ue.da.live')
|| window.location.hostname === 'localhost';
}
```

### 2. `fetchWithCacheBusting()`
Wrapper around `fetch()` that bypasses cache in UE:

```javascript
export async function fetchWithCacheBusting(resource, options = {}) {
const fetchOptions = isUniversalEditor()
? { ...options, cache: 'reload' }
: options;

return fetch(resource, fetchOptions);
}
```

**How it works:**
- In UE/localhost: Uses `cache: 'reload'` to bypass HTTP cache
- In production: Uses standard browser caching for performance

## Automatic Coverage

**All fragments automatically use cache-busting** via the centralized `loadFragment()` function:

```javascript
// blocks/fragment/fragment.js
import { fetchWithCacheBusting } from '../../scripts/utils.js';

export async function loadFragment(path) {
if (path && path.startsWith('/')) {
const root = getRootPath().replace(/\/$/, '');
const url = `${root}${path}.plain.html`;

// Automatic cache-busting for all fragments
const resp = await fetchWithCacheBusting(url);
// ...
}
}
```

Since every fragment uses this function, **no developer action is needed** - fragments work correctly in UE out of the box.

## Developer Usage

### For Custom Blocks

If you create a custom block that fetches `.plain.html` content, use the utility:

```javascript
// blocks/custom-content/custom-content.js
import { fetchWithCacheBusting } from '../../scripts/aem.js';

export default async function decorate(block) {
const contentPath = block.textContent.trim();

// Opt-in to cache-busting for UE authoring
const resp = await fetchWithCacheBusting(`${contentPath}.plain.html`);
block.innerHTML = await resp.text();
}
```

### For UE Detection Only

Use `isUniversalEditor()` to conditionally add authoring affordances:

```javascript
import { isUniversalEditor } from '../../scripts/aem.js';

export default async function decorate(block) {
if (isUniversalEditor()) {
// Add visual indicators for authors
block.setAttribute('data-editor-mode', 'true');
block.classList.add('ue-editing');
}
}
```

## Benefits

- **Automatic:** Fragments work correctly in UE without developer intervention
- **Opt-in:** Custom blocks choose to use cache-busting
- **Performance:** No impact on production (standard caching preserved)
- **Maintainable:** Single source of truth for UE detection
- **Reusable:** Utilities available via `aem.js` exports

## Technical Details

**Why `cache: 'reload'`?**
- Forces browser to validate cached responses with origin server
- Ensures authors always see latest published content
- More reliable than query parameters (which can be cached)

**Why include localhost?**
- Developers testing fragments locally need fresh content
- Consistent behavior between local and UE environments

**Export from aem.js:**
```javascript
// scripts/aem.js
export { isUniversalEditor, fetchWithCacheBusting } from './utils.js';
```

This makes utilities easily discoverable and follows EDS patterns for shared functionality.
3 changes: 3 additions & 0 deletions scripts/aem.js
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,9 @@ async function loadSections(element) {

init();

// Export utilities for blocks
export { isUniversalEditor, fetchWithCacheBusting } from './utils.js';

export {
buildBlock,
createOptimizedPicture,
Expand Down
35 changes: 35 additions & 0 deletions scripts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Utility functions for Edge Delivery Services
*/

/**
* Detects if running in Universal Editor context
* @returns {boolean} True if in Universal Editor or localhost
*/
export function isUniversalEditor() {
return window.location.hostname.includes('.ue.da.live')
|| window.location.hostname === 'localhost';
}

/**
* Fetch with automatic cache-busting in Universal Editor
*
* In UE context, uses cache: 'reload' to ensure authors always
* see fresh content. In production, uses standard caching.
*
* @param {string|Request} resource - URL or Request object
* @param {RequestInit} [options] - Fetch options
* @returns {Promise<Response>} Fetch response
*
* @example
* // Instead of: fetch(url)
* // Use: fetchWithCacheBusting(url)
* const response = await fetchWithCacheBusting('/fragments/header.plain.html');
*/
export async function fetchWithCacheBusting(resource, options = {}) {
const fetchOptions = isUniversalEditor()
? { ...options, cache: 'reload' }
: options;

return fetch(resource, fetchOptions);
}