
Using Content Driven Development
- 25 installs
- 45 repo stars
- Updated August 4, 2026
- adobe/helix-website
using-content-driven-development is a Claude Code skill that applies a content-first development process to AEM Edge Delivery Services block development.
About
using-content-driven-development applies a content-first development process to AEM Edge Delivery Services work. It requires identifying or creating test content before writing or modifying block code, prioritizing author-friendly content models. A developer follows it for building or changing blocks and core decoration, orchestrating the content-modeling, building-blocks, and testing-blocks skills.
- Enforces a content-first development process for AEM Edge Delivery: identify or create test content before writing block
- Orchestrates content-modeling, building-blocks, and testing-blocks skills across a phased workflow
- Test content doubles as PR validation links for PageSpeed Insights checks and author documentation
Using Content Driven Development by the numbers
- 25 all-time installs (skills.sh)
- Ranked #1,503 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
using content driven development capabilities & compatibility
- Capabilities
- content modeling · block development · workflow orchestration
- Use cases
- web design · frontend · documentation
What using content driven development says it does
Content Driven Development is a mandatory process for AEM Edge Delivery Services development that prioritizes content and author needs over developer convenience.
**NEVER start writing or modifying code without first identifying or creating the content you will use to test your changes.**
**Author needs come before developer needs.**
npx skills add https://github.com/adobe/helix-website --skill using-content-driven-developmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 25 |
|---|---|
| repo stars | ★ 45 |
| Last updated | August 4, 2026 |
| Repository | adobe/helix-website ↗ |
What it does
Apply a content-first process to AEM Edge Delivery block development, creating test content before writing code.
Who is it for?
AEM Edge Delivery developers building or modifying blocks who want to work against real author content
Skip if: Trivial CSS-only tweaks or non-AEM projects; it is not a marketing or SEO content skill
When should I use this skill?
Building new blocks, modifying existing blocks, or changing core decoration in an AEM Edge Delivery project
What you get
Blocks built against real test content with author-friendly content models and PR validation links
- Author-friendly content model
- Test content usable for development, PR validation, and author documentation
By the numbers
- Phased content-first process (Content Discovery and Modeling first)
- Orchestrates 4 related skills (content-modeling, building-blocks, testing-blocks, block-collection-and-party)
Files
Using Content Driven Development (CDD)
Content Driven Development is a mandatory process for AEM Edge Delivery Services development that prioritizes content and author needs over developer convenience. This skill orchestrates the development workflow to ensure code is built against real content with author-friendly content models.
Why Content-First Matters
Author needs come before developer needs. When building for AEM Edge Delivery, authors are the primary users of the structures we create. Content models must be intuitive and easy to work with, even if that means more complex decoration code.
Efficiency through preparation. Creating or identifying test content before coding provides:
- Immediate testing capability: No need to stop development to create test content
- Better PR workflows: Test content doubles as PR validation links for PSI checks
- Living documentation: Test content often serves as author documentation and examples
- Fewer assumptions: Real content reveals edge cases code-first approaches miss
NEVER start writing or modifying code without first identifying or creating the content you will use to test your changes.
When to Apply This Skill
Apply Content Driven Development principles to ALL AEM development tasks:
- ✅ Creating new blocks
- ✅ Modifying existing blocks (structural or functional changes)
- ✅ Changes to core decoration functionality
- ✅ Bug fixes that require validation
- ✅ Any code that affects how authors create or structure content
Skip CDD only for:
- ⚠️ Trivial CSS-only styling tweaks (but still identify test content for validation)
- ⚠️ Configuration changes that don't affect authoring
When in doubt, follow the CDD process. The time invested pays dividends in quality and efficiency.
Related Skills
This skill orchestrates other skills at the appropriate stages:
- content-modeling: Invoked when new content models need to be designed or existing models modified
- building-blocks: Invoked during implementation phase for block creation or modification
- testing-blocks: Referenced during validation phase for comprehensive testing
- block-collection-and-party: Used to find similar blocks and reference implementations
The Content-First Process
Follow these phases in order. Do not skip steps.
Phase 1: Content Discovery and Modeling
The first phase establishes what content you're working with and ensures the content model is author-friendly.
Step 1.1: Determine Content Availability
For new blocks:
Skip to Step 1.2 (Content Model Design). Searching for content that doesn't exist is a waste of time.
For modifications to existing blocks:
Ask the user: "Does content using this block already exist that we can use for testing?"
- YES → Identify existing content to test against
- Use the
scripts/find-block-content.jsscript to search for pages containing the block - Or ask the user: "What are the path(s) to page(s) with this block?"
- Validate the content loads correctly in your local dev environment
- Proceed to Phase 2 (skip content modeling if structure isn't changing)
- NO existing content → Proceed to Step 1.2
Step 1.2: Content Model Design
REQUIRED for:
- All new blocks
- Structural changes to existing blocks (adding/removing/modifying sections, variants, or the authoring structure)
Ask the user: "This requires a new content model. Would you like me to use the content-modeling skill to design an author-friendly content model now?"
- YES → Invoke the content-modeling skill
- Follow the content modeling process completely
- Return to this skill when content model is defined
- Proceed to Step 1.3
- NO → The user may want to define it themselves
- Ask: "Please describe the content structure authors will use"
- Document their description for reference
- Proceed to Step 1.3
Step 1.3: Content Creation
Once the content model is defined (from Step 1.2), you need test content.
Ask the user: "We need test content for development and validation. This content will serve multiple purposes:
- Testing during development
- PR validation link for PSI checks
- Author documentation and examples
Would you like to: 1. Create this content in the CMS now (Google Drive/SharePoint/DA/Universal Editor) 2. Create temporary local HTML files for testing (will need CMS content before PR)"
Option 1: CMS Content (Recommended)
- Guide the user through creating content in their CMS
- Wait for user confirmation that content is created and published
- Get the content URL(s) from the user
- Validate content loads in local dev environment
- Proceed to Phase 2
Option 2: Local HTML Files (Temporary)
- Create HTML file(s) in
drafts/folder matching the content model structure - Reference the HTML Structure Guide for proper file format
- Remind user: "Restart your dev server with:
aem up --html-folder drafts" - Note: "You will need to create actual CMS content before raising a PR"
- Proceed to Phase 2
Making Test Content Serve as Author Documentation
Test content can often double as author-facing documentation, saving time and keeping documentation current. Consider this when creating test content:
When test content IS sufficient as author documentation:
- The block is straightforward with clear patterns
- Test content shows all variants and use cases
- Content demonstrates best practices authors should follow
- Examples are realistic and relatable to actual use cases
When separate author documentation is needed:
- Block has complex configuration or many variants requiring explanation
- There are edge cases or gotchas authors need to understand
- Project standards require formal documentation in a specific location/format
- Block behavior isn't self-evident from examples alone
Structuring test content to serve both purposes: 1. Create comprehensive examples: Show all variants, edge cases, and common patterns 2. Use realistic content: Avoid "lorem ipsum" or technical placeholders 3. Demonstrate best practices: Structure content the way authors should 4. Consider location: Place content where it can serve as documentation
- Sidekick Library projects: Consider creating in
/tools/sidekick/library/or appropriate library location - Document Authoring: Place in DA Library structure
- Simple documentation: Use
/drafts/docs/or/drafts/library/ - Universal Editor: Follow project-specific documentation patterns
Ask the user about documentation approach: "Should this test content also serve as author documentation? If so, we can structure it accordingly and place it in an appropriate location (e.g., /drafts/library/{block-name} or your project's library system)."
If yes, guide content creation with documentation in mind. If no, proceed with test-focused content and note that author documentation will be needed later.
Phase 2: Implementation
CRITICAL: Do not begin Phase 2 until you have confirmed test content exists and is accessible.
Now that test content exists, proceed with implementation:
For Block Development
Invoke the building-blocks skill:
- Provide the skill with the content model and test content URL(s)
- Follow the building-blocks process for implementation
- Return to this skill when implementation is complete
- Proceed to Phase 3
For Core Functionality Changes
Follow standard development practices:
- Make changes to scripts, styles, or configuration
- Test against the identified content throughout development
- Ensure changes don't break existing blocks or content models
- Proceed to Phase 3
Phase 3: Validation
The final phase ensures the implementation works correctly with real content.
Step 3.1: Test with Real Content
Mandatory testing:
- ✅ View test content in local dev environment
- ✅ Verify all variants render correctly
- ✅ Check responsive behavior (mobile, tablet, desktop)
- ✅ Test edge cases revealed by the actual content
- ✅ Validate accessibility basics (keyboard navigation, screen reader friendly)
Step 3.2: Run Quality Checks
Required before considering implementation complete:
npm run lintIf linting fails, fix issues with:
npm run lint:fixStep 3.3: Comprehensive Testing
The testing-blocks skill is automatically invoked by building-blocks for block development.
For other code changes, or for additional testing guidance, invoke the testing-blocks skill which provides:
- Unit testing strategies for logic-heavy utilities
- Browser testing with Playwright/Puppeteer
- Linting and code quality checks
- Performance validation with GitHub checks
- Guidance on keeper vs throwaway tests
Step 3.4: PR Preparation
Before raising a PR, ensure:
- ✅ Test content exists in the CMS (not just local HTML)
- ✅ Test content URL is accessible for PSI checks
- ✅ All linting passes
- ✅ Author documentation is updated (if applicable)
The test content URL will be used as the PR validation link.
Anti-Patterns to Avoid
Common mistakes:
- ❌ Starting with code before understanding the content model
- ❌ Making assumptions about content structure without seeing real examples
- ❌ Creating developer-friendly but author-hostile content models
- ❌ Skipping content creation "to save time" (costs more time later)
- ❌ Testing against imagined content instead of real content
- ❌ Treating test content creation as separate from development workflow
Workflow Summary
Quick reference for the CDD process:
1. CONTENT DISCOVERY
└─ Existing content? → Use it
└─ New block/structure? → Design content model → Create test content
2. IMPLEMENTATION
└─ Build code against the real content model
└─ Test continuously with actual content
3. VALIDATION
└─ Comprehensive testing with test content
└─ Quality checks (linting, accessibility)
└─ PR preparation with test URL
KEY RULE: Never proceed to implementation without test contentScripts and Tools
Finding Existing Block Content
Use the provided script to search for pages containing a specific block:
# Search on localhost (default)
node .claude/skills/content-driven-development/scripts/find-block-content.js <block-name>
# Search for specific variant
node .claude/skills/content-driven-development/scripts/find-block-content.js <block-name> localhost:3000 <variant>
# Search on live site
node .claude/skills/content-driven-development/scripts/find-block-content.js <block-name> main--repo--owner.aem.live
# Search on preview with variant
node .claude/skills/content-driven-development/scripts/find-block-content.js <block-name> main--repo--owner.aem.page <variant>Examples:
node .claude/skills/content-driven-development/scripts/find-block-content.js hero
node .claude/skills/content-driven-development/scripts/find-block-content.js hero localhost:3000 dark
node .claude/skills/content-driven-development/scripts/find-block-content.js cards main--site--owner.aem.live three-upThis script queries the site's query-index to find all pages containing the specified block (and optional variant) and returns their URLs. The script uses proper DOM parsing to accurately identify blocks.
Integration with Other Skills
This skill acts as the orchestrator for AEM development workflows:
At Content Modeling stage: → Invoke content-modeling skill for author-friendly design
At Implementation stage: → Invoke building-blocks skill for block development → Reference block-collection-and-party skill for patterns
At Validation stage: → Reference testing-blocks skill for comprehensive testing
Following this orchestration ensures all development follows content-first principles.
HTML File Structure for Test Content
When creating local .plain.html files for testing blocks in the drafts/ folder, follow this structure to match how AEM Edge Delivery Services processes authored content.
Important Change: Plain HTML Format
The AEM CLI now automatically wraps HTML content with the headful structure (head, header, footer). When you create .plain.html files, you ONLY need to provide the section content.
What you create:
- ✅ Section divs with content:
<div>...</div>(one per section) - ✅ Blocks as
<div class="block-name">with nested divs - ✅ Default content (headings, paragraphs, links, images)
- ✅ Section metadata blocks when needed
What the AEM CLI adds automatically:
- ❌
<html>,<head>,<body>tags - ❌
<header>and<footer>elements - ❌
<main>wrapper - ❌ Head content (comes from project's head.html)
Plain HTML Structure
<div>
<!-- Section 1: Mixed content - default content and a block -->
<h1>Page Heading</h1>
<p>This is regular paragraph content.</p>
<div class="block-name">
<!-- Block content goes here -->
<div>
<div>Block content cell 1</div>
<div>Block content cell 2</div>
</div>
</div>
<p>More content after the block.</p>
</div>
<div>
<!-- Section 2: Block in its own section -->
<div class="block-name variant-name">
<!-- Block content -->
</div>
</div>
<div>
<!-- Section 3: Multiple blocks in one section -->
<div class="block-one">
<!-- First block content -->
</div>
<div class="block-two">
<!-- Second block content -->
</div>
</div>File Naming Convention
IMPORTANT: HTML files must use the .plain.html extension:
- ✅
drafts/hero-test.plain.html - ✅
drafts/blocks/cards.plain.html - ❌
drafts/hero-test.html(old format, no longer used)
Running with Local HTML
Start dev server with:
aem up --html-folder draftsPreview URLs:
- File:
drafts/hero-test.plain.html→ URL:http://localhost:3000/drafts/hero-test - File:
drafts/blocks/cards.plain.html→ URL:http://localhost:3000/drafts/blocks/cards - Special case:
drafts/index.plain.html→ URL:http://localhost:3000/drafts/index(NOT/drafts/)
Section Structure
Content is organized into sections (top-level <div> elements).
Basic Sections
<div>
<!-- Section 1 content -->
</div>
<div>
<!-- Section 2 content -->
</div>Important notes about sections:
- Sections can contain blocks, default content, or a mix of both
- A single section can contain multiple blocks
- There are no strict rules about when to create a new section vs. adding to an existing one
- This varies by project and authoring practices
- Some blocks may require or assume they are in their own section (check block documentation)
Section Metadata
Sections can include metadata to define styling and behavior using a special section-metadata div.
In HTML (using section-metadata div):
<div>
<div class="section-metadata">
<div>
<div>Style</div>
<div>dark</div>
</div>
</div>
<!-- Section content with dark background styling -->
</div>In markdown (Section Metadata table):
+------------------------------+
| Section Metadata |
+------------------+-----------+
| style | dark |
+------------------+-----------+How it works:
- The
section-metadatadiv is placed at the beginning of a section - It uses a nested div structure to represent key-value pairs (like a table)
- The platform processes this metadata and applies it to the parent section
- After processing, the
section-metadatadiv is removed from the DOM - Metadata values become CSS classes or data attributes on the section
Common section styles:
light- White or light backgrounddark- Dark background with light textgrey- Grey or off-white backgroundaccent- Branded color background
Style naming best practices:
- Use consistent names across sections with same visual treatment
- Don't create unique style names for every section
- Example: If 3 sections have white backgrounds, use "light" for all 3
Example with multiple sections:
<!-- Section 1: Light background -->
<div>
<div class="section-metadata">
<div>
<div>Style</div>
<div>light</div>
</div>
</div>
<div class="hero">
<div><div><h1>Welcome</h1></div></div>
</div>
</div>
<!-- Section 2: Dark background -->
<div>
<div class="section-metadata">
<div>
<div>Style</div>
<div>dark</div>
</div>
</div>
<div class="cards">
<div>
<div>Card 1</div>
<div>Card 2</div>
</div>
</div>
</div>
<!-- Section 3: Light background (reuses "light") -->
<div>
<div class="section-metadata">
<div>
<div>Style</div>
<div>light</div>
</div>
</div>
<h2>About Our Company</h2>
<p>This section has default content, not blocks.</p>
</div>For guidance on identifying sections and assigning styles: Use the page-decomposition skill when migrating pages.
Section Content Types
Sections can contain any combination of:
1. Default Content
Regular HTML elements like headings, paragraphs, lists, etc.
<div>
<h1>Main Heading</h1>
<h2>Subheading</h2>
<p>Paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
<ul>
<li>Unordered list item</li>
</ul>
<ol>
<li>Ordered list item</li>
</ol>
</div>Supported default elements:
- Headings:
<h1>,<h2>,<h3>,<h4>,<h5>,<h6> - Paragraphs:
<p> - Lists:
<ul>,<ol>,<li> - Inline formatting:
<strong>,<em>,<a> - Images: Use
<picture>elements with<source>and<img>tags (see Images section below) - Code blocks:
<pre>,<code> - Block quotes:
<blockquote>
2. Blocks
Blocks are <div> elements with specific class names that trigger decoration logic.
Basic block structure:
<div>
<div class="block-name">
<!-- Block content structured based on content model -->
</div>
</div>Block with variant:
<div>
<div class="block-name variant-name">
<!-- Block content -->
</div>
</div>Multiple variants:
<div>
<div class="block-name variant-one variant-two">
<!-- Block content -->
</div>
</div>3. Icons
Icons are authored using the :iconName: syntax and are processed into <span> elements with icon classes.
Author input:
:profile:HTML output:
<span class="icon icon-profile"></span>Important notes about icons:
- Icons can be wrapped in links, strong, em, or other inline elements
- The icon name corresponds to an SVG file in
/icons/(e.g.,:profile:→/icons/profile.svg) - Icon spans use two classes:
icon(base class) andicon-{name}(specific icon) - Icons are inline elements and can be combined with text
Examples:
Icon in a link:
<a href="/profile"><span class="icon icon-profile"></span> View Profile</a>Icon with emphasis:
<strong><span class="icon icon-star"></span> Featured</strong>Icon by itself:
<p><span class="icon icon-home"></span></p>4. Images
Images should always use the <picture> element with <source> elements for responsive images and format optimization.
Basic picture structure:
<picture>
<source type="image/webp" srcset="/media/image.jpg?width=2000&format=webply&optimize=medium" media="(min-width: 600px)">
<source type="image/webp" srcset="/media/image.jpg?width=750&format=webply&optimize=medium">
<source srcset="/media/image.jpg?width=2000&format=jpeg&optimize=medium" media="(min-width: 600px)">
<img loading="lazy" alt="Image description" src="/media/image.jpg?width=750&format=jpeg&optimize=medium">
</picture>Key aspects of picture elements:
- Multiple
<source>elements provide WebP format with fallbacks - Use
mediaattributes for responsive breakpoints (typically(min-width: 600px)for desktop) - Include width query parameters (
?width=750for mobile,?width=2000for desktop) - Add
formatandoptimize=mediumparameters for image optimization - The final
<img>element is the fallback - Always include
altattribute for accessibility - Use
loading="lazy"for images below the fold,loading="eager"for above-the-fold images
Standard responsive breakpoints:
<picture>
<!-- WebP desktop (600px+) -->
<source type="image/webp" srcset="/media/image.jpg?width=2000&format=webply&optimize=medium" media="(min-width: 600px)">
<!-- WebP mobile -->
<source type="image/webp" srcset="/media/image.jpg?width=750&format=webply&optimize=medium">
<!-- JPEG desktop (600px+) -->
<source srcset="/media/image.jpg?width=2000&format=jpeg&optimize=medium" media="(min-width: 600px)">
<!-- JPEG mobile (fallback) -->
<img loading="lazy" alt="Descriptive alt text" src="/media/image.jpg?width=750&format=jpeg&optimize=medium">
</picture>Important notes:
- Place images in the
/media/folder or appropriate project location - Always provide meaningful
alttext for accessibility - Use
loading="eager"only for hero/above-the-fold images - The platform's
createOptimizedPicture()JavaScript helper generates this structure automatically in decoration code
Simplified picture format for examples:
For brevity, examples in this document may show simplified picture tags:
<picture>
<img src="/media/image.jpg" alt="Description">
</picture>In actual test files, you can use either: 1. The full responsive structure shown above (recommended for realistic testing) 2. The simplified format (acceptable for quick prototyping, though less realistic)
Block Content Structure
The internal structure of a block depends on its content model. Blocks typically use nested <div> elements to represent the table-like structure from authoring.
Simple Block Example
A hero block with an image and text:
<div class="hero">
<div>
<div>
<picture>
<img src="/media/hero-image.jpg" alt="Hero image description">
</picture>
</div>
</div>
<div>
<div>
<h1>Hero Heading</h1>
<p>Hero description text</p>
</div>
</div>
</div>Block with Multiple Rows
A cards block with multiple items:
<div class="cards">
<div>
<div>
<picture>
<img src="/media/card1.jpg" alt="Card 1">
</picture>
</div>
<div>
<h3>Card 1 Title</h3>
<p>Card 1 description</p>
</div>
</div>
<div>
<div>
<picture>
<img src="/media/card2.jpg" alt="Card 2">
</picture>
</div>
<div>
<h3>Card 2 Title</h3>
<p>Card 2 description</p>
</div>
</div>
<div>
<div>
<picture>
<img src="/media/card3.jpg" alt="Card 3">
</picture>
</div>
<div>
<h3>Card 3 Title</h3>
<p>Card 3 description</p>
</div>
</div>
</div>Block Structure Mapping
The nested <div> structure in HTML corresponds to the table structure in authoring:
In document authoring (table):
| Block Name |
|-------------------|
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |In HTML:
<div class="block-name">
<div> <!-- Row 1 -->
<div>Cell 1</div> <!-- Column 1 -->
<div>Cell 2</div> <!-- Column 2 -->
</div>
<div> <!-- Row 2 -->
<div>Cell 3</div> <!-- Column 1 -->
<div>Cell 4</div> <!-- Column 2 -->
</div>
</div>Complete Example
Here's a complete example of a test .plain.html file for a hero block:
File: drafts/hero-test.plain.html
<!-- Hero block section -->
<div>
<div class="hero">
<div>
<div>
<picture>
<img src="/media/hero-image.jpg" alt="Welcome to our site">
</picture>
</div>
</div>
<div>
<div>
<h1>Welcome to Our Site</h1>
<p>This is a compelling hero message that encourages visitors to take action.</p>
<p><a href="/contact">Get Started</a></p>
</div>
</div>
</div>
</div>
<!-- Regular content section -->
<div>
<h2>About This Test</h2>
<p>This page demonstrates the hero block in action.</p>
</div>
<!-- Hero block with variant -->
<div>
<div class="hero dark">
<div>
<div>
<picture>
<img src="/media/hero-dark.jpg" alt="Dark variant hero">
</picture>
</div>
</div>
<div>
<div>
<h2>Dark Variant Hero</h2>
<p>Testing the dark variant of the hero block.</p>
</div>
</div>
</div>
</div>Preview at: http://localhost:3000/drafts/hero-test
Important Notes
File location:
- Create test HTML files in the
drafts/folder - Can be organized in subfolders:
drafts/blocks/hero/test.plain.html - Always use
.plain.htmlextension
Running with local HTML:
- Start dev server with:
aem up --html-folder drafts - This tells the dev server to serve HTML files from the drafts folder
- The CLI automatically wraps your plain HTML with head, header, and footer
Section organization:
- Sections can contain any mix of blocks and default content
- No strict rules about section boundaries - depends on project and authoring needs
- Some blocks may require being in their own section - check block documentation
Images:
- Use
<picture>elements with proper responsive structure (see Images section) - Reference images from the
/media/folder or appropriate location - Always include
altattributes for accessibility
Testing considerations:
- Test multiple variants in the same file by adding multiple sections
- Include edge cases in your test content
- Use realistic content, not placeholder text
Content model alignment:
- The HTML structure must match your block's expected content model
- Consult your content model documentation when structuring blocks
- The decoration JavaScript will process this structure at two levels:
- Page-level decoration (e.g.,
decorateMaininscripts.js) processes all content - Block-specific decoration (
blocks/{block-name}/{block-name}.js) processes individual blocks - Classes and enhanced markup are typically added during decoration, not in the authored HTML
When to Create CMS Content Instead
Local HTML files are useful for quick iteration, but remember:
- For PRs: You need actual CMS content for PSI validation links
- For documentation: CMS content can serve as author documentation
- For collaboration: CMS content is easier for non-developers to review
Always plan to create CMS content before finalizing your PR, even if you start with local HTML for rapid development.
#!/usr/bin/env node
/**
* Find pages containing a specific block in AEM Edge Delivery projects.
*
* This script queries the query-index to find instances of a block,
* helping developers identify existing content for testing during development.
*
* Usage:
* node find-block-content.js <block-name> [host] [variant]
*
* Examples:
* node find-block-content.js hero
* node find-block-content.js hero localhost:3000
* node find-block-content.js hero main--mysite--owner.aem.live
* node find-block-content.js hero main--mysite--owner.aem.page
* node find-block-content.js hero localhost:3000 dark
* node find-block-content.js cards main--mysite--owner.aem.live three-up
*
* The script will:
* 1. Query the site's query-index for all pages
* 2. Check each page for the specified block (and variant if provided)
* 3. Report all pages containing the block with their URLs
*
* Defaults to localhost:3000 if no host specified
*/
import { JSDOM } from 'jsdom';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Fetch all URLs from the query index with pagination
* @param {string} host - The host to query
* @returns {Promise<string[]>} Array of page paths
*/
async function fetchQueryIndex(host) {
const limit = 512;
let offset = 0;
const paths = [];
let more = true;
console.log(`Fetching query index from ${host}...\n`);
do {
try {
// Use http for localhost, https for everything else
const protocol = host.startsWith('localhost') ? 'http' : 'https';
const url = `${protocol}://${host}/query-index.json?offset=${offset}&limit=${limit}`;
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const json = await res.json();
const data = json.data || [];
data.forEach((item) => {
if (item.path) {
paths.push(item.path);
}
});
more = data.length === limit;
offset += limit;
if (more) {
console.log(`Fetched ${paths.length} pages so far...`);
}
} catch (err) {
console.error(`Error fetching query index: ${err.message}`);
more = false;
}
} while (more);
console.log(`\nTotal pages found: ${paths.length}\n`);
return paths;
}
/**
* Check if a page contains the specified block (and optional variant)
* @param {string} host - The host to query
* @param {string} path - The page path
* @param {string} blockName - Name of block to find
* @param {string} variant - Optional variant name to match
* @returns {Promise<boolean>} True if block is found
*/
async function pageContainsBlock(host, path, blockName, variant = null) {
try {
// Use http for localhost, https for everything else
const protocol = host.startsWith('localhost') ? 'http' : 'https';
const url = `${protocol}://${host}${path}`;
const res = await fetch(url);
if (!res.ok) {
return false;
}
const html = await res.text();
// Parse HTML with jsdom
const dom = new JSDOM(html);
const { document } = dom.window;
// Look for block using proper DOM query
// Blocks appear as elements with the block name as a class
const selector = `.${blockName}`;
const blockElements = document.querySelectorAll(selector);
if (blockElements.length === 0) {
return false;
}
// If no variant specified, any instance of the block counts
if (!variant) {
return true;
}
// Check if any block instance has the specified variant
return Array.from(blockElements).some((element) =>
element.classList.contains(variant)
);
} catch (err) {
return false;
}
}
/**
* Process URLs in batches with concurrency control
* @param {string} host - The host to query
* @param {string[]} paths - Array of page paths
* @param {string} blockName - Name of block to find
* @param {string} variant - Optional variant name
* @param {number} concurrency - Number of concurrent requests
* @returns {Promise<string[]>} Array of paths containing the block
*/
async function findBlockInPages(host, paths, blockName, variant = null, concurrency = 10) {
const matches = [];
const inFlight = new Set();
const searchTerm = variant ? `"${blockName}" block with "${variant}" variant` : `"${blockName}" block`;
console.log(`Searching ${paths.length} pages for ${searchTerm}...\n`);
for (let i = 0; i < paths.length; i += 1) {
const path = paths[i];
const promise = pageContainsBlock(host, path, blockName, variant).then((found) => {
if (found) {
matches.push(path);
console.log(`✓ Found: ${path}`);
}
inFlight.delete(promise);
});
inFlight.add(promise);
// Wait if we've hit concurrency limit
if (inFlight.size >= concurrency) {
await Promise.race(inFlight);
}
}
// Wait for remaining requests
await Promise.all(inFlight);
return matches;
}
/**
* Get the host to query
* @param {string} host - Host string or undefined for default
* @returns {string} The host to query
*/
function getHost(host) {
if (!host) {
return 'localhost:3000';
}
// Strip https:// or http:// if provided
return host.replace(/^https?:\/\//, '').replace(/\/$/, '');
}
/**
* Main execution
*/
async function main() {
const blockName = process.argv[2];
const hostArg = process.argv[3];
const variantArg = process.argv[4];
if (!blockName) {
console.error('Error: Block name is required');
console.error('\nUsage: node find-block-content.js <block-name> [host] [variant]');
console.error('\nExamples:');
console.error(' node find-block-content.js hero');
console.error(' node find-block-content.js hero localhost:3000');
console.error(' node find-block-content.js hero main--mysite--owner.aem.live');
console.error(' node find-block-content.js hero localhost:3000 dark');
console.error(' node find-block-content.js cards main--mysite--owner.aem.live three-up');
process.exit(1);
}
const host = getHost(hostArg);
const searchTerm = variantArg ? `"${blockName}" block with "${variantArg}" variant` : `"${blockName}" block`;
console.log(`Searching for ${searchTerm} on ${host}\n`);
console.log('─'.repeat(60));
console.log();
// Fetch all pages from query index
const paths = await fetchQueryIndex(host);
if (paths.length === 0) {
console.log('No pages found in query index.');
console.log('\nMake sure:');
console.log('- Your dev server is running (aem up)');
console.log('- The site has been indexed');
return;
}
// Search for block in pages
const matches = await findBlockInPages(host, paths, blockName, variantArg);
console.log();
console.log('─'.repeat(60));
console.log();
// Report results
if (matches.length === 0) {
const notFoundMsg = variantArg
? `No pages found containing the "${blockName}" block with "${variantArg}" variant.`
: `No pages found containing the "${blockName}" block.`;
console.log(notFoundMsg);
console.log('\nThis might mean:');
console.log('- The block is new and no content exists yet');
console.log('- The block name is spelled differently');
if (variantArg) {
console.log('- The variant name is spelled differently');
console.log(`- The block exists but not with the "${variantArg}" variant`);
}
console.log('- Content exists but hasn\'t been published');
} else {
const resultMsg = variantArg
? `✓ Found ${matches.length} page(s) containing the "${blockName}" block with "${variantArg}" variant:\n`
: `✓ Found ${matches.length} page(s) containing the "${blockName}" block:\n`;
console.log(resultMsg);
matches.forEach((path, index) => {
console.log(`${index + 1}. https://${host}${path}`);
});
console.log('\nYou can use these pages for testing during development.');
}
}
main().catch((err) => {
console.error('Error:', err.message);
process.exit(1);
});
{
"type": "module"
}