Claude Code Plugins

Community-maintained marketplace

Feedback

eds-performance-debugging

@ddttom/webcomponents-with-eds
5
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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 {
    // Extract content
    const content = extractContent(block);

    // Validate content
    if (!content || content.length === 0) {
      throw new Error('No content available');
    }

    // Create and render
    const container = createStructure(content);
    block.textContent = '';
    block.appendChild(container);

  } catch (error) {
    // Log error to console
    console.error('Block decoration failed:', error);

    // Show user-friendly error
    block.innerHTML = `
      <div class="error-message">
        <p>Unable to load content</p>
      </div>
    `;
  }
}

Async Error Handling

export default async function decorate(block) {
  try {
    // Show loading state
    showLoadingState(block);

    // Fetch data
    const response = await fetch('/api/data');

    // Check response
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const data = await response.json();

    // Validate data
    if (!data || !Array.isArray(data)) {
      throw new Error('Invalid data format');
    }

    // Render content
    hideLoadingState(block);
    renderContent(block, data);

  } catch (error) {
    console.error('Failed to load block data:', error);

    // Show error state
    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 {
    // Try to use modern feature
    if ('IntersectionObserver' in window) {
      setupLazyLoading(block);
    } else {
      // Fallback for older browsers
      loadAllImagesImmediately(block);
    }

  } catch (error) {
    console.error('Feature failed, using fallback:', error);
    // Fallback implementation
    basicImplementation(block);
  }
}

Debugging Workflows

Console Logging Best Practices

export default function decorate(block) {
  // Use console groups for organization
  console.group('Block: your-block');

  // Log input state
  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(); // Show stack 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.css */

/* Hide block until decorated */
.your-block:not(.decorated) {
  visibility: hidden;
}

/* Show block after decoration */
.your-block.decorated {
  visibility: visible;
}
// your-block.js
export default function decorate(block) {
  // Extract and process content
  const content = extractContent(block);
  const container = createStructure(content);

  // Replace content
  block.textContent = '';
  block.appendChild(container);

  // Mark as decorated (makes visible)
  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) {
  // Add loading class immediately
  block.classList.add('loading');

  try {
    // Build enhanced version
    const content = extractContent(block);
    const enhanced = createEnhancedStructure(content);

    // Replace with enhanced version
    block.textContent = '';
    block.appendChild(enhanced);

  } catch (error) {
    console.error('Enhancement failed:', error);
    // Keep original content if enhancement fails
  } finally {
    // Remove loading class
    block.classList.remove('loading');
    block.classList.add('decorated');
  }
}

Core Web Vitals Optimization

Largest Contentful Paint (LCP)

export default function decorate(block) {
  // Prioritize above-the-fold images
  const images = block.querySelectorAll('img');

  images.forEach((img, index) => {
    if (index === 0) {
      // First image: high priority, no lazy loading
      img.loading = 'eager';
      img.fetchpriority = 'high';
    } else {
      // Other images: lazy load
      img.loading = 'lazy';
    }
  });
}

Cumulative Layout Shift (CLS)

/* Reserve space for images to prevent layout shift */
.your-block img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9; /* Reserve space */
}

/* Reserve space for dynamic content */
.your-block-container {
  min-height: 300px; /* Prevent shift during loading */
}
export default async function decorate(block) {
  // Set explicit dimensions before loading content
  const height = block.offsetHeight;
  block.style.minHeight = `${height}px`;

  // Load content
  await loadContent(block);

  // Remove min-height after content loaded
  block.style.minHeight = '';
}

First Input Delay (FID)

export default function decorate(block) {
  // Defer non-critical work
  const criticalSetup = () => {
    // Critical rendering
    const content = extractContent(block);
    renderContent(block, content);
  };

  const nonCriticalSetup = () => {
    // Analytics, animations, etc.
    setupAnalytics(block);
    setupAnimations(block);
  };

  // Run critical work immediately
  criticalSetup();

  // Defer non-critical work
  if ('requestIdleCallback' in window) {
    requestIdleCallback(nonCriticalSetup);
  } else {
    setTimeout(nonCriticalSetup, 1);
  }
}

Performance Optimization Patterns

Minimize DOM Manipulation

// ❌ BAD - Multiple reflows
export default function decorate(block) {
  items.forEach(item => {
    const div = document.createElement('div');
    div.textContent = item;
    block.appendChild(div); // Reflow on each append
  });
}

// ✅ GOOD - Single reflow
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); // Single reflow
}

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(() => {
    // Expensive resize logic
    recalculateLayout(block);
  }, 250);

  window.addEventListener('resize', handleResize);

  // Cleanup
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}

Lazy Load Images

export default function decorate(block) {
  const images = block.querySelectorAll('img');

  images.forEach(img => {
    // Native lazy loading
    img.loading = 'lazy';

    // Or use Intersection Observer
    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) {
  // ❌ BAD - Multiple listeners
  const items = block.querySelectorAll('.item');
  items.forEach(item => {
    item.addEventListener('click', handleClick);
  });

  // ✅ GOOD - Event delegation
  block.addEventListener('click', (e) => {
    const item = e.target.closest('.item');
    if (item) {
      handleClick(e, item);
    }
  });
}

Common Issues and Solutions

Issue: Block Not Rendering

Check:

export default function decorate(block) {
  // 1. Check if block exists
  if (!block) {
    console.error('Block is null or undefined');
    return;
  }

  // 2. Check if block has content
  console.log('Block HTML:', block.innerHTML);
  if (!block.children.length) {
    console.warn('Block has no children');
  }

  // 3. Check for JavaScript errors
  try {
    const content = extractContent(block);
    console.log('Extracted content:', content);
  } catch (error) {
    console.error('Content extraction failed:', error);
  }
}

Issue: CSS Not Loading

Solution:

  1. Verify file names match exactly:

    blocks/your-block/
    ├── your-block.js   ← Must match
    ├── your-block.css  ← Must match
    
  2. Check browser DevTools → Network tab for 404 errors

  3. 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');
  };

  // Add listener
  block.addEventListener('click', handleClick);

  // Return cleanup function
  return () => {
    block.removeEventListener('click', handleClick);
  };
}

Issue: Race Conditions

Solution: Use proper async/await

// ❌ BAD - Race condition
export default function decorate(block) {
  fetch('/api/data')
    .then(r => r.json())
    .then(data => renderData(block, data));

  // This runs before fetch completes
  setupEventListeners(block);
}

// ✅ GOOD - Proper sequencing
export default async function decorate(block) {
  const response = await fetch('/api/data');
  const data = await response.json();
  renderData(block, data);

  // This runs after data is rendered
  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:

  1. Start recording
  2. Interact with your block
  3. Stop recording
  4. 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) {
  // Mark start
  performance.mark('block-start');

  // Do work
  await loadAndRender(block);

  // Mark end
  performance.mark('block-end');

  // Measure
  performance.measure('block-time', 'block-start', 'block-end');

  // Get results
  const measures = performance.getEntriesByName('block-time');
  console.log(`Block took ${measures[0].duration.toFixed(2)}ms`);

  // Clean up
  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

// Add to test.html
<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

# Test your block locally
npm run debug

# In Chrome:
# 1. Open DevTools
# 2. Go to Lighthouse tab
# 3. Select categories: Performance, Accessibility
# 4. Click "Generate report"

Target scores:

  • Performance: 90+
  • Accessibility: 90+
  • Best Practices: 90+

Error Boundaries

Global Error Handler

// In scripts/scripts.js or similar
window.addEventListener('error', (event) => {
  console.error('Global error:', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    error: event.error
  });

  // Optionally send to analytics
  // trackError(event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);

  // Optionally send to analytics
  // trackError(event.reason);
});

Block-Specific Error Boundary

export default function decorate(block) {
  // Wrap entire block in try-catch
  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);

    // Show error UI
    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:

  • No JavaScript errors in console
  • Load time < 100ms for simple blocks
  • No layout shifts (CLS score < 0.1)
  • Images lazy loaded (except hero images)
  • Event delegation used for multiple items
  • Proper error handling implemented
  • Memory leaks checked and fixed
  • Tested on slow network (3G)
  • Tested on mobile devices
  • Lighthouse performance score 90+

Next Steps

  1. Review your block for potential performance issues
  2. Add proper error handling with try-catch
  3. Implement FOUC prevention with CSS
  4. Test performance with DevTools
  5. Run Lighthouse audit
  6. Fix any issues identified
  7. Document performance characteristics

Remember: Fast, reliable blocks create better user experiences and improve your site's Core Web Vitals scores!