AEM Edge Delivery Services

Getting Started — USTA & USTA Foundation

A developer guide for setting up your local environment, understanding the project architecture, and working with content across both EDS sites built on the AEM Block Collection.

Project overview

This project supports two production sites from a single GitHub repository using a repoless architecture — there is no fstab.yaml; content routing is handled entirely through Document Authoring (DA) org configuration.

Site 1
USTA
usta.com
Improve section — tips, health & fitness, gear, rules. 200+ article pages.
Site 2
USTA Foundation
ustafoundation.com
Foundation pages — who we are, what we do, impact, news.
AttributeValue
GitHub orgAdobeDevXSC
Repositoryema-usta
Primary branchmain
ArchitectureRepoless EDS (no fstab.yaml)
Content systemDA (Document Authoring via da.live)
Base boilerplateaemsites/da-block-collection
Code: JS / CSS / HTML73% / 26% / 1%

Key URLs

GitHub

Document Authoring (DA)

Preview & live environments

The URL pattern for any environment or branch is {branch}--{repo}--{org}.aem.page (preview) or .aem.live (published). Swap main for any branch name to preview your feature branch.

Architecture

Both sites share a single codebase. EDS resolves which DA content org to serve based on the hostname — the repoless pattern maps each domain to its DA org without a checked-in fstab.yaml.

DA: adobedevxsc/ema-usta
+
DA: adobedevxsc/usta-foundation
GitHub: AdobeDevXSC/ema-usta
Single shared codebase
*.aem.page (preview)
|
*.aem.live (published)

Repository structure

# Key directories /blocks # 25 EDS blocks (16 standard + 9 USTA-custom) /scripts # aem.js · scripts.js · context.js · delayed.js /styles # styles.css · fonts.css · context.css · lazy-styles.css /fonts # Graphik woff2 files (Regular, Semibold, XXCond Bold) /icons # SVG icons (heart, player-avatar, search, foundation logo) /tools # Importer scripts, sidekick config, demo apps /plugins # Experimentation plugin (A/B testing, audiences, campaigns) /ue # Universal Editor block models (15 blocks) /.github # CI workflow: lint on push (ESLint + Stylelint, Node 20) /.migration # Migration project config (.migration/project.json)

Local development

Prerequisites

ToolInstallNotes
Node.js 20+nodejs.orgRequired for AEM CLI and linting
AEM CLIsudo npm install -g @adobe/aem-cliProvides aem up local server
npm packagesnpm iESLint, Stylelint, Husky pre-commit hook
AEM Code SyncGitHub AppSyncs code changes to AEM on push

Step-by-step setup

  1. 1
    Clone the repository

    Clone to your local machine and open in your editor of choice.

    git clone https://github.com/AdobeDevXSC/ema-usta.git cd ema-usta
  2. 2
    Install the AEM CLI

    The AEM CLI provides a local development server that proxies content from DA and code from your local filesystem. Install it globally via npm:

    sudo npm install -g @adobe/aem-cli

    Verify the install:

    aem --version

    If aem is not found after install, make sure your global npm bin is on your PATH — find it with npm bin -g.

  3. 3
    Install npm packages

    This sets up linting (ESLint + Stylelint) and the Husky pre-commit hook that auto-regenerates Universal Editor component JSON.

    npm i
  4. 4
    Start the local dev server

    Run aem up with the --url flag pointing to the site you want to work against. Each site has its own DA content org and preview URL:

    # USTA — DA: da.live/#/adobedevxsc/ema-usta aem up --url=https://main--ema-usta--adobedevxsc.aem.page/ # USTA Foundation — DA: da.live/#/adobedevxsc/usta-foundation aem up --url=https://main--ustafoundation--adobedevxsc.aem.page/

    This opens http://localhost:3000 and proxies content from the specified DA preview environment. Your local /blocks, /scripts, and /styles are served from disk against whichever site's content you've targeted.

After each code change, the browser hot-reloads automatically — no manual refresh needed for JS/CSS changes to blocks.

GitHub workflow

CI pipeline

Every push to any branch triggers the GitHub Actions lint workflow (.github/workflows/main.yaml). It runs on Node.js 20 and executes npm run lint — which covers both ESLint (JS) and Stylelint (CSS). PRs should be green before merging to main.

Pre-commit hook

Husky runs npm run build:json on every commit. This regenerates the merged Universal Editor component JSON files (component-definition.json, component-filters.json, component-models.json) from the per-block model sources in /ue/models/blocks/. Never edit the root JSON files directly — they are auto-generated.

Previewing a PR branch

Every branch has its own preview URL automatically. Substitute the branch name into the EDS URL pattern:

# Pattern https://{branch}--ema-usta--adobedevxsc.aem.page # Example: PR branch named "feature/new-hero" https://feature-new-hero--ema-usta--adobedevxsc.aem.page
Slashes in branch names become hyphens in the preview URL. A branch named feature/nav-update becomes feature-nav-update--ema-usta--adobedevxsc.aem.page.

AEM Code Sync

The AEM Code Sync GitHub App must be installed on the AdobeDevXSC org and granted access to this repository. It watches for pushes and syncs code to the AEM CDN, making your branch immediately available at the preview URL above.

Document Authoring (DA)

All page content is managed through DA at da.live. There are two separate DA content orgs — one per site — both mapped to the same GitHub repository.

Org structure

SiteDA orgContent scope
USTA adobedevxsc/ema-usta Improve section (tips, fitness, gear, rules) · nav · footer · metadata
USTA Foundation adobedevxsc/usta-foundation Foundation pages · hero sections · impact · news

Content authoring workflow

Content is created and edited as documents in DA. Authors write blocks using a table syntax — each table row defines a block and its content. When content is previewed or published from the DA sidekick, the EDS pipeline renders the tables into the corresponding block JS/CSS.

As a developer, you'll primarily use DA to browse real content for local testing and to troubleshoot block rendering issues. The sidekick's "Preview" action syncs DA content to .aem.page; "Publish" promotes it to .aem.live.

Query index

The USTA Improve section generates a query index at /en/home/improve/query-index.json (configured in helix-query.yaml). The filter-cards block consumes this index to dynamically render and filter article cards by category. Fields: path, title, description, date, lastModified, featured, author, image, tags.

Blocks reference

The project has 25 total blocks — 16 inherited from the AEM Block Collection and 9 custom blocks built specifically for USTA. Each block lives in its own directory under /blocks/{block-name}/ containing a JS file and a CSS file.

Custom USTA blocks

BlockDescription
cards-iconCards layout with icon images — USTA-specific styling
columns-featureFeature columns with background colour/image styling
columns-statsStatistics in a column layout with large numerals
filter-bubbleFilter chip/tag UI — dispatches filter-change events
filter-cardsDynamic cards from query-index.json · 6-per-page · listens for filter-change
hero-landingLanding page hero variant
near-youPlaceholder for location-based dynamic content
player-profileTennis player profile display
player-rankingsPlayer rankings table (currently mock data)

Hero variants

The hero block has three body-class-scoped variants that render completely different layouts depending on which site/section the page belongs to:

VariantTriggerLayout
Defaultbody .heroRelative-position background image, white H1, max-width 1200px
Foundationbody.foundation .heroFull-height, dark gradient overlay, left-aligned white text + CTA buttons
Improvebody.improve .heroBanner-style (280px mobile / 340px desktop), teal overlay, centered white H1

Developer skills

Highlighted skills and patterns to be productive on this project from day one.

JS Block authoring

Each block exports a default(block) function. The block element is the decorated DOM node — manipulate its children, query it, decorate it. No framework, no bundler.

JS filter-change events

filter-bubble dispatches a custom filter-change event on document. filter-cards listens and re-renders. Add new listener blocks using document.addEventListener('filter-change', ...).

JS Experimentation plugin

A/B testing, audience targeting, and campaigns are handled by the plugin in /plugins/experimentation/. Experiments are configured via metadata in DA documents.

CSS Body-class theming

Site theme is set by a body class (body.foundation, body.improve). Block CSS selectors scope to these classes to achieve per-site visual variation from one codebase.

CSS Section styles

Authors apply section backgrounds via metadata: .light / .highlight (gray), .related-articles (olive/sage), .gold (yellow). These map to CSS classes in styles.css.

DA Repoless routing

No fstab.yaml — DA org configuration handles hostname-to-content-path mapping. Understanding which DA org a URL resolves to is essential for debugging content issues.

JS UE models

Block models for Universal Editor live in /ue/models/blocks/. Run npm run build:json to regenerate root component JSON files after editing any model. The Husky hook does this automatically on commit.

JS Import infrastructure

Bulk content migration is handled by Playwright-driven import scripts in /tools/importer/. Four scripts target different source site sections; parsers and transformers handle DOM cleanup.

CSS Responsive grid (filter-cards)

filter-cards uses a 3-column grid on desktop, 2-column on tablet, 1-column on mobile. Cards paginate in groups of 6 via a "More" button — no infinite scroll.

The full block inventory, hero variant details, import script docs, and UE model specs are documented in USTA-FOUNDATION.md in the repository root.