Duration: 25 minutes
Quick navigation
- Context
- Hands-on Lab
- Key Takeaways
Complete SETUP.md if not already done. Exercises can be done in sequence or independently; if independent, ensure SETUP is done and you have the items below.
Required:
- On your feature branch (
jsmith— first initial + last name, lowercase) - Local dev server at
http://localhost:3000 - Code editor open with the repository
- Exercises 1–4 completed (if doing in sequence)
- Personal workspace:
/drafts/jsmith/(use your name, lowercase)
What's already set up for you:
The instructor has pre-configured the entire JSON2HTML pipeline so you can focus on understanding how it works:
| Component | Location | Status |
|---|---|---|
| Data source (Sheet) | /future-events in DA.live |
Published — available as JSON |
| List template | /labs/exercise5/events-template in repo |
Committed |
| Detail template | /labs/exercise5/event-template in repo |
Committed |
| Event block | blocks/event/event.js + event.css in repo |
Committed |
| Worker config | JSON2HTML Cloudflare worker | Configured for main branch |
Verify data source exists:
-
Open in browser:
https://main--nycmasterclass--cloudadoption.aem.page/future-events.json -
You should see: JSON with event records (Sydney, London, Bangalore, Berlin, Singapore, Dubai) including city, date, venue, highlights, images, etc.
-
If you see 404: Ask the instructor to publish the
/future-eventsSheet.
Key concept: Sheets in DA.live automatically become JSON endpoints. The Sheet at /future-events becomes available as /future-events.json.
- How a pre-configured JSON2HTML pipeline generates multiple pages from a single JSON data source
- How Mustache templates transform JSON into HTML (list + detail templates)
- How the JSON2HTML worker service is configured and how it matches URL patterns
- How to add new data and see pages generated automatically
- How the
eventblock decorates both list cards and detail pages with responsive grid layouts
The challenge: You need event pages for 6 cities plus a landing page — each page has the same structure but different data.
The solution: Use the JSON2HTML worker as a page generation engine — one JSON data source + Mustache templates = unlimited pages. In this exercise, the entire pipeline is pre-configured. You'll explore how it works and then prove it's dynamic by adding new events.
Why use JSON2HTML:
- Zero manual authoring - Worker generates pages from data automatically
- Two templates, unlimited pages - List view + detail view from one data source
- Branch-aware - Test on your branch before production
- Easy maintenance - Update template once → all pages update
- Scalable - 6 events or 600, same templates
Real-world use cases:
- Event series across multiple cities (list + individual event pages)
- Product catalog with browse page + product detail pages
- Speaker/author directory with grid listing + individual profiles
- Store locator with map/list view + individual store pages
Understanding the entire flow — from first visit to cached edge delivery:
┌─────────────┐
│ Browser │ 1. User visits /events/sydney
│ │ 2. Page doesn't exist on edge yet → 404
└──────┬──────┘
│
│ User clicks "Update" in AEM Sidekick
│
▼
┌─────────────┐
│ JSON2HTML │ 3. Matches /events/ pattern in config
│ Worker │ 4. Fetches /future-events.json
│ (Cloudflare)│ 5. Filters to record where URL = "/events/sydney"
│ │ 6. Fetches template: /labs/exercise5/event-template
│ │ 7. Renders: Mustache template + JSON record = HTML
│ │ 8. Stores generated page on the edge (CDN)
└──────┬──────┘
│
│ HTML now on edge
│
▼
┌─────────────┐
│ Browser │ 9. Page loads from edge
│ │ 10. EDS decorates HTML (sections, blocks, wrappers)
│ [Rendered │ 11. Event block JS/CSS applies
│ Page] │ 12. User sees fully styled event detail page
└─────────────┘
┌─────────────┐
│ Browser │ User visits /events/sydney
└──────┬──────┘
│
│ GET /events/sydney
│
▼
┌─────────────┐
│ Edge │ Page already exists → served instantly
│ (CDN) │ No worker invocation needed
└──────┬──────┘
│
│ Cached HTML response
│
▼
┌─────────────┐
│ Browser │ EDS decorates → block JS/CSS applies → rendered
└─────────────┘
For the list page (/events/list):
- Same flow — click "Update" from Sidekick to generate and cache on edge
- Worker does NOT filter — passes ALL records to template
- Template loops with
{{#data}}...{{/data}}to render all event cards - EDS wraps each card in an
.event-wrapperinside one.section - CSS grid lays out cards responsively (1/2/3 columns)
Key insight: The JSON2HTML worker is a page generation engine, not a runtime proxy. It generates HTML once, stores it on the edge, and subsequent visits are served directly from the CDN — just like any other EDS page. Use "Update" from the Sidekick to regenerate pages when data or templates change.
The instructor has created a Sheet in DA.live at /future-events with upcoming masterclass events in 6 cities.
JSON endpoint: https://main--nycmasterclass--cloudadoption.aem.page/future-events.json
Key fields in each record:
| Field | Example | Used For |
|---|---|---|
city |
Sydney | Page title, headings |
country |
Australia | Location display |
date |
March 15-16, 2026 | Event dates |
venue |
Sydney Convention Centre | Venue name |
address |
14 Darling Drive, Sydney NSW 2000 | Full address |
description |
Join us for two days of... | Event description |
highlights |
12 expert-led sessions, 8 hands-on labs, ... | What's included |
image |
https://images.unsplash.com/... | Hero/card image |
registrationUrl |
https://events.adobe.com/sydney-2026 | Register button |
URL |
/events/sydney | Page path (used by pathKey) |
URL patterns: /events/sydney, /events/london, /events/bangalore, /events/berlin, /events/singapore, /events/dubai
Key concept: The worker matches incoming requests (e.g., /events/sydney) to the correct record using the URL field, then renders it using your Mustache template.
Everything is pre-configured. The instructor has already generated the pages on edge. Start by seeing the result!
Test on desktop and mobile: Use Chrome DevTools responsive view — open DevTools (F12 or Cmd+Option+I), toggle the device toolbar (Cmd+Shift+M / Ctrl+Shift+M) to verify the grid at different widths (1/2/3 columns). Use this when checking list and detail pages in this exercise.
Open the list page (replace jsmith with your branch name):
https://jsmith--nycmasterclass--cloudadoption.aem.page/events/list
You should see:
- "Upcoming Masterclass Events" heading
- Event cards in a responsive grid
- Mobile (< 600px): 1 card per row
- Tablet (≥ 600px): 2 cards per row
- Desktop (≥ 900px): 3 cards per row
- Clicking "View Details" navigates to the detail page
Open a detail page:
https://jsmith--nycmasterclass--cloudadoption.aem.page/events/sydney
You should see:
- Hero section with image, city name, date, registration button
- About, Event Details, What's Included sections
- "Back to All Events" link
Test all cities: /events/sydney, /events/london, /events/bangalore, /events/berlin, /events/singapore, /events/dubai
All pages work! (1 list + 6 detail) — No manual page creation was needed. These pages were generated by the JSON2HTML worker and are now served directly from the edge.
Note: If a page shows 404, open the AEM Sidekick on that URL and click "Update" to trigger the worker to generate and cache the page on the edge.
The instructor has created two Mustache templates in labs/exercise5/ in the repository.
Location in the repository: /labs/exercise5/events-template.html
This template loops over all records using {{#data}}...{{/data}} and renders each as an event block card:
<h1>Upcoming Masterclass Events</h1>
<p>Join us in 2026 for Edge Delivery Services training in cities worldwide.</p>
{{#data}}
<div class="event">
<div>
<div>
<picture>
<img src="{{image}}" alt="{{city}} skyline">
</picture>
</div>
</div>
<div>
<div>
<h3><a href="{{URL}}">{{city}} Masterclass</a></h3>
<p><strong>{{date}}</strong></p>
<p>{{venue}}, {{country}}</p>
<p>{{description}}</p>
<p><a href="{{URL}}">View Details</a></p>
</div>
</div>
</div>
{{/data}}Critical: All {{#data}} event blocks must be in the same section (no --- between them). This ensures EDS places all .event-wrapper elements inside one .section, allowing the CSS grid to work.
Location in the repository: /labs/exercise5/event-template.html
This template renders a single event's full details. Key snippet:
<div class="event">
<div><div><picture><img src="{{image}}" alt="{{city}} skyline"></picture></div></div>
<div><div>
<h1>{{city}} Masterclass 2026</h1>
<p><strong>{{date}}</strong> · {{venue}}</p>
<p><a href="{{registrationUrl}}">Register Now</a></p>
</div></div>
</div>
---
## About This Event
{{description}}
---
## Event Details
- **Location:** {{venue}}, {{city}}, {{country}}
- **Address:** {{address}}
- **Date:** {{date}}Mustache syntax reference:
{{variable}}— Outputs value (e.g.,{{city}}→ "Sydney"){{#array}}...{{/array}}— Loops over array (list template uses{{#data}})
Reference: Mustache Documentation
The JSON2HTML worker has been configured with two path rules. Here's the configuration that was POSTed:
[
{
"path": "/events/list",
"endpoint": "https://main--nycmasterclass--cloudadoption.aem.page/future-events.json",
"arrayKey": "data",
"template": "/labs/exercise5/events-template"
},
{
"path": "/events/",
"endpoint": "https://main--nycmasterclass--cloudadoption.aem.page/future-events.json",
"arrayKey": "data",
"pathKey": "URL",
"template": "/labs/exercise5/event-template"
}
]What this configuration does:
| Config | List Page | Detail Pages |
|---|---|---|
| path | /events/list — exact match |
/events/ — matches all /events/* |
| pathKey | (omitted) — no filtering | URL — filters to matching record |
| template | events-template — loops all records |
event-template — renders single event |
Important: /events/list must come before /events/ in the array. The worker matches top-to-bottom, and /events/ would match /events/list if it came first.
Try the JSON2HTML Simulator to see exactly how templates are rendered.
Open: https://tools.aem.live/tools/json2html-simulator/
-
JSON Data (left panel):
- Open
https://main--nycmasterclass--cloudadoption.aem.page/future-events.jsonin browser - Copy the entire JSON response and paste into the panel
- Open
-
Simulator Options (click ⚙ Options):
- arrayKey:
data - pathKey:
URL - testPath:
/events/sydney
- arrayKey:
-
Mustache Template (middle panel):
- Paste the event-template content from
labs/exercise5/event-template.html
- Paste the event-template content from
-
Click "Render" or press
Cmd+Enter
You should see: Fully rendered HTML for the Sydney event.
Try other cities: Change testPath to /events/london, /events/bangalore, etc.
- Simulator Options: Set pathKey to empty, testPath to
/events/list - Mustache Template: Paste the events-template content
- Click "Render"
You should see: HTML with all event cards rendered.
Now prove the system is truly dynamic — add new events and watch the pages generate automatically.
-
In DA.live, navigate to:
/future-eventshttps://da.live/#/cloudadoption/nycmasterclass/future-events -
You should see a spreadsheet with the existing events (Sydney, London, Bangalore, Berlin, Singapore, Dubai).
Add new rows to the sheet with new cities. For each row, fill in all columns to match the existing data format:
| Field | Example for New York | Example for Tokyo |
|---|---|---|
city |
New York | Tokyo |
country |
United States | Japan |
date |
September 20-21, 2026 | October 10-11, 2026 |
venue |
Javits Center | Tokyo Big Sight |
address |
429 11th Ave, New York, NY 10001 | 3-11-1 Ariake, Koto City, Tokyo |
description |
Two days of hands-on Edge Delivery Services training in the heart of Manhattan. | Experience EDS training in Tokyo with expert-led sessions and hands-on labs. |
highlights |
10 expert-led sessions, 6 hands-on labs, networking lunch, certification prep | 10 sessions, 6 labs, bento networking lunch, Japanese localization workshop |
image |
(use any Unsplash city image URL) | (use any Unsplash city image URL) |
registrationUrl |
https://events.adobe.com/newyork-2026 | https://events.adobe.com/tokyo-2026 |
URL |
/events/newyork | /events/tokyo |
Tip: Copy an existing row and modify the values to ensure you have all required columns.
- Preview the sheet in DA.live (click the Preview button)
- Wait a few seconds for the JSON endpoint to update
- Verify the JSON includes your new records:
https://main--nycmasterclass--cloudadoption.aem.page/future-events.json
The pages on edge are cached — they won't automatically reflect new data. You need to trigger the worker to regenerate them.
-
Open the list page in your browser:
https://jsmith--nycmasterclass--cloudadoption.aem.page/events/list -
Open the AEM Sidekick and click "Update" — this tells the JSON2HTML worker to regenerate the page with the latest data and store it on the edge.
-
Refresh the page — you should see your new events appear as additional cards in the grid alongside the original events.
-
Generate a new detail page — navigate to your new city's URL:
https://jsmith--nycmasterclass--cloudadoption.aem.page/events/newyork -
The page will initially show 404 (it's never been generated before). Click "Update" in the Sidekick to trigger the worker to generate it.
-
Refresh — you should see a fully rendered detail page for your new city.
Key takeaway: You didn't create any new templates or update any code. You only added data to the sheet and clicked "Update" — the worker + templates generated new pages automatically. This is the power of JSON2HTML.
The event block (blocks/event/event.js and event.css) uses smart CSS selectors to detect whether it's rendering a list or a detail view.
When the worker returns HTML with multiple <div class="event"> blocks in one section, EDS decorates it like this:
Input (from worker):
<div>
<div class="event">...</div>
<div class="event">...</div>
<div class="event">...</div>
</div>After EDS decoration:
<div class="section event-container">
<div class="event-wrapper">
<div class="event block" data-block-name="event">...</div>
</div>
<div class="event-wrapper">
<div class="event block" data-block-name="event">...</div>
</div>
<div class="event-wrapper">
<div class="event block" data-block-name="event">...</div>
</div>
</div>Key observations:
- Each
<div class="event">gets wrapped in a<div class="event-wrapper"> - The parent
<div>becomes<div class="section event-container"> .event-wrapperelements are direct children of.section(no intermediate div!)
The CSS uses this to differentiate list vs. detail:
/* Detail: section has exactly ONE event-wrapper */
main .section.event-container > .event-wrapper:only-child .event { ... }
/* List: section has MULTIPLE event-wrappers → apply grid */
main .section.event-container:has(> .event-wrapper ~ .event-wrapper) { ... }Why this matters: If you target main .section > div:has(> .event-wrapper) (with an extra > div), nothing will match because .event-wrapper elements are direct children of .section, not nested inside an intermediate div.
The event block code exists on the answers branch. Copy it into your branch so EDS can load it.
# From your branch, copy the event block files from answers
git checkout answers -- blocks/event/event.js blocks/event/event.css
# Verify the files are now in your working directory
ls blocks/event/
# Stage, commit, and push
git add blocks/event/event.js blocks/event/event.css
git commit -m "feat: add event block for JSON2HTML list and detail pages"
git push origin jsmithReplace jsmith with your branch name.
What you just copied:
blocks/event/event.js— Block decoration logicblocks/event/event.css— Styles for list cards and detail views
What lives in the repo (already available on your branch):
labs/exercise5/events-template.html— List page Mustache templatelabs/exercise5/event-template.html— Detail page Mustache template
What lives in DA.live (set up by instructor):
/future-events— Data sheet (JSON endpoint)
What lives in the worker service (configured by instructor):
- JSON2HTML worker configuration (path patterns, endpoints, templates)
Use Case 1: Multi-City Event Series (this exercise!)
- Data: Single JSON with all event details
- Templates: List template + detail template
- Result: Add new city → just update JSON, both list and detail auto-generate
- Scale: Hundreds of events without manual authoring
Use Case 2: Product Catalogs
- Data: Products JSON or API (SKU, price, specs, images)
- URL Pattern:
/products/(grid) +/products/laptop-model-123(detail) - Result: 2 templates → 1000+ product pages + browse pages
Use Case 3: Speaker/Author Profiles
- Data: Speakers JSON (name, bio, photo, sessions)
- URL Pattern:
/speakers/(directory) +/speakers/john-doe(profile) - Result: Dynamic speaker pages from central data
Use Case 4: Store Locator
- Data: Locations JSON (address, hours, services)
- URL Pattern:
/stores/(list) +/stores/new-york-manhattan(detail) - Result: Individual pages for each store + browsable directory
Common Pattern:
JSON Data → Worker → [Match Path + Apply Template] → HTML → EDS Decoration → Styled Page
- JSON2HTML worker is a page generation engine — it creates HTML from JSON + Mustache templates and stores it on the edge
- Generate once, serve from edge — worker runs on "Update" from Sidekick, subsequent visits are served directly from CDN
- Two templates — list template (loops with
{{#data}}) and detail template (single record) - One block, two views — the
eventblock decorates both list cards and detail pages - Add data, click "Update" — new rows in the sheet + Sidekick "Update" = new pages on edge
- CSS
:has()selector — detects list vs. detail by counting.event-wrapperchildren - EDS DOM structure —
.event-wrapperelements are direct children of.section(no intermediate div) - Responsive grid — 1 column mobile, 2 tablet, 3 desktop
- Branch-aware — Test on your branch without affecting production
- Worker config ordering matters — specific paths before general ones
- Scale effortlessly — 6 events or 600, same templates
The pattern: Data in JSON → Sidekick "Update" → Worker generates HTML → Stored on edge → Served to all visitors
- List page renders at
/events/listwith responsive grid (1/2/3 columns) - All detail pages render (
/events/sydney,/events/london, etc.) - Navigation works — list → detail via "View Details", detail → list via "Back"
- Understand list template — loops with
{{#data}}, all blocks in one section - Understand detail template — single record rendering with
{{variable}}syntax - Understand worker config — path ordering, arrayKey, pathKey, template
- Tested in simulator with real
future-events.jsondata (both templates) - Added new events to the future-events sheet in DA.live
- New events appear on list page and generate working detail pages automatically
- Understand EDS DOM —
.event-wrapperas direct children of.section - Understand complete flow: Request → Worker → JSON + Template → HTML → EDS → Styled Page
- Tested in Chrome DevTools responsive view (desktop and mobile) for list and detail pages
- Branch has event block —
blocks/event/event.jsandevent.cssavailable on your branch
List page shows cards in one column (no grid):
- Verify all
<div class="event">blocks are inside one section (one parent<div>) - Check that there are no
---section dividers between event blocks in the list template - Confirm
.event-wrapperelements are direct children of.section(inspect in DevTools) - The CSS selector requires 2+ wrappers at the same level
Pages show "Not Found":
- Verify worker config path patterns match the URL
- Check that
/events/listconfig comes before/events/in the array - Check that you're using the correct branch URL
- Ensure config was POSTed successfully (check response)
- Try hard refresh (Cmd+Shift+R or Ctrl+Shift+R) and/or use update from sidekick
Template doesn't render:
- Verify template path in config matches the relative path from the repo exactly
- Check for typos in Mustache variable names (case-sensitive!)
- Test in simulator first to isolate issues
Data missing or wrong:
- Verify
arrayKeypoints to correct array in JSON (data) - Verify
pathKeymatches the field name exactly (URL) - Check that
URLvalues in JSON match request paths
"401 Unauthorized":
- Verify admin token is correct
- Token must have permissions for config endpoint
Block not loading:
- Verify
blocks/event/event.jsandblocks/event/event.cssare committed and pushed - Check browser DevTools console for JS errors
- Ensure the block class name in HTML (
event) matches the folder name (blocks/event/)
New events don't appear on the list page:
- Verify you previewed the sheet in DA.live after adding rows (click the Preview button)
- Check the JSON endpoint directly — open
https://main--nycmasterclass--cloudadoption.aem.page/future-events.jsonand confirm your new records are in thedataarray - Verify the
URLfield in your new row follows the pattern/events/cityname(lowercase, no spaces) - Worker may cache briefly — wait 1-2 minutes and hard refresh and/or use update from sidekick
Changes don't appear:
- Worker config is cached briefly — wait 1-2 minutes
- Try hard refresh (Cmd+Shift+R or Ctrl+Shift+R) and/or use update from sidekick
- Check you're on the correct branch URL
Use Browser DevTools to debug:
- Open DevTools → Elements tab
- Inspect the
.section.event-containerto verify.event-wrapperstructure - Check Console tab for block loading errors
- Check Network tab to verify CSS/JS files are loading
- JSON2HTML Documentation
- JSON2HTML Simulator
- Admin Edit Tool
- Mustache Documentation
- EDS Markup Reference
- CSS :has() Selector
The complete solution for this exercise (event block, templates) is on the answers branch. The same branch contains solutions for all lab exercises.
Exercise 6: Form Submissions with Workers - You'll learn how to build forms that securely submit data through Cloudflare Workers to external services like Slack.