| name | eds-performance-debugging |
| description | Guide for debugging and performance optimization of EDS blocks including error handling, FOUC prevention, Core Web Vitals optimization, and debugging workflows for Adobe Edge Delivery Services. |
EDS Performance & Debugging Guide
Purpose
Guide developers through debugging EDS blocks, optimizing performance, implementing proper error handling, and achieving excellent Core Web Vitals scores.
When to Use This Skill
Automatically activates when:
- Debugging errors in blocks or scripts
- Working with keywords: "error", "debug", "performance", "slow", "FOUC"
- Optimizing Core Web Vitals
- Handling exceptions in block code
Error Handling Patterns
Basic Error Handling in Blocks
export default function decorate(block) {
try {
const content = extractContent(block);
if (!content || content.length === 0) {
throw new Error('No content available');
}
const container = createStructure(content);
block.textContent = '';
block.appendChild(container);
} catch (error) {
console.error('Block decoration failed:', error);
block.innerHTML = `
<div class="error-message">
<p>Unable to load content</p>
</div>
`;
}
}
Async Error Handling
export default async function decorate(block) {
try {
showLoadingState(block);
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data || !Array.isArray(data)) {
throw new Error('Invalid data format');
}
hideLoadingState(block);
renderContent(block, data);
} catch (error) {
console.error('Failed to load block data:', error);
hideLoadingState(block);
showErrorState(block, 'Failed to load content. Please try again later.');
}
}
function showLoadingState(block) {
block.innerHTML = '<div class="loading">Loading...</div>';
}
function hideLoadingState(block) {
const loading = block.querySelector('.loading');
if (loading) loading.remove();
}
function showErrorState(block, message) {
block.innerHTML = `
<div class="error-state">
<p>${message}</p>
<button onclick="location.reload()">Retry</button>
</div>
`;
}
Graceful Degradation
export default function decorate(block) {
try {
if ('IntersectionObserver' in window) {
setupLazyLoading(block);
} else {
loadAllImagesImmediately(block);
}
} catch (error) {
console.error('Feature failed, using fallback:', error);
basicImplementation(block);
}
}
Debugging Workflows
Console Logging Best Practices
export default function decorate(block) {
console.group('Block: your-block');
console.log('Input HTML:', block.innerHTML);
console.log('Block classes:', block.className);
try {
const content = extractContent(block);
console.log('Extracted content:', content);
const container = createStructure(content);
console.log('Created structure:', container);
block.textContent = '';
block.appendChild(container);
console.log('Final HTML:', block.innerHTML);
} catch (error) {
console.error('Decoration failed:', error);
console.trace();
}
console.groupEnd();
}
Conditional Debugging
const DEBUG = window.location.hostname === 'localhost';
function debug(...args) {
if (DEBUG) {
console.log('[DEBUG]', ...args);
}
}
export default function decorate(block) {
debug('Decorating block:', block.className);
const content = extractContent(block);
debug('Content extracted:', content);
renderContent(block, content);
debug('Rendering complete');
}
Performance Timing
export default async function decorate(block) {
const startTime = performance.now();
try {
await loadAndRender(block);
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 100) {
console.warn(`Block took ${duration.toFixed(2)}ms (target: <100ms)`);
} else {
console.log(`Block loaded in ${duration.toFixed(2)}ms`);
}
} catch (error) {
console.error('Block loading failed:', error);
}
}
FOUC Prevention
CSS-First Approach
.your-block:not(.decorated) {
visibility: hidden;
}
.your-block.decorated {
visibility: visible;
}
export default function decorate(block) {
const content = extractContent(block);
const container = createStructure(content);
block.textContent = '';
block.appendChild(container);
block.classList.add('decorated');
}
Placeholder Content
.your-block::before {
content: '';
display: block;
width: 100%;
height: 300px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
.your-block.decorated::before {
display: none;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Progressive Enhancement
export default function decorate(block) {
block.classList.add('loading');
try {
const content = extractContent(block);
const enhanced = createEnhancedStructure(content);
block.textContent = '';
block.appendChild(enhanced);
} catch (error) {
console.error('Enhancement failed:', error);
} finally {
block.classList.remove('loading');
block.classList.add('decorated');
}
}
Core Web Vitals Optimization
Largest Contentful Paint (LCP)
export default function decorate(block) {
const images = block.querySelectorAll('img');
images.forEach((img, index) => {
if (index === 0) {
img.loading = 'eager';
img.fetchpriority = 'high';
} else {
img.loading = 'lazy';
}
});
}
Cumulative Layout Shift (CLS)
.your-block img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
.your-block-container {
min-height: 300px;
}
export default async function decorate(block) {
const height = block.offsetHeight;
block.style.minHeight = `${height}px`;
await loadContent(block);
block.style.minHeight = '';
}
First Input Delay (FID)
export default function decorate(block) {
const criticalSetup = () => {
const content = extractContent(block);
renderContent(block, content);
};
const nonCriticalSetup = () => {
setupAnalytics(block);
setupAnimations(block);
};
criticalSetup();
if ('requestIdleCallback' in window) {
requestIdleCallback(nonCriticalSetup);
} else {
setTimeout(nonCriticalSetup, 1);
}
}
Performance Optimization Patterns
Minimize DOM Manipulation
export default function decorate(block) {
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item;
block.appendChild(div);
});
}
export default function decorate(block) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
const div = document.createElement('div');
div.textContent = item;
fragment.appendChild(div);
});
block.textContent = '';
block.appendChild(fragment);
}
Debounce Expensive Operations
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
export default function decorate(block) {
const handleResize = debounce(() => {
recalculateLayout(block);
}, 250);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
Lazy Load Images
export default function decorate(block) {
const images = block.querySelectorAll('img');
images.forEach(img => {
img.loading = 'lazy';
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const image = entry.target;
image.src = image.dataset.src;
observer.unobserve(image);
}
});
});
observer.observe(img);
}
});
}
Optimize Event Listeners
export default function decorate(block) {
const items = block.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
block.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (item) {
handleClick(e, item);
}
});
}
Common Issues and Solutions
Issue: Blank Page / Block Not Visible
Symptoms:
- Console shows no errors
- JavaScript executes successfully
- Elements are in the DOM
- But page appears completely blank
Root Cause: EDS global styles hide <body> by default
Solution:
EDS uses a visibility pattern where the body is hidden until content is ready:
body {
display: none;
}
body.appear {
display: block;
}
Fix for test files:
<script type="module">
import decorate from './your-block.js';
document.body.classList.add('appear');
document.addEventListener('DOMContentLoaded', () => {
const blocks = document.querySelectorAll('.your-block');
blocks.forEach(decorate);
});
</script>
Debugging Steps:
-
Check if elements exist:
console.log('Button in DOM:', document.querySelector('.your-button'));
-
Check computed styles:
- Open DevTools → Elements tab
- Inspect the element
- Check Computed styles for
display: none or visibility: hidden
-
Check body visibility:
console.log('Body classes:', document.body.className);
console.log('Body computed display:', getComputedStyle(document.body).display);
-
Force visibility (debugging):
document.body.classList.add('appear');
Production Notes:
- In production, EDS automatically adds
appear class when page loads
- Test files need to add it manually
- This pattern prevents FOUC (Flash of Unstyled Content)
Issue: Block Not Rendering
Check:
export default function decorate(block) {
if (!block) {
console.error('Block is null or undefined');
return;
}
console.log('Block HTML:', block.innerHTML);
if (!block.children.length) {
console.warn('Block has no children');
}
try {
const content = extractContent(block);
console.log('Extracted content:', content);
} catch (error) {
console.error('Content extraction failed:', error);
}
}
Issue: Buttons Not Styled
Symptoms:
- Buttons exist in DOM but look unstyled
- No background color, borders, or padding
Root Cause: Global button styles not loading or not being applied
Solution:
-
Verify global styles load:
<link rel="stylesheet" href="/styles/styles.css">
-
Check button inherits global styles:
button {
display: inline-block;
padding: 5px 30px;
background-color: var(--link-color);
color: var(--background-color);
border-radius: 30px;
}
-
Add fallback styles if needed:
.your-block button {
display: inline-block;
padding: 10px 30px;
background-color: #0066cc;
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
}
Best Practice: Rely on global styles, only add block-specific overrides
Issue: CSS Not Loading
Solution:
-
Verify file names match exactly:
blocks/your-block/
├── your-block.js ← Must match
├── your-block.css ← Must match
-
Check browser DevTools → Network tab for 404 errors
-
Ensure CSS is valid:
npm run lint:css
Issue: Memory Leaks
Solution: Clean up event listeners
export default function decorate(block) {
const handleClick = () => {
console.log('Clicked');
};
block.addEventListener('click', handleClick);
return () => {
block.removeEventListener('click', handleClick);
};
}
Issue: Race Conditions
Solution: Use proper async/await
export default function decorate(block) {
fetch('/api/data')
.then(r => r.json())
.then(data => renderData(block, data));
setupEventListeners(block);
}
export default async function decorate(block) {
const response = await fetch('/api/data');
const data = await response.json();
renderData(block, data);
setupEventListeners(block);
}
Browser DevTools Tips
Network Tab
Monitor:
- CSS file loading (should be automatic)
- JavaScript module loading
- API requests and responses
- Failed requests (404, 500)
Console Tab
Use:
console.log() for debugging
console.error() for errors
console.warn() for warnings
console.table() for structured data
console.time() / console.timeEnd() for timing
Performance Tab
Record and analyze:
- Start recording
- Interact with your block
- Stop recording
- Review:
- Scripting time
- Rendering time
- Painting time
- Long tasks (>50ms)
Elements Tab
Use:
- Inspect DOM structure
- Check computed styles
- View event listeners
- Check accessibility tree
Performance Monitoring
Custom Timing
export default async function decorate(block) {
performance.mark('block-start');
await loadAndRender(block);
performance.mark('block-end');
performance.measure('block-time', 'block-start', 'block-end');
const measures = performance.getEntriesByName('block-time');
console.log(`Block took ${measures[0].duration.toFixed(2)}ms`);
performance.clearMarks();
performance.clearMeasures();
}
Memory Usage
if (performance.memory) {
console.log('Memory usage:', {
used: (performance.memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
total: (performance.memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB',
limit: (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2) + ' MB'
});
}
Testing for Performance
Load Time Testing
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
const startTime = performance.now();
await loadBlock(block);
const endTime = performance.now();
const duration = endTime - startTime;
console.log(`Load time: ${duration.toFixed(2)}ms`);
if (duration > 100) {
console.warn('⚠️ Load time exceeds 100ms target');
} else {
console.log('✅ Load time within target');
}
</script>
Lighthouse Testing
npm run debug
Target scores:
- Performance: 90+
- Accessibility: 90+
- Best Practices: 90+
Error Boundaries
Global Error Handler
window.addEventListener('error', (event) => {
console.error('Global error:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
});
Block-Specific Error Boundary
export default function decorate(block) {
const originalDecorate = () => {
const content = extractContent(block);
const container = createStructure(content);
block.textContent = '';
block.appendChild(container);
};
try {
originalDecorate();
} catch (error) {
console.error(`Block ${block.className} failed:`, error);
block.innerHTML = `
<div class="block-error">
<p>This content is temporarily unavailable.</p>
${DEBUG ? `<pre>${error.stack}</pre>` : ''}
</div>
`;
}
}
Related Documentation
Performance Checklist
Before deploying your block:
Next Steps
- Review your block for potential performance issues
- Add proper error handling with try-catch
- Implement FOUC prevention with CSS
- Test performance with DevTools
- Run Lighthouse audit
- Fix any issues identified
- Document performance characteristics
Remember: Fast, reliable blocks create better user experiences and improve your site's Core Web Vitals scores!