${item.title}
${item.description}
# Adobe Edge Delivery Services - Full Guide for Devs, Architects and AI # The Developer, Architect or AI Guide to Edge Delivery Services (EDS): From Document to Website ## Introduction Edge Delivery Services (EDS, formerly known as Franklin or Project Helix) represents a paradigm shift in content management systems. Unlike traditional CMSs that require content authors to adapt to the system's rigid structures, EDS flips this relationship—the system adapts to how authors naturally create content. This comprehensive guide examines how EDS transforms documents into high-performance websites, with a special focus on extending functionality without modifying core files. This post [formatted as markdown](https://allabout.network/blogs/ddt/adobe-edge-delivery-services-full-guide-for-devs-architects-and-ai.md) can assist in building EDS Components using AI tools like cursor and claude. ## Core Philosophy and Requirements ### The Content-First Philosophy At its core, EDS embraces a content-first approach that radically simplifies the authoring process. Content creators work in familiar tools like Documents or Microsoft Word, while the system handles the technical transformation into structured web pages. This separation of concerns allows: - Content authors to focus on writing in familiar environments - Developers to build functionality without disrupting content workflows - Both teams to work simultaneously, accelerating delivery As a developer working with EDS, understanding this philosophy is crucial—your job isn't to build a website from scratch, but to enhance how documents transform into web experiences. ### Development Requirements and Constraints Before diving into EDS, it's important to understand its core development philosophy and constraints. These aren't limitations but deliberate design choices that promote simplicity, performance, and maintainability: - **Modern JavaScript without TypeScript**: EDS relies on vanilla JavaScript, avoiding transpilation complexity - **Pure CSS without preprocessors**: Direct CSS keeps things simple and performant - **No build-heavy frameworks**: Skip React, Vue, and other frameworks that add complexity - **Focus on simplicity and performance**: Every decision prioritizes page speed and core web vitals - **Clear code organization**: Structured, well-documented code is essential - **No dependencies and no build steps**: Zero build process means faster development and fewer points of failure These requirements enable EDS to achieve perfect Core Web Vitals scores (100/100/100/100) by eliminating the overhead traditionally associated with modern web development. This approach is increasingly rare but remarkably effective—letting developers focus on solving real problems rather than managing toolchains. ## The Document Transformation Journey Let's follow a document's complete journey from creation to final rendered webpage. This transformation process is at the heart of how EDS works, and understanding it will help you see where and how to extend functionality as a developer. ### Stage 1: Document Creation The journey begins with a content author creating or editing a document in Google Docs (or Microsoft Word). In this familiar environment, authors naturally structure their content using: - Headings and subheadings (H1-H6) to organize information hierarchically - Paragraphs with rich text formatting (bold, italic, underline) - Lists (ordered and unordered) for structured information - Images embedded directly in the document - Links to internal and external resources - Text formatting for emphasis and organization Authors can also use EDS-specific features: - Section breaks (horizontal rules with ---) to divide content into logical sections - Tables with specific headers to create specialized blocks like "Columns" or "Cards" - Metadata tables with key-value pairs to define page properties for SEO and configuration For example, a typical document might include a hero section with a main heading, followed by several content sections divided by horizontal rules, and special blocks created with tables. ### Stage 2: Document to Markdown Conversion (Server-Side) When an author presses "Preview" in the Sidekick tool, the transformation begins: **Document Retrieval** - EDS accesses the document via Google Docs or SharePoint API - The raw document content is extracted for processing **Structural Analysis** - The system analyzes the document structure - It identifies headings, paragraphs, tables, and other elements - Special elements like metadata tables are recognized **Markdown Conversion** - The document is systematically converted to Markdown format - Document structure is preserved in Markdown syntax - Tables are specially processed as potential block components - Images are extracted and stored separately **Markdown Storage** - The generated Markdown becomes the source of truth for page content - It's stored in the "content bus" for versioning and future access - This separation allows content and code to evolve independently The Markdown representation serves as an intermediate format that bridges the gap between the document-based authoring experience and web delivery. ### Stage 3: Markdown to Initial HTML Generation (Server-Side) Once the Markdown is prepared, EDS transforms it into basic HTML: **HTML Structure Creation** - The server processes the Markdown to generate HTML - A semantic document structure is created with appropriate HTML elements - Headings become `
` elements - Lists become `
` elements on the page:
```
/**
* Minimal Code Expander Block
* This component enhances pre/code elements on the page with:
* Copy to clipboard functionality - One-click copy with visual feedback
* Expand/collapse for long code blocks - Toggles visibility for better readability
* The component works by finding all pre/code elements, wrapping them in a custom
* container with controls, and adding event listeners for the interactive features.
* No syntax highlighting is applied - all code is displayed as plain text.
*/
export default async function decorate(block) {
// Configuration values for the component.
// Control appearance, behavior thresholds, and text labels.
const THRESHOLD = 40; // Line count for a "long" code block.
const COPY_TEXT = 'Copy';
const COPIED_TEXT = 'Copied!';
const EXPAND_TEXT = 'Expand';
const COLLAPSE_TEXT = 'Collapse';
// Locate all code blocks on the page.
const codeElements = document.querySelectorAll('pre code');
if (codeElements.length === 0) return;
Array.from(codeElements).forEach((codeElement, index) => {
// Skip if the element is already processed.
if (codeElement.closest('.code-expander-wrapper')) return;
// Extract code content and determine if it's a long document.
const code = codeElement.textContent;
const isLongDocument = code.split('\n').length > THRESHOLD;
// Create a wrapper for the enhanced code block.
const wrapper = document.createElement('div');
wrapper.className = 'code-expander-wrapper';
wrapper.dataset.codeIndex = index;
// Create a header that will contain control buttons.
const header = document.createElement('div');
header.className = 'code-expander-header';
// Create a container for control buttons.
const buttonGroup = document.createElement('div');
buttonGroup.className = 'code-expander-buttons';
// Conditionally add expand/collapse button for long code blocks.
let expandButton = null;
if (isLongDocument) {
expandButton = document.createElement('button');
expandButton.className = 'code-expander-expand-collapse';
expandButton.textContent = EXPAND_TEXT;
// Toggle expansion state on button click.
expandButton.onclick = () => {
newPreElement.classList.toggle('expanded');
expandButton.textContent = newPreElement.classList.contains('expanded')
? COLLAPSE_TEXT
: EXPAND_TEXT;
};
buttonGroup.appendChild(expandButton);
}
// Add a copy button with clipboard functionality.
const copyButton = document.createElement('button');
copyButton.className = 'code-expander-copy';
copyButton.textContent = COPY_TEXT;
copyButton.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(code);
copyButton.textContent = COPIED_TEXT;
setTimeout(() => {
copyButton.textContent = COPY_TEXT;
}, 2000);
} catch (err) {
console.error('Error copying content:', err);
}
});
buttonGroup.appendChild(copyButton);
// Assemble header and wrapper.
header.appendChild(buttonGroup);
wrapper.appendChild(header);
// Create a new pre element for the enhanced display.
const newPreElement = document.createElement('pre');
if (isLongDocument) newPreElement.classList.add('collapsible');
const newCodeElement = document.createElement('code');
newCodeElement.textContent = code;
newPreElement.appendChild(newCodeElement);
wrapper.appendChild(newPreElement);
// Replace the original code block with the enhanced version.
const preElement = codeElement.parentNode;
const container = document.createElement('div');
preElement.parentNode.replaceChild(container, preElement);
container.appendChild(wrapper);
});
}
```
Note the key aspects of this implementation:
- Non-invasive approach: It enhances existing elements without modifying core files
- Configuration at the top: All configurable values are defined as constants at the top
- Thorough documentation: Clear comments explain the purpose and function
- Robust error handling: Try/catch blocks prevent failures from breaking the page
- Clean modularity: Each function has a single responsibility
- Performance consciousness: Uses modern APIs and efficient DOM operations
This pattern allows you to extend functionality without the risks associated with modifying core files.
When developing with EDS, you should follow specific coding standards to ensure your code is maintainable and compatible with future updates:
```
// Use configuration constants at the top of your code
const BLOCKNAME_CONFIG = {
BOOK_TITLE: 'Code',
ERROR_MESSAGE: 'Error loading content. Please try again.',
COPY_BUTTON_RESET_DELAY: 2000,
LONG_DOCUMENT_THRESHOLD: 40,
// Add other configuration options here
};
```
When writing code that uses console output, remember to precede it with:
```
// eslint-disable-next-line no-console
console.log('Debug information');
```
This prevents ESLint errors while maintaining the ability to use console logging for debugging purposes.
## Common Implementation Challenges and Solutions
Teams developing with EDS often encounter similar challenges. Here are practical solutions to common problems:
### Challenge: Analytics Implementation
**Problem**: Adding analytics without modifying core files.
**Solution**: Use the delayed.js file for analytics scripts:
```
// In delayed.js
(function() {
// Create script element
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXXX-X';
document.head.appendChild(script);
// Initialize analytics
window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'UA-XXXXXXXX-X');
// Add custom event tracking
document.addEventListener('click', e => {
const target = e.target.closest('a, button');
if (!target) return;
const trackingData = {
event_category: target.tagName.toLowerCase(),
event_label: target.innerText || target.textContent,
};
if (target.href) {
trackingData.outbound = !target.href.includes(window.location.hostname);
}
gtag('event', 'click', trackingData);
});
})();
```
### Challenge: Dynamic Content from External APIs
**Problem**: Incorporating content from external services.
When integrating with external data sources or the EDS query-index.json, you should follow a consistent pattern for data structures. Here's an example of the expected JSON structure for data integration:
```
{
"total": 1,
"offset": 0,
"limit": 1,
"data": [
{
"path": "/example-path",
"title": "Example Title",
"image": "/path/to/image.jpg",
"description": "Example description",
"lastModified": "1724942455"
}
],
"type": "sheet"
}
```
**Solution**: Create a specialized block that fetches and formats the content:
```
import readBlockConfig from 'scripts/aem.js';
export default async function decorate(block) {
const config = readBlockConfig(block);
const apiUrl = config.apiUrl || 'https://api.example.com/data';
try {
// Clear initial content.
block.textContent = '';
// Show loading state.
const loading = document.createElement('div');
loading.className = 'loading-indicator';
loading.textContent = 'Loading content...';
block.appendChild(loading);
// Fetch data from the API.
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`API returned ${response.status}`);
}
const data = await response.json();
// Remove loading state.
loading.remove();
// Render dynamic content.
const container = document.createElement('div');
container.className = 'dynamic-content';
data.items.forEach((item) => {
const itemEl = document.createElement('div');
itemEl.className = 'dynamic-item';
const title = document.createElement('h3');
title.textContent = item.title;
itemEl.appendChild(title);
const desc = document.createElement('p');
desc.textContent = item.description;
itemEl.appendChild(desc);
container.appendChild(itemEl);
});
block.appendChild(container);
} catch (error) {
console.error('Error loading dynamic content:', error);
// Clear block and display error state.
block.textContent = '';
const errorEl = document.createElement('div');
errorEl.className = 'error-state';
errorEl.textContent = 'Unable to load content. Please try again later.';
block.appendChild(errorEl);
}
}
```
### Challenge: Cookie Consent Implementation
**Problem**: Implementing cookie consent without modifying core files.
**Solution**: Create a cookie-consent block that's included in the template:
```
export default function decorate(block) {
// Hide the original block
block.style.display = 'none';
// Check if consent already given
if (localStorage.getItem('cookie-consent') === 'accepted') {
// Consent already given, enable cookies/tracking
enableTracking();
return;
}
// Create consent banner
const banner = document.createElement('div');
banner.className = 'cookie-consent-banner';
banner.innerHTML = `
## CSS Injection and Selector Patterns
When developing blocks in EDS, remember that block-specific CSS is injected only when the block is present on the page. This means you don't need to use complex selectors like \`:has()\` or sibling selectors (\`\~\`, \`+\`) to target elements when the block is present. Instead, use direct selectors since the CSS will only be loaded when the block exists. For example, if you need to hide the header and footer when your block is present, simply use \`header, footer { display: none; }\` rather than complex selectors like \`header:has(+ .your-block)\`. This approach is more maintainable, performs better, and follows the principle that block CSS is scoped to its specific use case.
### More CSS Rules
- **Block Isolation**: Every CSS selector in a block's CSS should only apply to that block
- **Structure with Flexbox/Grid**: Modern CSS layout techniques create responsive designs
- **Mobile-First Approach**: Base styles for mobile, then add media queries for larger screens
- **Consistent Class Naming**: Follow patterns like .blockname-element-state
- **Use CSS Variables**: Leverage custom properties for consistent theming
- **Never Style Container**: Don't apply styles directly to .blockname-container
- **Support Variations**: Handle block variations through class combinations
- **Ensure Responsiveness**: Include responsive design for different screen sizes
```
/* Block-specific styles should be isolated */
.myblock {
/* Only apply to elements within .myblock */
padding: 20px;
}
/* Don't let selectors become too complex */
.myblock > div > div > p {
/* AVOID: Too specific and fragile */
}
/* Mobile-first approach without initial media queries */
myblock.columns {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
/* Standard breakpoints at 600px, 900px, 1200px */
@media (min-width: 600px) {
.columns {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 900px) {
.columns {
grid-template-columns: repeat(3, 1fr);
}
}
/* Use CSS variables for theming and configuration */
:root {
--primary-color: #1473e6;
--heading-font: 'Adobe Clean', sans-serif;
--body-font: 'Adobe Clean', sans-serif;
--spacing-m: 16px;
}
myblock.button {
background-color: var(--primary-color);
font-family: var(--body-font);
padding: var(--spacing-m);
}
```
Key principles to follow:
- **Simplicity**: "Don't let your CSS Selectors become complex and unreadable"
- **Mobile First**: "Your CSS without media query should render a mobile site"
- **Standard Breakpoints**: "Generally use 600px, 900px and 1200px as your breakpoints, all min-width"
- **Variable-Based Configuration**: Use CSS variables for theme colors, spacing, and other configurable values
- **Block Isolation:** Every CSS selector in the .css of a block only applies to the block Container elements in EDS must never have styles applied to them directly. While containers (elements with class names ending in -container) are an expected part of the EDS architecture and serve important structural purposes, applying CSS to these elements violates core EDS principles and creates maintenance challenges. Developers should treat the -container suffix as a signal that an element is for structure only and should remain visually unstyled.
- **Create properly named elements** inside containers instead of styling the containers themselves, when styles need to be applied to large content areas,. For example, rather than styling a blockname-container, create a blockname-area or blockname-content element within it to hold those styles. This pattern maintains clean separation between document structure and visual presentation, making blocks more maintainable and compatible with future EDS updates. A class name blockname is obviously ok, it will not conflict.
- **Always namespace class names with the block's name to prevent conflicts** with other components or global styles. Avoid using generic class names like .header, .content, or .area, which could easily conflict with other blocks or page elements. Instead, follow the pattern of blockname-feature-state (e.g., slideshow-button-active rather than just button-active). This namespacing ensures your styles remain isolated to your block and prevents unexpected behavior when multiple blocks are used on the same page.
### EDS JavaScript Best Practices
The JavaScript approach in EDS emphasizes simplicity and performance. It's recommended to adhere to Airbnb's JavaScript Style Guide to ensure clean, maintainable code. This widely-adopted style guide provides consistent conventions for variable naming, function declarations, object creation, conditional statements, error handling, and comments.
```
// Configuration variables at the top
const CONFIG = {
ANIMATION_DURATION: 300,
MAX_ITEMS: 12,
API_ENDPOINT: '/query-index.json',
};
// Use async/await for cleaner asynchronous code
export default async function decorate(block) {
try {
// Fetch data asynchronously
const response = await fetch(CONFIG.API_ENDPOINT);
const data = await response.json();
// Process data
const filteredItems = data.data
.filter(item => item.type === 'blog')
.slice(0, CONFIG.MAX_ITEMS);
// Update UI
renderItems(block, filteredItems);
} catch (error) {
// Handle errors gracefully
// eslint-disable-next-line no-console
console.error('Error loading content:', error);
renderErrorState(block);
}
}
// Break logic into focused functions
function renderItems(block, items) {
// Clear existing content
block.textContent = '';
// Create container
const container = document.createElement('div');
container.className = 'items-container';
```
```
// Add items
items.forEach(item => {
container.appendChild(createItemElement(item));
});
```
```
// Add to DOM
block.appendChild(container);
}
function createItemElement(item) {
// Create element for single item
// ...
}
function renderErrorState(block) {
// Show user-friendly error state
// ...
}
```
Key principles:
- **Keep It Simple**: "Frameworks often introduce web performance issues... while trying to address trivial problems"
- **Modern Features**: "Make sure the features you are using are well supported by evergreen browsers"
- **Configuration at Top**: Place all configurable values in a config object at the top
- **Function Separation**: Each function should have a single responsibility
- **Error Handling**: Always include robust error handling
- **Asynchronous Best Practices**: Use async/await for cleaner asynchronous code
### Content Structure Best Practices
How content is structured in documents significantly impacts both authoring experience and website performance:
- **Minimize Blocks**: "Blocks are not great for authoring... It is definitely an anti-pattern to have things that are represented natively as default content and put them into a block." Use blocks only when necessary for specialized components.
* **No Nested Blocks**: "Nested blocks are definitely a lot worse" for authoring experience. Keep block structure flat for easier authoring.
- **Use Full URLs**: "Authors (and most humans) often think of a URL as an opaque token... It is always advisable to just let authors work with fully qualified URLs." Don't try to normalize or transform URLs unnecessarily.
* **Reuse Standards**: "The EDS Block Collection is a great source for well designed content models." Don't reinvent the wheel—use existing patterns when possible.
- **Progressive Enhancement**: Start with the simplest possible implementation and enhance as needed, rather than building complex solutions from the start.
### Documentation Best Practices
Comprehensive documentation is crucial for block maintainability:
```
/blocks/myblock/
├── myblock.js
├── myblock.css
├── README.md # Comprehensive documentation
├── example.md # Simple copy-paste example for devs
└── demo.md # More complex real-world usage examples
```
README.md should include:
- Purpose and functionality
- Configuration options
- Usage instructions for authors
- Accessibility considerations
- Performance impact
- Variations and examples
This comprehensive documentation approach ensures both developers and content authors can effectively work with your blocks.
## Advanced Techniques
### Dynamic Content with Query Index
EDS provides a powerful indexing system for creating dynamic content:
```
async function loadContent() {
try {
// Fetch the indexed content from the endpoint.
const response = await fetch('/query-index.json');
if (!response.ok) {
throw new Error(`Failed to fetch data: ${response.status}`);
}
// Await the parsing of the JSON response.
const data = await response.json();
// Filter for items with a '/blogs/' path.
const filteredItems = data.data.filter((item) =>
item.path.includes('/blogs/')
);
// Sort items based on the lastModified date in descending order.
const sortedItems = filteredItems.sort((a, b) =>
new Date(b.lastModified) - new Date(a.lastModified)
);
// Generate HTML for each item.
const html = sortedItems
.map(
(item) => `
${item.title}
${item.description}
`
)
.join('');
// Update the container's innerHTML with the generated content.
container.innerHTML = html;
} catch (error) {
console.error('Error loading content:', error);
}
}
loadContent();
```
This technique is used in the BlogList block to create dynamic content listings without requiring authors to manually update links.
### Auto Blocking
Auto blocking programmatically creates blocks based on content patterns:
```
function buildHeroBlock(main) {
const h1 = main.querySelector('h1');
const picture = main.querySelector('picture');
// If h1 follows picture in the DOM
if (h1 && picture && (h1.compareDocumentPosition(picture) & Node.DOCUMENT_POSITION_PRECEDING)) {
const section = document.createElement('div');
section.append(buildBlock('hero', { elems: [picture, h1] }));
main.prepend(section);
}
}
```
This allows you to automatically enhance document patterns without requiring authors to create block tables.
## Important note
When creating, or modifying blocks, treat the following files as read-only
```
styles/
├── fonts.css
├── lazy-styles.css
└── styles.css
fonts/
├── roboto-bold.woff2
└── roboto-regular.woff2
scripts/
├── aem.js
├── delayed.js
└── scripts.js
```
## Block Examples
To help you get started with EDS development, here are some additional block implementations showcasing different patterns and techniques.
### Fortune Cookie Block
A simple block that fetches and displays random quotes:
```
export default async function decorate(block) {
const fortuneCookieElement = document.querySelector('.fortunecookie');
const url = '/data/cookies.json';
try {
const response = await fetch(url);
const data = await response.json();
const dataArray = data.data;
const randomIndex = Math.floor(Math.random() * dataArray.length);
const randomItem = dataArray[randomIndex];
const content = `${randomItem.key}: ${randomItem.value}
`;
fortuneCookieElement.innerHTML = content;
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error fetching the JSON data:', error);
}
}
```
This block demonstrates fetching JSON data and using it to update content dynamically.
### Index Block
A block that builds a table of contents for the page:
```
export default function decorate(block) {
const headers = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
const indexBlock = document.querySelector('.index');
// Create the index header
const indexHeader = document.createElement('div');
indexHeader.className = 'index-header';
indexHeader.innerHTML = `
Index
`;
// Create the index content container
const indexContent = document.createElement('div');
indexContent.className = 'index-content';
// Append the index header and content container to the index block
indexBlock.appendChild(indexHeader);
indexBlock.appendChild(indexContent);
let isIndexBuilt = false; // Flag to track if the index has been built
indexHeader.addEventListener('click', () => {
if (!isIndexBuilt) {
buildIndex();
isIndexBuilt = true; // Set the flag to true after building the index
indexContent.style.display = 'none';
}
if (indexContent.style.display === 'none') {
indexContent.style.display = 'block';
indexHeader.querySelector('.arrow').style.transform = 'rotate(-135deg)';
} else {
indexContent.style.display = 'none';
indexHeader.querySelector('.arrow').style.transform = 'rotate(45deg)';
}
});
function buildIndex() {
const indexContent2 = document.querySelector('.index-content');
const ul = document.createElement('ul');
headers.forEach((header, index) => {
const id = `header-${index}`;
header.id = id;
const li = document.createElement('li');
li.style.marginLeft = `${(parseInt(header.tagName[1], 10) - 1) * 20}px`;
const a = document.createElement('a');
a.href = `#${id}`;
a.textContent = header.textContent;
li.appendChild(a);
ul.appendChild(li);
});
indexContent2.innerHTML = '';
indexContent2.appendChild(ul);
}
}
```
The CSS for this block creates a collapsible interface:
```
.index {
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
cursor: pointer;
}
.index-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.arrow {
border: solid black;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 3px;
transition: transform 0.3s;
}
.down {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.index-content {
display: none;
margin-top: 10px;
}
.index-content ul {
list-style-type: none;
padding: 0;
}
.index-content ul li {
margin-bottom: 5px;
}
.index-content ul li a {
text-decoration: none;
color: #333;
}
```
This block demonstrates:
- Lazy initialization (only building the index when needed)
- Dynamic DOM manipulation
- Interactive UI elements
- Content discovery enhancement
### Return To Top Block
A utility block that provides a "scroll to top" button:
```
export default async function decorate(block) {
const returnToTopButton = document.querySelector('.returntotop');
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
returnToTopButton.style.display = 'block';
} else {
returnToTopButton.style.display = 'none';
}
});
// Scroll to top when the button is clicked
returnToTopButton.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
});
```
}
With accompanying CSS:
```
.returntotop {
position: fixed;
bottom: 20px;
right: 20px;
padding: 10px 20px;
background-color: #007BFF;
color: white;
cursor: pointer;
text-align: center;
border-radius: 5px;
text-decoration: none;
font-size: 14px;
display: none;
}
.returntotop:hover {
background-color: #0056b3;
}
```
This simple utility block demonstrates:
- Event handling
- Fixed positioning
- User experience enhancement
- Conditional display based on scroll position
These examples showcase the variety of functionality that can be implemented as blocks in Edge Delivery Services, from content enhancement to user interface improvements, all without modifying core files.
## Conclusion
### Document-First Content Management
EDS fundamentally reverses the traditional CMS paradigm—instead of forcing authors to adapt to technical constraints, it adapts technical implementation to how authors naturally work. This philosophical shift has profound implications:
- **Natural Authoring Flow**: Authors use familiar tools (Google Docs, Word) without needing to understand web technologies
- **Separation of Concerns**: Content creation and technical implementation remain cleanly separated
- **Focus on Content**: The emphasis stays on the content itself, not on the technical container
As the documentation states: "In an ideal situation the majority of content is authored outside of blocks, as introducing tables into a document makes it harder to read and edit." This content-first approach challenges the component-first mentality that dominates most CMS platforms.
### Simplicity Over Complexity
Throughout EDS's implementation, there's a relentless focus on simplicity:
- **No Build Process**: Direct JavaScript and CSS without transpilation or processing
- **No Dependencies**: No external libraries or frameworks to manage
- **Clear Structure**: Well-defined roles for each part of the codebase
- **Minimal Core**: Small, focused JavaScript files that do specific jobs well
This simplicity-first approach delivers tremendous benefits in terms of maintainability, performance, and developer onboarding. Rather than relying on complex toolchains and frameworks, EDS pushes developers to solve problems directly with fundamental web technologies.
### Performance by Design
EDS doesn't treat performance as an afterthought or something to be optimized later—it's built into the core architecture:
- **Three-Phase Loading**: The E-L-D pattern ensures critical content loads first
- **Resource Optimization**: Automatic image optimization, responsive images, and format selection
- **Minimal JavaScript**: No heavy frameworks or unnecessary code
- **Progressive Enhancement**: Core content works even before enhancement scripts run
- **Smart Prioritization**: Resources load based on visibility and importance
This architecture consistently delivers perfect Lighthouse scores (100/100/100/100), something many development teams struggle to achieve even with significant optimization efforts.
### Empowering Both Authors and Developers
The EDS approach creates a system where:
- Authors can focus on creating great content in familiar tools
- Developers can build powerful functionality without disrupting content workflows
- Both teams can work simultaneously without blocking each other
This balance is rare in content management systems, which tend to prioritize either developer experience or author experience at the expense of the other.
### The Expander Pattern for Extension
Perhaps most importantly, EDS promotes the "expander" pattern for extending functionality without modifying core files. This pattern:
- **Preserves Upgradeability**: Core files remain untouched, allowing easy upgrades
- **Maintains Separation**: Custom functionality stays isolated and self-contained
- **Encourages Modularity**: Extensions are organized into distinct, focused blocks
- **Supports Selective Loading**: Extensions can be conditionally applied based on page type
By embracing this pattern, development teams can add sophisticated functionality—analytics, personalization, third-party integrations—without the risk and maintenance burden of modifying core files.
## Using this document with AI
This post [formatted as markdown](https://allabout.network/blogs/ddt/adobe-edge-delivery-services-full-guide-for-devs-architects-and-ai.md) can assist in building EDS Components using AI tools like cursor and claude. A shorter version is here
Add the MD file/ text file (your choice) to docs in cursor settings -> features -> docs.
\
![][image4]\
\
![][image5]
It will then be able to be referenced during Cursor Chats
![][image6]
If you prefer to use Claude Projects, add the markdown as project knowledge, other IDEs/AI use similar patterns.\
\
![][image7]
Now that you have the AI assistant setup you can create new block by setting up the block structure and populating it with a README.md explaining the functionality to the reader (The AI in this case)\
\
As an example we might want to create a markdown block
+------------------------------------------------------------------------------------------------------------------+
| markdown |
+------------------------------------------------------------------------------------------------------------------+
| \# Markdown Block |
| |
| This block processes and displays markdown content within a styled container. |
| |
| \## Usage |
| |
| To use the Markdown block, create a table in your document with "Markdown" in the first cell. The content in the |
| cells below will be processed as markdown and displayed in a light blue box with rounded corners and a 2px thick |
| border. |
| |
| \| Markdown \| |
| |
| \|----------\| |
| |
| \| Your markdown content here \| |
| |
| \## Authoring |
| |
| When creating content for the Markdown block in Google Docs or Microsoft Word: |
| |
| 1\. Create a table with at least two rows. |
| |
| 2\. In the first cell of the first row, type "Markdown". |
| |
| 3\. In the cells below, add your markdown content. |
| |
| 4\. The block will automatically process and display the markdown content. |
| |
| \## Styling |
| |
| The Markdown block uses CSS variables for easy customization: |
| |
| \- \`--markdown-bg-color\`: Background color of the container (default: light blue) |
| |
| \- \`--markdown-border-color\`: Border color of the container (default: blue) |
| |
| \- \`--markdown-border-radius\`: Border radius of the container (default: 8px) |
| |
| \- \`--markdown-padding\`: Padding inside the container (default: 20px) |
+------------------------------------------------------------------------------------------------------------------+
Create this in /blocks/markdown/README.md then ask the AI to implement it.
![][image8]
No doubt you will make adjustments as the AI does it work, at the end ask it to update/blocks/READMe.md and you will have developer and content author documentation.
### Final Thoughts
Edge Delivery Services represents a refreshing perspective in web development—one that challenges many contemporary assumptions about how websites should be built and managed. By embracing document-based authoring, prioritizing performance by design, and promoting clean separation between content and code, EDS delivers a compelling alternative to traditional CMS platforms.
For developers accustomed to component-based systems and complex build processes, EDS might initially feel constraining. However, these constraints often foster creativity and focus attention on solving real problems rather than managing tooling complexity. The result is websites that are faster, more maintainable, and more author-friendly.
As you implement your own EDS projects, remember these key principles:
- **Respect the Document**: Prioritize the natural flow of document-based authoring
- **Embrace Constraints**: Let EDS's limitations guide you toward simpler, more effective solutions
- **Performance First**: Consider performance implications in every decision
- **Don't Modify Core Files**: Use the expander pattern for extensions
- **Document Everything**: Create comprehensive documentation for blocks and systems
- Use **AIRBNB style guide:** Keep everything clean
By following these principles, you'll create websites that achieve the rare combination of excellent performance, maintainable code, and superior authoring experience that Edge Delivery Services makes possible.
`;
// Add event listeners
banner.querySelector('.accept-button').addEventListener('click', () => {
localStorage.setItem('cookie-consent', 'accepted');
banner.remove();
enableTracking();
});
banner.querySelector('.decline-button').addEventListener('click', () => {
localStorage.setItem('cookie-consent', 'declined');
banner.remove();
});
// Add to page
document.body.appendChild(banner);
}
function enableTracking() {
// Load analytics and other tracking scripts
const script = document.createElement('script');
script.src = '/scripts/tracking.js';
document.head.appendChild(script);
}
```
### Challenge: Personalization
**Problem**: Implementing personalization without complex frameworks.
**Solution**: Use local storage for simple personalization:
```
export default function decorate(block) {
// Get or create user profile
let userProfile = JSON.parse(localStorage.getItem('user-profile')) || {};
// Track page visits
userProfile.pageVisits = userProfile.pageVisits || [];
userProfile.pageVisits.push({
path: window.location.pathname,
timestamp: Date.now(),
});
// Limit history length
if (userProfile.pageVisits.length > 20) {
userProfile.pageVisits = userProfile.pageVisits.slice(-20);
}
// Determine interests based on page visits
const interests = determineInterests(userProfile.pageVisits);
userProfile.interests = interests;
// Save updated profile
localStorage.setItem('user-profile', JSON.stringify(userProfile));
// Personalize content
personalizeContent(block, userProfile);
}
function determineInterests(pageVisits) {
// Simple interest determination based on URL patterns
const interests = {};
pageVisits.forEach(visit => {
if (visit.path.includes('/products/')) {
interests.products = true;
} else if (visit.path.includes('/services/')) {
interests.services = true;
} else if (visit.path.includes('/blog/')) {
interests.blog = true;
}
});
return interests;
}
function personalizeContent(block, userProfile) {
// Clear block contents
block.textContent = '';
// Generate personalized content based on interests
const heading = document.createElement('h2');
heading.textContent = userProfile.interests.products
? 'Products You Might Like'
: 'Our Popular Products';
block.appendChild(heading);
// ... additional personalized content ...
}
```
These practical solutions demonstrate how to implement common requirements within EDS's architectural constraints, without modifying core files or compromising performance.
### CSS Best Practices