One YAML file per component. Everything else generated.
The client's enterprise SaaS learning platform, which I rebuilt as a modern AEM Cloud component system. The headline is a schema-first code-generation pipeline I designed: a single YAML definition per component becomes the TypeScript types, the Lit scaffolds, the AEM author dialogs and the HTL — so the same component is never hand-written in four slightly different places. Built and delivered to the client; launch is pending SSO, so it isn't public yet.
·
The problem with hand-built AEM components
An AEM component is really four artefacts that have to agree with each other: a Java/HTL template that renders server-side, an XML dialog that tells authors what fields exist, a TypeScript model for any client behaviour, and the CSS. Keep them in sync by hand across dozens of components and they drift — a field gets renamed in the dialog but not the model, a type goes stale, a new author option never reaches the template. Multiply that by a team and a multi-month build and the drift is constant.
The pipeline
I made the YAML schema the single source of truth. Each component has one component.yaml;
npm run generate emits the TypeScript interfaces, the Lit scaffold and the CSS scaffold,
and generate:aem emits the dialog XML, component XML, EditConfig and HTL. The YAML became
the contract between frontend and backend — change a field once and every downstream artefact follows.
one per component TypeScript interfaces Lit 3 scaffold CSS scaffold (tokens) AEM dialog + component XML EditConfig HTL template
Why it matters to a team: a new component is a YAML file and a generate command, not a half-day of boilerplate spread across four file types. Junior engineers and AI assistants extend the platform by editing a schema, not by reverse-engineering conventions — the generator enforces them.
The hardest bug
Composite multifields — nested repeatable groups of fields — were generating dialog XML that
AEM accepted but silently rendered wrong when a nestedType reference was missing. Silent
failure is the worst kind in a generator, because the output looks plausible. The fix was to make the
generator throw on any missing nestedType reference rather than emit
almost-correct XML. Correctness moved left, to generate time, where a developer sees it immediately.
Performance by construction
AEM renders HTML server-side, so the page doesn't need a client framework hydrating the whole tree —
only islands of interactivity do. I chose Lit 3 over React for exactly that reason and
built progressive hydration with several strategies (eager, idle, visible, interaction)
so each component hydrates only when it needs to. Design tokens live in cascade layers
with a no-!important rule at org scale, and Vite handles manual-chunk splitting.
- Vite 7
- Lit 3
- TypeScript 5.9 (strict)
- Zod 4
- Lightning CSS
- Open Props
- Storybook 10
- cascade layers
- progressive hydration
- AEM Cloud
- HTL / Sling Models
- GitLab CI
- Adobe Cloud Manager
- axe-core
- Playwright
Real Java, not just frontend
The platform isn't frontend-only. I wrote around 30 Sling Models, an OSGi CourseService, and a Core WCM Accordion extension — the backend the generated components bind to. Coming up through AEM means I can drop into Java when a feature genuinely needs it, rather than throwing it over a wall.
Quality and release
Every component carries axe-core checks and Storybook visual-regression coverage; the suite runs
JUnit + Selenium with Allure reports on GitLab Pages. Releases are semver with conventional commits and
zero-touch deploys to Adobe Cloud Manager. AI-agent tooling (AGENTS.md / CLAUDE.md
plus 17 domain skill files) lets autonomous sessions extend the codebase along the same conventions the
generator enforces.
The shape of the contribution: I authored the architecture and the overwhelming majority of the repository — roughly 292 of 294 commits, about 116k lines and 36 PRs in the first five weeks — then set the conventions that let other engineers extend it safely.
Back to selected work · Next: a global pharmaceutical client, three regulated platforms · Talk to me about a similar build