Complete Technical Training for Adobe EDS Blocks
An Interactive Tutorial for Mastering Adobe Edge Delivery Services Block Development
Remember the EDS philosophy: simplicity over complexity, performance over features, content over code structure. Every decision should serve the user experience first.
AI Instructor Introduction
Welcome to the comprehensive Adobe EDS (Edge Delivery Services) block development training! I'm your AI instructor, and I'll guide you through every aspect of creating custom blocks for the EDS framework. This isn't just documentation—it's an interactive learning experience designed to transform you from beginner to expert.
What Makes This Training Special:
🎯 Progressive Learning: From basic concepts to advanced patterns
🛠️ Hands-On Exercises: Real code you can run immediately
🚀 Production-Ready: Best practices from actual implementations
🔧 Troubleshooting Focus: Common issues and their solutions
Chapter 1: EDS Foundation - Understanding the Framework
Introduction: Why EDS Changes Everything
Before we explore code, let's understand why Adobe Edge Delivery Services represents a fundamental shift in how we think about web development. Most developers approach EDS with traditional CMS thinking and struggle because they're trying to force old patterns into a revolutionary new paradigm.
EDS wasn't built to be "another CMS" or "another framework." It was designed to solve a fundamental problem that has plagued web development for decades: the tension between content creators who want to focus on writing and developers who need technical control. Traditional systems force one side to compromise—either content creators learn complex technical interfaces, or developers sacrifice functionality for simplicity.
The Philosophy Revolution That Changes How You Code
EDS solves this by inverting the entire relationship between content and technology. Instead of asking "How do we fit content into our technical system?" EDS asks "How do we make our technical system adapt to natural content creation?"
This philosophical shift has profound implications for how you write code. In traditional systems, you build components first and then fill them with content. In EDS, content exists first, and you enhance it progressively. This isn't just a different workflow—it's a completely different way of thinking about web development.
The EDS Philosophy Revolution Let me start by explaining something fundamental that sets EDS apart from every other web framework you've used:
Traditional CMS Thinking:
Content Authors → Adapt to → Technical System
EDS Revolutionary Approach:
Technical System → Adapts to → Natural Content Creation
Core Principles You Must Internalise
- Content-First Architecture
// ❌ Traditional: Build UI first, add content later
function buildComponent() {
createHeader();
createBody();
addContent(); // Content forced into predefined structure
}
// ✅ EDS Way: Content defines structure, enhance progressively
export default function decorate(block) {
// Content already exists, enhance it intelligently
const existingContent = extractContentFromBlock(block);
enhanceWithFunctionality(existingContent);
}
- Zero Dependencies Philosophy
// ❌ Avoid: Heavy frameworks and dependencies
import React from 'react';
import { Button, Modal } from 'antd';
import moment from 'moment';
// ✅ EDS Way: Pure web standards
const button = document.createElement('button');
const modal = document.createElement('dialog');
const timestamp = new Date().toISOString();
- Performance-First Development
/* ❌ Avoid: Heavy frameworks that hurt Core Web Vitals */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap');
/* ✅ EDS Way: System fonts and efficient loading */
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
Hands-On Exercise 1: Understanding EDS HTML Transformation
Let's see how EDS transforms content. Create this test file:
<!-- /blocks/hello-world/test.html -->
<!DOCTYPE html>
<html>
<head>
<title>EDS Transformation Demo</title>
<script type="module" src="/scripts/aem.js"></script>
</head>
<body>
<div class="hello-world block" data-block-name="hello-world" data-block-status="initialised">
<div>
<div>
<p>Hello, EDS World!</p>
<p>This is my first block.</p>
</div>
</div>
</div>
\
<script type="module">
import decorate from './hello-world.js';
document.querySelectorAll('.hello-world.block').forEach(decorate);
</script>
</body>
</html>
Key Learning Points:
📝 EDS blocks use .block-name.block class structure 📦 Content is nested in content
structure 🎯 JavaScript targets .block-name.block for decoration 🔄 The decorate function receives fully rendered HTML
Chapter 2: Block Architecture Mastery
Understanding Blocks: The Heart of EDS Enhancement
Now that you understand the EDS philosophy, let's explore how it translates into practical development through blocks. Blocks are where the magic happens—they're the mechanism that transforms static content into interactive experiences whilst maintaining EDS's performance and simplicity principles.
Think of blocks as enhancement layers. They don't replace content; they amplify it. When a content author creates a table in Google Docs and marks it as a "gallery" block, they're not building a technical component—they're expressing intent. Your job as a developer is to recognise that intent and enhance the content accordingly.
This is profoundly different from component-based frameworks where you define the structure and then populate it with data. In EDS, the structure already exists in a basic form, and you're progressively enhancing it. This distinction is crucial because it affects every architectural decision you'll make.
Why Block Architecture Matters
The block architecture isn't arbitrary—it's designed to solve real problems that plague modern web development:
Performance: Blocks load only when needed, CSS and JavaScript are scoped, and there's no framework overhead
Maintainability: Each block is self-contained with clear boundaries and responsibilities
Scalability: Teams can work on different blocks simultaneously without conflicts
Content Independence: Authors can create content without knowing technical implementation details
The EDS Block Anatomy
Every EDS block follows a strict architectural pattern designed to support these principles. Let me break it down:
/blocks/block-name/
├── block-name.js # 🧠 The brain - decoration logic
├── block-name.css # 🎨 The skin - visual presentation
├── test.html # 🧪 The lab - development testing
├── README.md # 📖 The manual - documentation
└── example.md # 📋 The demo - usage examples
Naming Conventions That Matter
// ✅ Correct naming patterns
.counter.block // JavaScript selector
.counter // CSS styling
.counter-button // Element styling
.counter-display // Element styling
.counter-button-active // State modifier
// ❌ Wrong patterns that will break
.counter-block // Conflicts with EDS .block class
.counterButton // Not EDS convention
.counter_button // Underscores not used
Hands-On Exercise 2: Building Your First Interactive Block
Let's build a simple counter block that demonstrates all EDS patterns:
// /blocks/counter/counter.js
const COUNTER_CONFIG = {
INITIAL_VALUE: 0,
STEP_SIZE: 1,
MAX_VALUE: 100,
ANIMATION_DURATION: 200,
STORAGE_KEY: 'eds-counter-value'
};
export default function decorate(block) {
try {
// 1. Extract existing content (EDS pattern)
const content = extractContentFromBlock(block);
\
// 2. Create enhanced structure
const counterContainer = createCounterStructure(content);
\
// 3. Add functionality
setupCounterBehaviour(counterContainer);
\
// 4. Replace block content (EDS pattern)
block.innerHTML = '';
block.appendChild(counterContainer);
\
// 5. Add CSS class for styling
block.classList.add('counter-enhanced');
\
} catch (error) {
console.warn('[Counter] Enhancement failed:', error);
showFallbackContent(block);
}
}
function extractContentFromBlock(block) {
// EDS provides content in nested div structure
const contentDiv = block.querySelector('div > div');
const initialValue = parseInt(contentDiv?.textContent?.trim()) || COUNTER_CONFIG.INITIAL_VALUE;
return { initialValue };
}
function createCounterStructure(content) {
const container = document.createElement('div');
container.className = 'counter-container';
// Create counter display
const display = document.createElement('div');
display.className = 'counter-display';
display.textContent = content.initialValue;
display.setAttribute('aria-live', 'polite');
display.setAttribute('aria-label', 'Current counter value');
// Create decrease button
const decreaseBtn = document.createElement('button');
decreaseBtn.className = 'counter-button counter-button-decrease';
decreaseBtn.textContent = '-';
decreaseBtn.setAttribute('aria-label', 'Decrease counter');
// Create increase button
const increaseBtn = document.createElement('button');
increaseBtn.className = 'counter-button counter-button-increase';
increaseBtn.textContent = '+';
increaseBtn.setAttribute('aria-label', 'Increase counter');
container.appendChild(decreaseBtn);
container.appendChild(display);
container.appendChild(increaseBtn);
return container;
}
function setupCounterBehaviour(container) {
const display = container.querySelector('.counter-display');
const decreaseBtn = container.querySelector('.counter-button-decrease');
const increaseBtn = container.querySelector('.counter-button-increase');
let currentValue = parseInt(display.textContent);
// Decrease button handler
decreaseBtn.addEventListener('click', () => {
if (currentValue > 0) {
currentValue -= COUNTER_CONFIG.STEP_SIZE;
updateDisplay(display, currentValue);
saveValue(currentValue);
}
});
// Increase button handler
increaseBtn.addEventListener('click', () => {
if (currentValue < COUNTER_CONFIG.MAX_VALUE) {
currentValue += COUNTER_CONFIG.STEP_SIZE;
updateDisplay(display, currentValue);
saveValue(currentValue);
}
});
// Keyboard support
container.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp' || e.key === '+') {
e.preventDefault();
increaseBtn.click();
} else if (e.key === 'ArrowDown' || e.key === '-') {
e.preventDefault();
decreaseBtn.click();
}
});
}
function updateDisplay(display, value) {
// Smooth transition effect
display.style.transform = 'scale(1.1)';
display.textContent = value;
setTimeout(() => {
display.style.transform = 'scale(1)';
}, COUNTER_CONFIG.ANIMATION_DURATION);
}
function saveValue(value) {
try {
localStorage.setItem(COUNTER_CONFIG.STORAGE_KEY, value.toString());
} catch (error) {
console.warn('[Counter] Could not save value:', error);
}
}
function showFallbackContent(block) {
block.innerHTML = `
<div class="counter-fallback">
<p>Counter functionality unavailable</p>
<p>Please refresh the page to try again</p>
</div>
`;
}
Corresponding CSS with EDS Best Practices
/* /blocks/counter/counter.css */
.counter.block {
/* Base block styling - never style .counter-container */
max-width: 300px;
margin: 2rem auto;
font-family: var(--body-font-family, system-ui);
}
.counter-container {
display: flex;
align-items: centre;
justify-content: centre;
gap: 1rem;
padding: 1.5rem;
background: var(--background-colour, #fff);
border: 2px solid var(--border-colour, #e5e7eb);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.counter-display {
font-size: 2rem;
font-weight: bold;
colour: var(--text-colour, #1f2937);
min-width: 4rem;
text-align: centre;
transition: transform 0.2s ease;
}
.counter-button {
width: 3rem;
height: 3rem;
border: none;
border-radius: 50%;
background: var(--link-colour, #0066cc);
colour: white;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: centre;
justify-content: centre;
}
.counter-button:hover {
background: var(--link-hover-colour, #0052a3);
transform: scale(1.05);
}
.counter-button:focus {
outline: 2px solid var(--link-colour, #0066cc);
outline-offset: 2px;
}
.counter-button:active {
transform: scale(0.95);
}
.counter-button:disabled {
background: #9ca3af;
cursor: not-allowed;
transform: none;
}
/* Responsive design */
@media (max-width: 600px) {
.counter-container {
padding: 1rem;
gap: 0.75rem;
}
.counter-display {
font-size: 1.5rem;
min-width: 3rem;
}
.counter-button {
width: 2.5rem;
height: 2.5rem;
font-size: 1.25rem;
}
}
/* Error state styling */
.counter-fallback {
text-align: centre;
padding: 2rem;
colour: var(--text-colour-secondary, #6b7280);
font-style: italic;
}
Chapter 3: Local Development Mastery with server.js
The Development Challenge: Bridging Local and Production
One of the biggest challenges in EDS development is creating an efficient local development environment that accurately reflects production behaviour. Unlike traditional frameworks with complex build processes, EDS runs the same code in development and production—but this creates a unique challenge: how do you develop locally when your blocks depend on content, images, and assets that exist on your production server?
This is where EDS's development server becomes crucial. It's not just a static file server—it's an intelligent proxy system that lets you develop blocks locally whilst seamlessly accessing production assets. This architecture is fundamental to the EDS development workflow and understanding it will dramatically improve your productivity.
Why the Proxy Architecture Is Revolutionary
Traditional development often requires either copying entire production databases locally or mocking all external dependencies. Both approaches have serious drawbacks: local copies are complex to maintain and quickly become stale, whilst mocks don't reflect real-world conditions.
EDS solves this with a hybrid approach: your code runs locally for instant feedback, but missing assets are automatically fetched from production. This means you're always developing against real content and real data, but with the fast iteration cycles of local development.
Understanding the Development Environment
The EDS development server implements a sophisticated local-first, proxy-fallback architecture that's crucial for efficient development. Here's how it fundamentally changes your workflow:
// server.js architecture overview
const serverFlow = {
1: "Request comes in",
2: "Check if file exists locally",
3: "Serve local file if found",
4: "Proxy to production if not found",
5: "Return 404 if both fail"
};
Setting Up Your Development Environment
# 1. Start the EDS development server
npm run debug
# 2. Server starts with full logging
🚀 Server running at http://localhost:3000
📁 Serving files from: /your/project/path
🔗 Proxying missing files to: https://allabout.network
📄 Main page: http://localhost:3000/aem.html
Hands-On Exercise 3: Development Workflow Mastery
Let's practice the complete development workflow:
Create your test file:
<!-- /blocks/counter/test.html -->
<!DOCTYPE html>
<html>
<head>
<title>Counter Block Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="module" src="/scripts/aem.js"></script>
<link rel="stylesheet" href="/styles/styles.css">
</head>
<body class="appear">
<main>
<div class="section">
<div class="section-metadata">
<div>
<div>style</div>
<div>highlight</div>
</div>
</div>
</div>
\
<div class="section">
<h1>Counter Block Testing</h1>
\
<div class="test-scenario">
<h2>Basic Counter</h2>
<div class="counter block" data-block-name="counter" data-block-status="initialised">
<div>
<div>5</div>
</div>
</div>
</div>
\
<div class="test-scenario">
<h2>Zero Counter</h2>
<div class="counter block" data-block-name="counter" data-block-status="initialised">
<div>
<div>0</div>
</div>
</div>
</div>
</div>
</main>
<script type="module">
import decorate from './counter.js';
\
document.addEventListener('DOMContentLoaded', () => {
const blocks = document.querySelectorAll('.counter.block');
blocks.forEach(decorate);
});
</script>
</body>
</html>
Watch the server logs:
Request: GET /blocks/counter/test.html
Serving local file: /project/blocks/counter/test.html
Request: GET /styles/styles.css
Proxying request to: https://allabout.network/styles/styles.css
✅ Successfully proxied: /styles/styles.css
Request: GET /blocks/counter/counter.js
Serving local file: /project/blocks/counter/counter.js
Request: GET /blocks/counter/counter.css
Serving local file: /project/blocks/counter/counter.css
Navigate to your test: http://localhost:3000/blocks/counter/test.html
Understanding the Proxy System
The development server uses an intelligent proxy system:
// When you request a file that doesn't exist locally
Request: GET /media/hero-image.jpg
Local file: Not found
Proxy target: https://allabout.network/media/hero-image.jpg
Result: Production image served locally
This means you can:
✅ Develop blocks locally with real production data ✅ Test with actual images and assets ✅ No need to copy entire production environment ✅ Focus on your block development
Chapter 4: Advanced Block Patterns
Beyond Basic Enhancement: Creating Sophisticated Experiences
At this point, you understand EDS fundamentals and can create basic interactive blocks. Now we'll explore how to build sophisticated functionality whilst maintaining EDS's core principles. This is where EDS development becomes an art—balancing complexity with simplicity, features with performance, and interactivity with content-first design.
The patterns you'll learn in this chapter solve real-world problems: how to fetch and display dynamic data, how to create complex user interfaces, how to manage state across interactions, and how to build reusable functionality. But we'll do it the EDS way—progressively enhancing content rather than replacing it, using web standards rather than frameworks, and maintaining perfect performance scores.
The Challenge of Complex Functionality
Traditional frameworks handle complexity by adding layers of abstraction—state management libraries, component frameworks, build tools, and runtime dependencies. EDS takes the opposite approach: it handles complexity through careful architecture and smart use of web standards. This requires a different mindset and different patterns.
When building complex blocks, you're not just writing JavaScript—you're designing enhancement strategies. How do you show loading states without layout shift? How do you handle errors gracefully? How do you manage focus and accessibility? How do you ensure your block works even if JavaScript fails to load?
Data-Driven Blocks: The Foundation of Dynamic Content
Many sophisticated EDS blocks need to fetch and display dynamic data—article lists, product catalogues, user-generated content. This creates several challenges: how do you show immediate content whilst data loads? How do you handle network failures? How do you manage content that changes frequently?
Let's explore the sophisticated patterns that solve these challenges:
// /blocks/article-cards/article-cards.js
const CARDS_CONFIG = {
DEFAULT_DATA_PATH: '/articles/query-index.json',
MAX_CARDS: 12,
LOADING_DELAY: 300,
ERROR_RETRY_ATTEMPTS: 3,
CARD_ANIMATION_STAGGER: 100
};
export default async function decorate(block) {
try {
// Show loading state immediately
showLoadingState(block);
\
// Extract data path from content
const dataPath = extractDataPath(block) || CARDS_CONFIG.DEFAULT_DATA_PATH;
\
// Fetch data with error handling
const articlesData = await fetchWithRetry(dataPath);
\
// Generate cards with staggered animation
await generateArticleCards(block, articlesData);
\
} catch (error) {
console.warn('[Article Cards] Enhancement failed:', error);
showErrorState(block, error);
}
}
function extractDataPath(block) {
const contentDiv = block.querySelector('div > div');
const path = contentDiv?.textContent?.trim();
// Validate path format
if (path && (path.endsWith('.json') || path.startsWith('/'))) {
return path;
}
return null;
}
async function fetchWithRetry(url, attempt = 1) {
try {
const response = await fetch(url);
\
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
\
const data = await response.json();
\
if (!data || !Array.isArray(data.data)) {
throw new Error('Invalid data format received');
}
\
return data;
\
} catch (error) {
if (attempt < CARDS_CONFIG.ERROR_RETRY_ATTEMPTS) {
console.warn(`[Article Cards] Attempt ${attempt} failed, retrying...`);
await delay(1000 * attempt); // Exponential backoff
return fetchWithRetry(url, attempt + 1);
}
\
throw error;
}
}
function showLoadingState(block) {
block.innerHTML = `
<div class="article-cards-loading">
<div class="loading-spinner"></div>
<p>Loading articles...</p>
</div>
`;
block.classList.add('article-cards-loading-state');
}
async function generateArticleCards(block, articlesData) {
const articles = articlesData.data.slice(0, CARDS_CONFIG.MAX_CARDS);
// Create container
const container = document.createElement('div');
container.className = 'article-cards-container';
// Generate cards
const cardPromises = articles.map((article, index) =>
createArticleCard(article, index)
);
// Wait for all cards to be created
const cards = await Promise.all(cardPromises);
// Add cards to container
cards.forEach(card => container.appendChild(card));
// Replace loading state with cards
block.innerHTML = '';
block.appendChild(container);
block.classList.remove('article-cards-loading-state');
block.classList.add('article-cards-loaded');
// Animate cards in with stagger
animateCardsIn(cards);
}
async function createArticleCard(article, index) {
const card = document.createElement('article');
card.className = 'article-card';
card.style.animationDelay = `${index * CARDS_CONFIG.CARD_ANIMATION_STAGGER}ms`;
// Create image with lazy loading
const imageContainer = document.createElement('div');
imageContainer.className = 'article-card-image';
if (article.image) {
const img = document.createElement('img');
img.src = article.image;
img.alt = article.title || 'Article image';
img.loading = 'lazy';
imageContainer.appendChild(img);
}
// Create content
const content = document.createElement('div');
content.className = 'article-card-content';
if (article.title) {
const title = document.createElement('h3');
title.className = 'article-card-title';
title.textContent = article.title;
content.appendChild(title);
}
if (article.description) {
const description = document.createElement('p');
description.className = 'article-card-description';
description.textContent = article.description;
content.appendChild(description);
}
if (article.path) {
const link = document.createElement('a');
link.className = 'article-card-link';
link.href = article.path;
link.textContent = 'Read More';
link.setAttribute('aria-label', `Read more about ${article.title}`);
content.appendChild(link);
}
card.appendChild(imageContainer);
card.appendChild(content);
return card;
}
function animateCardsIn(cards) {
cards.forEach((card, index) => {
setTimeout(() => {
card.classList.add('article-card-visible');
}, index * CARDS_CONFIG.CARD_ANIMATION_STAGGER);
});
}
function showErrorState(block, error) {
block.innerHTML = `
<div class="article-cards-error">
<h3>Unable to load articles</h3>
<p>Please try refreshing the page</p>
<button onclick="location.reload()" class="retry-button">
Retry
</button>
</div>
`;
block.classList.add('article-cards-error-state');
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Hands-On Exercise 4: Complex Modal Block
Let's build a sophisticated modal block that demonstrates advanced EDS patterns:
// /blocks/feature-modal/feature-modal.js
const MODAL_CONFIG = {
ANIMATION_DURATION: 300,
ESCAPE_KEY: 'Escape',
OVERLAY_CLICK_DISMISS: true,
FOCUS_TRAP: true,
BODY_SCROLL_LOCK: true
};
export default function decorate(block) {
try {
const modalData = extractModalData(block);
const modalStructure = createModalStructure(modalData);
\
// Replace block content
block.innerHTML = '';
block.appendChild(modalStructure.trigger);
\
// Add modal to body (not block, for z-index control)
document.body.appendChild(modalStructure.modal);
\
// Setup behaviour
setupModalBehaviour(modalStructure);
\
} catch (error) {
console.warn('[Feature Modal] Enhancement failed:', error);
showFallbackContent(block);
}
}
function extractModalData(block) {
const rows = Array.from(block.querySelectorAll('div > div > p'));
return {
triggerText: rows[0]?.textContent || 'Open Modal',
title: rows[1]?.textContent || 'Modal Title',
content: rows[2]?.innerHTML || 'Modal content goes here',
ctaText: rows[3]?.textContent || 'Close'
};
}
function createModalStructure(data) {
// Create trigger button
const trigger = document.createElement('button');
trigger.className = 'feature-modal-trigger';
trigger.textContent = data.triggerText;
trigger.setAttribute('aria-haspopup', 'dialog');
// Create modal overlay
const overlay = document.createElement('div');
overlay.className = 'feature-modal-overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-labelledby', 'modal-title');
// Create modal container
const modal = document.createElement('div');
modal.className = 'feature-modal-container';
// Create header
const header = document.createElement('div');
header.className = 'feature-modal-header';
const title = document.createElement('h2');
title.id = 'modal-title';
title.className = 'feature-modal-title';
title.textContent = data.title;
const closeButton = document.createElement('button');
closeButton.className = 'feature-modal-close';
closeButton.innerHTML = '×';
closeButton.setAttribute('aria-label', 'Close modal');
header.appendChild(title);
header.appendChild(closeButton);
// Create content
const content = document.createElement('div');
content.className = 'feature-modal-content';
content.innerHTML = data.content;
// Create footer
const footer = document.createElement('div');
footer.className = 'feature-modal-footer';
const ctaButton = document.createElement('button');
ctaButton.className = 'feature-modal-cta';
ctaButton.textContent = data.ctaText;
footer.appendChild(ctaButton);
// Assemble modal
modal.appendChild(header);
modal.appendChild(content);
modal.appendChild(footer);
overlay.appendChild(modal);
return { trigger, modal: overlay, closeButton, ctaButton };
}
function setupModalBehaviour({ trigger, modal, closeButton, ctaButton }) {
let isOpen = false;
let previousFocus = null;
// Open modal
function openModal() {
if (isOpen) return;
\
previousFocus = document.activeElement;
isOpen = true;
\
modal.classList.add('feature-modal-visible');
document.body.classList.add('modal-open');
\
// Focus management
const firstFocusable = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
firstFocusable?.focus();
\
// Setup focus trap
if (MODAL_CONFIG.FOCUS_TRAP) {
setupFocusTrap(modal);
}
}
// Close modal
function closeModal() {
if (!isOpen) return;
\
isOpen = false;
modal.classList.remove('feature-modal-visible');
document.body.classList.remove('modal-open');
\
// Restore focus
previousFocus?.focus();
previousFocus = null;
\
// Remove focus trap
removeFocusTrap();
}
// Event listeners
trigger.addEventListener('click', openModal);
closeButton.addEventListener('click', closeModal);
ctaButton.addEventListener('click', closeModal);
// Escape key
document.addEventListener('keydown', (e) => {
if (e.key === MODAL_CONFIG.ESCAPE_KEY && isOpen) {
closeModal();
}
});
// Overlay click
if (MODAL_CONFIG.OVERLAY_CLICK_DISMISS) {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
}
}
function setupFocusTrap(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
function trapFocus(e) {
if (e.key === 'Tab') {
if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
}
modal.addEventListener('keydown', trapFocus);
modal._focusTrap = trapFocus; // Store for cleanup
}
function removeFocusTrap() {
const modal = document.querySelector('.feature-modal-overlay');
if (modal && modal._focusTrap) {
modal.removeEventListener('keydown', modal._focusTrap);
delete modal._focusTrap;
}
}
Chapter 5: CSS Architecture and Responsive Design
The Art of EDS Styling: Performance Meets Flexibility
CSS in EDS isn't just about making things look good—it's about creating styles that load fast, work everywhere, and integrate seamlessly with the content-first architecture. Unlike traditional frameworks where CSS often becomes an afterthought or gets generated by build tools, EDS CSS is hand-crafted, purposeful, and directly tied to performance.
EDS has specific CSS constraints that might seem limiting at first but are actually liberating once you understand them. These constraints ensure that your styles don't conflict with other blocks, don't hurt performance, and work consistently across different content scenarios.
Why EDS CSS Architecture Matters
In traditional development, CSS often becomes a maintenance nightmare: specificity wars, unused rules, unpredictable cascading, and performance bottlenecks. EDS prevents these problems through architectural constraints that force good practices whilst maintaining flexibility.
The key insight is that EDS CSS follows the same content-first philosophy as the JavaScript: you're not building a design system from scratch, you're enhancing and refining what's already there. The base styles provide a solid foundation, and your block styles add purposeful enhancements.
Understanding the CSS Hierarchy
EDS has a carefully designed CSS hierarchy that ensures consistency and performance:
Base Styles (/styles/styles.css): Global typography, colours, and layout foundations
Block Styles (/blocks/blockname/blockname.css): Component-specific styling
Variation Styles: Additional classes for different block presentations
This hierarchy isn't just organisation—it's performance enhancement. Base styles load once and cache across all pages. Block styles load only when needed. Variations use simple class toggles rather than duplicating CSS.
EDS CSS Best Practices
EDS has specific CSS patterns that you must follow for compatibility. These aren't arbitrary rules—they're designed to prevent conflicts and ensure performance:
/* ✅ Correct: Target the block element */
.feature-modal.block {
display: inline-block;
margin: 1rem 0;
}
/* ❌ Wrong: Never style container elements */
.feature-modal-container {
/* EDS container elements should never have styling */
}
/* ✅ Correct: Style wrapper elements for layout */
.feature-modal-wrapper {
padding: 1rem;
max-width: 600px;
}
Responsive Design Patterns
/* /blocks/feature-modal/feature-modal.css */
.feature-modal.block {
display: inline-block;
}
.feature-modal-trigger {
background: var(--link-colour, #0066cc);
colour: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.feature-modal-trigger:hover {
background: var(--link-hover-colour, #0052a3);
transform: translateY(-1px);
}
.feature-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: centre;
justify-content: centre;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.feature-modal-overlay.feature-modal-visible {
opacity: 1;
visibility: visible;
}
.feature-modal-container {
background: white;
border-radius: 12px;
max-width: 90vw;
max-height: 90vh;
width: 600px;
overflow: hidden;
transform: scale(0.9) translateY(20px);
transition: transform 0.3s ease;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
.feature-modal-visible .feature-modal-container {
transform: scale(1) translateY(0);
}
.feature-modal-header {
display: flex;
justify-content: space-between;
align-items: centre;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.feature-modal-title {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
colour: var(--heading-colour, #1f2937);
}
.feature-modal-close {
background: none;
border: none;
font-size: 2rem;
cursor: pointer;
colour: #6b7280;
padding: 0;
width: 2rem;
height: 2rem;
display: flex;
align-items: centre;
justify-content: centre;
border-radius: 4px;
transition: all 0.2s ease;
}
.feature-modal-close:hover {
background: #f3f4f6;
colour: #374151;
}
.feature-modal-content {
padding: 1.5rem;
max-height: 60vh;
overflow-y: auto;
}
.feature-modal-footer {
padding: 1.5rem;
border-top: 1px solid #e5e7eb;
text-align: right;
}
.feature-modal-cta {
background: var(--link-colour, #0066cc);
colour: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
}
.feature-modal-cta:hover {
background: var(--link-hover-colour, #0052a3);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.feature-modal-container {
width: 95vw;
max-height: 95vh;
margin: 1rem;
}
.feature-modal-header,
.feature-modal-content,
.feature-modal-footer {
padding: 1rem;
}
.feature-modal-title {
font-size: 1.25rem;
}
}
/* Body scroll lock */
body.modal-open {
overflow: hidden;
}
/* Focus styles for accessibility */
.feature-modal-trigger:focus,
.feature-modal-close:focus,
.feature-modal-cta:focus {
outline: 2px solid var(--link-colour, #0066cc);
outline-offset: 2px;
}
Chapter 6: Testing and Debugging Mastery
The EDS Testing Philosophy: Real Conditions, Real Results
Testing EDS blocks requires a fundamentally different approach than testing traditional web applications. You're not testing isolated components in a controlled environment—you're testing enhancement layers that must work across varied content scenarios, different network conditions, and diverse user capabilities.
EDS testing is about ensuring graceful degradation and progressive enhancement work correctly. Your blocks must function when JavaScript loads slowly, when CSS fails to load, when images are blocked, when users have disabilities, and when content authors create unexpected content structures. This approach is what enables EDS sites to maintain perfect Lighthouse scores in production.
Why Traditional Testing Approaches Fall Short
Most web testing focuses on happy path scenarios: fast networks, modern browsers, perfect content, enabled JavaScript. But EDS blocks must work in the real world where conditions are unpredictable. A block that works perfectly in a controlled test environment might fail catastrophically when deployed to production with real user traffic.
EDS testing methodologies address this by simulating real-world conditions and edge cases. You'll test with throttled networks, disabled JavaScript, various content structures, and different user interaction patterns. This approach ensures your blocks enhance user experience rather than breaking it.
The Multi-Layered Testing Strategy
Effective EDS testing happens at multiple levels:
Structural Testing: Does your block handle different content structures gracefully?
Functional Testing: Do all interactive features work across browsers and devices?
Performance Testing: Does your block maintain EDS's performance standards?
Accessibility Testing: Can all users interact with your block effectively?
Degradation Testing: Does your block fail gracefully when components don't load?
EDS-Specific Testing Patterns
Testing EDS blocks requires understanding the framework's unique characteristics and building test scenarios that reflect real usage:
<!-- Full test file pattern -->
<!DOCTYPE html>
<html>
<head>
<title>Feature Modal - Test Suite</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="module" src="/scripts/aem.js"></script>
<link rel="stylesheet" href="/styles/styles.css">
<style>
.test-section {
margin: 2rem 0;
padding: 1.5rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
}
\
.debug-info {
background: #f3f4f6;
padding: 1rem;
border-radius: 4px;
margin: 1rem 0;
font-size: 0.875rem;
}
\
.test-controls {
margin: 1rem 0;
}
\
.test-button {
background: #059669;
colour: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
margin: 0 8px 8px 0;
cursor: pointer;
}
</style>
</head>
<body class="appear">
<main>
<div class="section">
<h1>Feature Modal - Test Suite</h1>
\
<!-- Basic functionality test -->
<div class="test-section">
<h2>Basic Modal Test</h2>
<div class="debug-info">
<strong>Expected behaviour:</strong> Modal opens and closes properly
</div>
\
<div class="feature-modal block" data-block-name="feature-modal" data-block-status="initialised">
<div>
<div>
<p>Open Demo Modal</p>
<p>Welcome to Our Service</p>
<p>This is a demonstration modal that showcases our feature. Click the button below to continue.</p>
<p>Get Started</p>
</div>
</div>
</div>
</div>
\
<!-- Accessibility test -->
<div class="test-section">
<h2>Accessibility Test</h2>
<div class="debug-info">
<strong>Test:</strong> Tab navigation, Escape key, focus management
</div>
<div class="test-controls">
<button class="test-button" onclick="testKeyboardNavigation()">Test Keyboard</button>
<button class="test-button" onclick="testFocusManagement()">Test Focus</button>
</div>
\
<div class="feature-modal block" data-block-name="feature-modal" data-block-status="initialised">
<div>
<div>
<p>Accessibility Test Modal</p>
<p>Focus Management Test</p>
<p>Test tab navigation and escape key functionality</p>
<p>Close</p>
</div>
</div>
</div>
</div>
\
<!-- Error handling test -->
<div class="test-section">
<h2>Error Handling Test</h2>
<div class="debug-info">
<strong>Test:</strong> Malformed content handling
</div>
\
<div class="feature-modal block" data-block-name="feature-modal" data-block-status="initialised">
<div>
<div>
<!-- Minimal content to test error handling -->
<p>Minimal Modal</p>
</div>
</div>
</div>
</div>
</div>
</main>
<script type="module">
import decorate from './feature-modal.js';
\
document.addEventListener('DOMContentLoaded', () => {
// Make body appear (EDS requirement)
document.body.classList.add('appear');
\
// Initialise all modal blocks
const blocks = document.querySelectorAll('.feature-modal.block');
blocks.forEach(decorate);
});
\
// Test functions
window.testKeyboardNavigation = function() {
console.log('🧪 Testing keyboard navigation...');
\
const trigger = document.querySelector('.feature-modal-trigger');
if (trigger) {
trigger.focus();
console.log('✅ Trigger focused');
\
// Simulate Enter key
trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
trigger.click();
\
setTimeout(() => {
// Test Escape key
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
console.log('✅ Escape key tested');
}, 500);
}
};
\
window.testFocusManagement = function() {
console.log('🧪 Testing focus management...');
\
const trigger = document.querySelector('.feature-modal-trigger');
if (trigger) {
const originalFocus = document.activeElement;
trigger.click();
\
setTimeout(() => {
const modalFocused = document.activeElement;
console.log('✅ Modal focused element:', modalFocused.className);
\
// Close modal
const closeBtn = document.querySelector('.feature-modal-close');
if (closeBtn) {
closeBtn.click();
\
setTimeout(() => {
console.log('✅ Focus returned to:', document.activeElement.className);
}, 100);
}
}, 100);
}
};
</script>
</body>
</html>
Advanced Debugging Techniques
// Debug utility for EDS blocks
const DEBUG_MODE = window.location.hostname === 'localhost' &&
window.location.port === '3000';
function debugLog(blockName, message, data = null) {
if (DEBUG_MODE) {
console.log(`[${blockName.toUpperCase()}] ${message}`, data || '');
}
}
function debugBlock(block, blockName) {
if (DEBUG_MODE) {
console.group(`🔍 Debugging ${blockName}`);
console.log('Block element:', block);
console.log('Block classes:', block.className);
console.log('Block dataset:', block.dataset);
console.log('Block content:', block.innerHTML);
console.log('Block dimensions:', {
width: block.offsetWidth,
height: block.offsetHeight
});
console.groupEnd();
}
}
// Enhanced decorate function with debugging
export default function decorate(block) {
debugBlock(block, 'feature-modal');
try {
debugLog('feature-modal', 'Starting decoration');
\
// ... your block logic here ...
\
debugLog('feature-modal', 'Decoration completed successfully');
\
} catch (error) {
debugLog('feature-modal', 'Decoration failed', error);
console.error('[Feature Modal] Error:', error);
showFallbackContent(block);
}
}
Chapter 7: Performance Enhancement
The Performance Imperative: Why Speed Defines Success
Performance isn't optional in EDS—it's fundamental to the platform's value proposition. EDS exists because traditional CMS and framework solutions consistently fail to deliver the fast, lightweight experiences that modern users demand. Every architectural decision in EDS prioritises performance, and your blocks must continue this tradition.
When we talk about performance in EDS, we're not just talking about load times. We're talking about Core Web Vitals, user experience metrics that directly impact search rankings, conversion rates, and user satisfaction. EDS sites consistently achieve perfect 100/100/100/100 Lighthouse scores not by accident, but through careful architectural choices at every level.
Understanding the Performance Ecosystem
EDS performance comes from systematic enhancement across multiple layers:
Architecture Level: No framework overhead, minimal JavaScript, CSS-only animations
Loading Level: Progressive enhancement, critical resource prioritisation, intelligent caching
Runtime Level: Efficient DOM manipulation, minimal reflows, enhanced event handling
Content Level: Enhanced images, lazy loading, content-driven code splitting
Your blocks participate in this ecosystem. Every line of JavaScript, every CSS rule, every DOM manipulation either contributes to or detracts from the overall performance profile. Understanding this responsibility is crucial for EDS development.
The Core Web Vitals Challenge
Core Web Vitals measure user-perceived performance through three key metrics:
Largest Contentful Paint (LCP): How quickly the main content becomes visible
First Input Delay (FID): How quickly the page responds to user interactions
Cumulative Layout Shift (CLS): How much the page layout shifts during loading
Traditional frameworks struggle with these metrics because they prioritise developer experience over user experience. EDS inverts this priority, and your blocks must follow suit.
Core Web Vitals for EDS Blocks
Understanding how your blocks impact Core Web Vitals is essential for maintaining EDS's performance advantages:
// Performance-enhanced block pattern
export default async function decorate(block) {
// 1. Show content immediately (LCP enhancement)
showImmediateContent(block);
// 2. Defer heavy operations (FID enhancement)
requestIdleCallback(() => {
enhanceWithAdvancedFeatures(block);
});
// 3. Avoid layout shifts (CLS enhancement)
reserveSpace(block);
}
function showImmediateContent(block) {
// Extract and show basic content immediately
const content = extractBasicContent(block);
const basicStructure = createBasicStructure(content);
block.innerHTML = '';
block.appendChild(basicStructure);
}
function reserveSpace(block) {
// Set dimensions to prevent layout shift
const container = block.querySelector('.feature-container');
if (container) {
container.style.minHeight = '200px'; // Reserve space
}
}
// Lazy loading images efficiently
function createEnhancedImage(src, alt) {
const img = document.createElement('img');
img.loading = 'lazy';
img.decoding = 'async';
img.src = src;
img.alt = alt;
// Prevent layout shift
img.style.aspectRatio = '16/9';
img.style.objectFit = 'cover';
return img;
}
// Efficient event handling
function setupEfficientEventHandlers(container) {
// Use event delegation instead of individual handlers
container.addEventListener('click', (e) => {
if (e.target.matches('.button-primary')) {
handlePrimaryAction(e);
} else if (e.target.matches('.button-secondary')) {
handleSecondaryAction(e);
}
});
// Use passive listeners where possible
container.addEventListener('scroll', handleScroll, { passive: true });
}
Memory Management
// Proper cleanup for EDS blocks
class BlockManager {
constructor(block) {
this.block = block;
this.cleanup = [];
this.observers = [];
}
addEventHandler(element, event, handler, options) {
element.addEventListener(event, handler, options);
\
// Store cleanup function
this.cleanup.push(() => {
element.removeEventListener(event, handler, options);
});
}
addIntersectionObserver(callback, options) {
const observer = new IntersectionObserver(callback, options);
this.observers.push(observer);
return observer;
}
destroy() {
// Clean up event listeners
this.cleanup.forEach(cleanup => cleanup());
\
// Disconnect observers
this.observers.forEach(observer => observer.disconnect());
\
// Clear references
this.cleanup = [];
this.observers = [];
}
}
// Usage in block
export default function decorate(block) {
const manager = new BlockManager(block);
// Store manager reference for cleanup
block._manager = manager;
// Setup with automatic cleanup tracking
const button = block.querySelector('button');
manager.addEventHandler(button, 'click', handleClick);
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
if (block._manager) {
block._manager.destroy();
}
});
}
Chapter 8: Accessibility Excellence
Accessibility as Foundation, Not Afterthought
Accessibility in EDS isn't a compliance checkbox—it's a fundamental design principle that improves the experience for everyone. When you build with accessibility from the ground up, you create more strong, more usable, and more maintainable code. EDS's content-first philosophy naturally supports accessibility because it starts with semantic content and enhances progressively.
The misconception that accessibility limits design creativity is exactly backwards. Proper accessibility practices force you to think more clearly about user interactions, information hierarchy, and progressive enhancement. These constraints lead to better design decisions and more resilient code.
Why EDS and Accessibility Are Natural Partners
EDS's architectural principles align perfectly with accessibility best practices:
Semantic Foundation: Content starts as proper HTML elements with inherent accessibility
Progressive Enhancement: Core functionality works without JavaScript, enhanced features layer on top
Performance Focus: Fast loading benefits everyone, especially users with disabilities
Device Independence: Works across assistive technologies and input methods
This alignment means that following EDS patterns naturally creates more accessible experiences. But it also means you have a responsibility to maintain these accessibility benefits as you add enhancement layers.
The Accessibility Mindset Shift
Most developers think about accessibility as adding features for disabled users. This is wrong. Accessibility is about creating flexible interfaces that work for the widest range of human capabilities and interaction preferences. When you design for screen readers, you improve keyboard navigation. When you enhance for motor disabilities, you improve mobile touch interactions. When you design for cognitive differences, you improve usability for everyone.
In EDS development, this means thinking about accessibility at every decision point: How will this enhancement work for keyboard users? How will screen readers interpret this content? How will users with motor difficulties interact with this interface? How will people with cognitive differences understand this flow?
Full Accessibility Implementation
Building truly accessible EDS blocks requires understanding and implementing multiple layers of accessibility support:
// Full accessibility implementation
export default function decorate(block) {
const accessibleStructure = createAccessibleStructure(block);
setupAccessibilityFeatures(accessibleStructure);
block.innerHTML = '';
block.appendChild(accessibleStructure);
}
function createAccessibleStructure(block) {
const container = document.createElement('div');
container.className = 'accessible-component';
// Semantic structure
const main = document.createElement('main');
main.setAttribute('role', 'main');
main.setAttribute('aria-label', 'Component content');
// Heading hierarchy
const heading = document.createElement('h2');
heading.id = 'component-heading';
heading.textContent = 'Component Title';
// Interactive elements with proper ARIA
const button = document.createElement('button');
button.setAttribute('aria-describedby', 'component-description');
button.setAttribute('aria-expanded', 'false');
// Description for screen readers
const description = document.createElement('div');
description.id = 'component-description';
description.className = 'sr-only';
description.textContent = 'Click to expand component details';
// Live region for dynamic updates
const liveRegion = document.createElement('div');
liveRegion.setAttribute('aria-live', 'polite');
liveRegion.setAttribute('aria-atomic', 'true');
liveRegion.className = 'sr-only';
main.appendChild(heading);
main.appendChild(description);
main.appendChild(button);
main.appendChild(liveRegion);
container.appendChild(main);
return container;
}
function setupAccessibilityFeatures(container) {
const button = container.querySelector('button');
const liveRegion = container.querySelector('[aria-live]');
// Keyboard navigation
container.addEventListener('keydown', (e) => {
switch (e.key) {
case 'Enter':
case ' ':
if (e.target === button) {
e.preventDefault();
button.click();
}
break;
case 'Escape':
closeComponent();
break;
}
});
// Screen reader announcements
function announceChange(message) {
liveRegion.textContent = message;
\
// Clear after announcement
setTimeout(() => {
liveRegion.textContent = '';
}, 1000);
}
// Focus management
function manageFocus() {
const focusableElements = container.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
\
focusableElements.forEach((element, index) => {
element.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
// Custom tab order if needed
}
});
});
}
manageFocus();
}
CSS for Accessibility
/* Screen reader only content */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.component-button {
border: 2px solid;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.component-container {
animation: none;
transition: none;
}
}
/* Focus indicators */
.component-button:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Colour blindness considerations */
.component-status {
/* Don't rely only on colour */
background: #28a745;
colour: white;
}
.component-status::before {
content: "✓ ";
font-weight: bold;
}
Chapter 9: Troubleshooting Common Issues
The Reality of EDS Development: When Things Go Wrong
Even with perfect understanding of EDS principles, you'll encounter issues. This isn't a failure—it's an inevitable part of working with a sophisticated system that bridges content creation, web standards, and performance enhancement. The key is developing systematic approaches to identify, understand, and resolve issues quickly.
EDS troubleshooting is different from traditional web debugging because you're working with a content-first system that has specific architectural constraints. Issues often stem from misunderstanding these constraints rather than bugs in your code. Learning to recognise and resolve these misunderstandings will make you a more effective EDS developer.
The Systematic Debugging Approach
Effective EDS troubleshooting follows a systematic approach that moves from broad system checks to specific implementation details:
System Level: Is EDS itself working? Are core files loading?
Architecture Level: Does your block follow EDS structural requirements?
Implementation Level: Are your JavaScript and CSS working as expected?
Content Level: Does your block handle the specific content structure correctly?
Environment Level: Are development server and proxy working properly?
This systematic approach prevents the common mistake of diving into code details before confirming that the foundational systems are working correctly.
Understanding EDS-Specific Error Patterns
EDS has unique error patterns that stem from its architectural choices. Understanding these patterns helps you diagnose issues quickly and avoid common pitfalls:
Dynamic Loading Issues: Problems with EDS's automatic CSS/JS loading
Content Structure Issues: Blocks that don't handle EDS's nested div structure
Proxy and Development Issues: Problems with the local development server
CSS Specificity Issues: Conflicts with EDS's CSS architecture
Progressive Enhancement Issues: Blocks that don't degrade gracefully
Let's explore the most common issues and their systematic solutions:
Issue 1: Block Not Loading
This is the most common issue for new EDS developers, and it usually stems from misunderstanding EDS's block structure requirements.
Symptoms:
- Block appears as plain content
- No styling applied
- JavaScript not executing
Debugging Steps:
// 1. Check block structure
console.log('Block exists:', !!document.querySelector('.my-block.block'));
console.log('Block classes:', document.querySelector('.my-block.block')?.className);
// 2. Check file loading
const cssLinks = document.querySelectorAll('link[rel="stylesheet"]');
cssLinks.forEach(link => {
console.log('CSS loaded:', link.href, link.sheet ? '✅' : '❌');
});
// 3. Check JavaScript errors
window.addEventListener('error', (e) => {
console.error('JavaScript error:', e.error);
});
// 4. Check EDS initialisation
console.log('EDS scripts loaded:', {
aem: !!window.hlx,
blocks: !!window.hlx?.codeBasePath
});
Common Solutions:
<!-- ✅ Correct block structure -->
<div class="my-block block" data-block-name="my-block" data-block-status="initialised">
<div>
<div>Content here</div>
</div>
</div>
<!-- ❌ Wrong: Missing .block class -->
<div class="my-block" data-block-name="my-block">
<!-- ❌ Wrong: Incorrect data attributes -->
<div class="my-block block" data-block="my-block">
Issue 2: CSS Not Applied
Debugging CSS Loading:
// Check if CSS file loaded
function checkCSSLoading(blockName) {
const expectedCSS = `/blocks/${blockName}/${blockName}.css`;
const cssLink = document.querySelector(`link[href="${expectedCSS}"]`);
if (!cssLink) {
console.error(`❌ CSS not loaded: ${expectedCSS}`);
return false;
}
if (!cssLink.sheet) {
console.error(`❌ CSS file empty or failed to load: ${expectedCSS}`);
return false;
}
console.log(`✅ CSS loaded successfully: ${expectedCSS}`);
return true;
}
// Check CSS rules applied
function checkCSSRules(element, property) {
const computed = window.getComputedStyle(element);
const value = computed.getPropertyValue(property);
console.log(`CSS ${property}:`, value);
return value;
}
Issue 3: Server Proxy Issues
Server Debugging:
# Check server logs for proxy issues
Request: GET /missing-file.json
Local file not found, attempting proxy for: /missing-file.json
❌ Error proxying request for /missing-file.json: fetch failed
# Solutions:
1. Check internet connection
2. Verify proxy URL in server.js
3. Check if remote file exists
4. Look for CORS issues
Issue 4: Modal/Overlay Not Appearing
Common Modal Issues:
// Debug modal visibility
function debugModal() {
const modal = document.querySelector('.modal-overlay');
if (!modal) {
console.error('❌ Modal element not found');
return;
}
console.log('Modal element:', modal);
console.log('Modal computed styles:', {
display: window.getComputedStyle(modal).display,
visibility: window.getComputedStyle(modal).visibility,
opacity: window.getComputedStyle(modal).opacity,
zIndex: window.getComputedStyle(modal).zIndex
});
// Check if body has modal-open class
console.log('Body classes:', document.body.className);
// Check for JavaScript errors
console.log('Modal click handlers:', modal.onclick ? '✅' : '❌');
}
// Common fixes
function fixModalIssues() {
// 1. Ensure modal is added to body, not block
const modal = createModal();
document.body.appendChild(modal); // Not block.appendChild(modal)
// 2. Check z-index stacking
modal.style.zIndex = '9999';
// 3. Ensure proper CSS transition
modal.classList.add('modal-visible');
// 4. Clear localStorage if it's preventing display
localStorage.removeItem('modal-dismissed');
}
Chapter 10: Advanced Deployment and Production
From Development to Production: The Final Mile
Deploying EDS blocks to production is deceptively simple—you copy files to a server—but ensuring production success requires careful preparation, validation, and monitoring. Production is where your architectural choices, performance enhancements, and accessibility implementations are tested by real users under real conditions.
The transition from development to production in EDS is unique because there's no build process to catch errors, no bundling step to enhance assets, and no deployment pipeline to validate functionality. This simplicity is powerful, but it places responsibility squarely on your development practices and testing procedures.
Why Production Readiness Matters More in EDS
In traditional frameworks, build processes and deployment pipelines catch many issues before they reach users. EDS's direct deployment approach means that what you develop is exactly what users experience. This direct relationship between development and production has profound implications:
Performance Impact: Every byte of CSS and JavaScript affects user experience directly
Error Handling: Runtime errors can't be caught by build tools—they must be prevented by code quality
Compatibility: Browser and device issues must be caught during development testing
Accessibility: No tooling will enforce accessibility—it must be built into your development process
The Production Environment Difference
Production environments introduce variables that development can't fully simulate:
Scale: Real user traffic patterns and concurrent usage
Content Diversity: Content authors creating unexpected content structures
Network Conditions: Slow connections, intermittent connectivity, geographic latency
Device Diversity: Screen readers, older browsers, mobile devices, unusual viewport sizes
Integration Complexity: Interaction with analytics, marketing tools, content management systems
Successful EDS deployment requires anticipating and preparing for these production realities.
Production Checklist
Before deploying your EDS block to production, systematic validation across multiple dimensions ensures user success:
// Production readiness checklist
const PRODUCTION_CHECKLIST = {
performance: {
coreWebVitals: 'Lighthouse score 100/100/100/100',
imageEnhancement: 'All images enhanced and lazy loaded',
codeMinification: 'JavaScript and CSS minified',
bundleSize: 'Total block size < 50KB'
},
accessibility: {
screenReader: 'Tested with NVDA/VoiceOver',
keyboardNavigation: 'All interactive elements accessible',
colourContrast: 'WCAG AA compliance verified',
focusManagement: 'Proper focus indicators and trapping'
},
compatibility: {
browsers: 'Tested on Chrome, Firefox, Safari, Edge',
mobile: 'Responsive design verified',
slowConnections: 'Works on 3G connections',
javascriptDisabled: 'Graceful degradation implemented'
},
security: {
xss: 'Input sanitisation implemented',
contentSecurity: 'CSP compliance verified',
dataValidation: 'All user inputs validated',
errorHandling: 'No sensitive data in error messages'
}
};
// Automated production validation
function validateProductionReadiness(blockName) {
console.group(`🚀 Production Readiness: ${blockName}`);
// Check bundle size
const blockJS = `/blocks/${blockName}/${blockName}.js`;
const blockCSS = `/blocks/${blockName}/${blockName}.css`;
fetch(blockJS).then(response => {
const sizeKB = (response.headers.get('content-length') / 1024).toFixed(2);
console.log(`JavaScript size: ${sizeKB}KB`);
});
fetch(blockCSS).then(response => {
const sizeKB = (response.headers.get('content-length') / 1024).toFixed(2);
console.log(`CSS size: ${sizeKB}KB`);
});
// Check accessibility
const block = document.querySelector(`.${blockName}.block`);
if (block) {
const ariaElements = block.querySelectorAll('[aria-label], [aria-describedby], [role]');
console.log(`Accessibility attributes: ${ariaElements.length} found`);
}
console.groupEnd();
}
Deployment Best Practices
# 1. Test locally first
npm run debug
# Navigate to http://localhost:3000/blocks/your-block/test.html
# 2. Validate code quality
npm run lint
# 3. Test accessibility
# Use browser dev tools accessibility checker
# 4. Performance audit
# Run Lighthouse audit on test page
# 5. Cross-browser testing
# Test on multiple browsers and devices
# 6. Deploy to staging
# Test in staging environment with real content
# 7. Production deployment
# Deploy files to production server
Monitoring and Maintenance
// Add monitoring to production blocks
export default function decorate(block) {
try {
// Your block logic here
\
// Success monitoring
if (window.gtag) {
window.gtag('event', 'block_loaded', {
block_name: 'your-block',
status: 'success'
});
}
\
} catch (error) {
// Error monitoring
console.error('[Block] Error:', error);
\
if (window.gtag) {
window.gtag('event', 'block_error', {
block_name: 'your-block',
error_message: error.message
});
}
\
showFallbackContent(block);
}
}
// Performance monitoring
function monitorPerformance(blockName) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes(blockName)) {
console.log(`${blockName} loading time:`, entry.duration);
}
}
});
observer.observe({ entryTypes: ['resource'] });
}
Final Assessment and Next Steps
Mastery Checklist
Rate yourself on these competencies:
Foundation (Beginner)
✓ Understand EDS content-first philosophy
✓ Can create basic HTML test files
✓ Understand block naming conventions
✓ Can start and use development server
✓ Can create simple decorate functions
Intermediate
✓ Can build interactive blocks with event handlers
✓ Understand CSS architecture and responsive design
✓ Can implement proper error handling
✓ Can debug blocks using browser dev tools
✓ Can fetch and display external data
Advanced
✓ Can implement complex modal and overlay systems
✓ Master accessibility patterns and ARIA attributes
✓ Can enhance for Core Web Vitals
✓ Can implement focus management and keyboard navigation
✓ Can troubleshoot production issues
Expert
✓ Can architect dual-directory build systems
✓ Can implement performance monitoring
✓ Can create reusable patterns and utilities
✓ Can mentor others in EDS development
✓ Can contribute to EDS framework improvements
Your Next Project
Now that you've completed this training, choose a capstone project:
- E-commerce Product Gallery - Cards with filtering and modals
- Interactive Dashboard - Data visualisation with real-time updates
- Multi-step Form Wizard - Complex form with validation and progress
- Content Management Interface - CRUD operations with local storage
- Accessibility Showcase - Demonstrate all accessibility patterns
Continued Learning Resources
- EDS Official Documentation - Keep up with framework updates
- Web Standards - MDN Web Docs for HTML, CSS, JavaScript
- Accessibility - WCAG guidelines and testing tools
- Performance - Core Web Vitals and enhancement techniques
- Community - Join EDS developer forums and discussions
AI Instructor Final Words
Congratulations! You've completed the EDS block development training. You now have the knowledge to:
- Build sophisticated, accessible, and performant EDS blocks
- Debug issues efficiently using the development server
- Implement proper testing and validation workflows
- Deploy production-ready blocks that maintain EDS's performance standards
Remember the EDS philosophy: simplicity over complexity, performance over features, content over code structure. Every decision should serve the user experience first.
The web development landscape is constantly evolving, but the principles you've learned here—semantic HTML, progressive enhancement, accessibility-first design, and performance enhancement—are timeless.
Your journey as an EDS developer has just begun. Use these skills to create amazing web experiences that are fast, accessible, and delightful for all users.
Happy coding, and welcome to the EDS community! 🚀
This training document will continue to evolve. Bookmark it, reference it, and most importantly—build amazing things with EDS!
Related Articles