Org Skills

aem-frontend-development

Build and review Adobe Experience Manager Sites front-end code (AEM as a Cloud Service first, 6.5 noted) — the archetype ui.frontend webpack build, client libraries, Style System CSS, responsive grid, author-safe JavaScript, the Cloud Manager front-end pipeline, Site Themes and performance.

Download .zip Raw Source
When agents use itUse this whenever the user edits ui.frontend, webpack config or clientlib.config.js (aem-clientlib-generator), a clientlib .content.xml with categories, embed, dependencies or allowProxy, js.txt/css.txt, /etc.clientlibs URLs, async/defer loading, BEM styles for the Style System, aem-GridColumn or layout container breakpoints, JS that breaks the page editor or needs wcmmode checks, npm run watch, aem-site-theme-builder, the front-end pipeline, SPA Editor or Universal Editor questions, or Core Image renditions and Core Web Vitals on an AEM site. Also apply it when reviewing AEM front-end pull requests, clientlib structure or page load performance.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill aem-frontend-development -a github-copilot
# or with the org installer (adds .github/skills/aem-frontend-development):
npx -y github:AGCO-Global/org-skills add skill aem-frontend-development

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill aem-frontend-development
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill aem-frontend-development -g

Installs the aem-development-skills plugin, which bundles all AEM / Development skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install aem-development-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill aem-frontend-development -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill aem-frontend-development -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill aem-frontend-development -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill aem-frontend-development -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

AEM Front-end Development

AEM front-end code runs in two very different places: a cached, CDN-fronted publish page and an author page editor that re-renders components inside an iframe. Build one bundle per site, load it predictably, and write JS that survives both environments without knowing which one it is in.

1. Decide first

Need Use Notes
Sites built on the Maven archetype (HTL components) ui.frontend webpack build → aem-clientlib-generator → ui.apps Default for developer-owned projects
Theme-only changes, fast deploys, designer-owned CSS/JS Front-end pipeline in Cloud Manager Cloud only; deploys a dist build independently of the full-stack pipeline
Low-code sites from Site Templates Site Theme + aem-site-theme-builder + front-end pipeline Theme repo is plain npm; no Java
App-like experiences, content from AEM Headless (GraphQL / Content Fragments) + Universal Editor Preferred over SPA Editor for new work
Existing React/Angular SPA Editor project Maintain; plan migration Adobe has deprecated SPA Editor for new projects — verify current guidance before investing

2. ui.frontend and clientlib generation

  • npm run dev / npm run prod build via webpack into dist; clientlib.config.js (aem-clientlib-generator) copies the output into ui.apps/.../clientlibs/clientlib-site and clientlib-dependencies, writing .content.xml, js.txt and css.txt. Those generated folders are build output: never hand-edit them and keep them out of review noise.
  • Maven runs npm via frontend-maven-plugin; pin the Node/npm version there and commit the lockfile so local, CI and Cloud Manager builds match.
  • Webpack already minifies, so set processors to avoid double processing:
// clientlib.config.js (excerpt)
libs: [{
  name: 'clientlib-site', allowProxy: true, serializationFormat: 'xml',
  categories: ['mysite.site'], dependencies: ['mysite.dependencies'],
  cssProcessor: ['default:none', 'min:none'], jsProcessor: ['default:none', 'min:none'],
  assets: { js: ['dist/clientlib-site/*.js'], css: ['dist/clientlib-site/*.css'],
            resources: { cwd: 'dist/clientlib-site', files: ['**/*.*'], flatten: false,
                         ignore: ['**/*.js', '**/*.css'] } }
}]
  • One entry per site (plus an optional small dependencies lib). Split further only with evidence (e.g. a heavy component loaded on few pages).

3. Client libraries

<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:ClientLibraryFolder" allowProxy="{Boolean}true"
    categories="[mysite.base]" embed="[mysite.components.hero,mysite.components.teaser]"/>
  • embed concatenates other categories into this one — one request. dependencies emits a separate <script>/<link> before it — use sparingly, each is a request.
  • allowProxy="{Boolean}true" on every lib under /apps: publish denies reads on /apps, so assets must be served as /etc.clientlibs/<project>/clientlibs/.... Reference fonts and images relative to the clientlib resources folder, never /apps/... paths.
  • Component clientlibs are embedded into the site lib. Do not include them per component in HTL; duplicated includes and render-blocking scripts follow.
  • Include once, from the page component (or the Core Page policy's clientlib settings): CSS in <head>, JS at end of body or deferred.
<sly data-sly-use.clientlib="core/wcm/components/commons/v1/templates/clientlib.html">
  <sly data-sly-call="${clientlib.css @ categories='mysite.site'}"/>
  <sly data-sly-call="${clientlib.js @ categories='mysite.site', defer=true}"/>
</sly>

The Core Components clientlib template supports async/defer and related attributes; the older /libs/granite/sightly/templates/clientlib.html does not. async breaks ordering between libs — prefer defer.

  • Cache busting: AEM as a Cloud Service serves long-cache, content-hashed clientlib URLs out of the box; on 6.5 use a versioned-clientlib solution (e.g. ACS AEM Commons) plus Dispatcher cache headers.
  • Author-only code (dialog validators, editor extensions) goes in categories such as cq.authoring.dialog or a component's extraClientlibs, never in the site bundle.

4. CSS: BEM, Style System, responsive grid

  • Core Components emit BEM (.cmp-teaser, .cmp-teaser__title). Follow the same scheme; one SCSS partial per component under ui.frontend/src/main/webpack/components/.
  • Style System classes are added to the component wrapper, not the inner cmp- element. Name them as modifiers and scope descendants:
.cmp-teaser--dark .cmp-teaser__title { color: var(--color-inverse); }
.cmp-teaser--hero-layout .cmp-teaser { display: grid; grid-template-columns: 1fr 1fr; }
  • Styles must not depend on the markup order of another component, on author-only wrappers, or on !important to beat Core defaults — raise specificity with the block class instead.
  • Design tokens as CSS custom properties; themes and brands swap token values, not selectors.
  • Layout Container uses aem-Grid / aem-GridColumn--<breakpoint>--<n> classes. Breakpoint names and widths in the template's cq:responsive config must match the generated grid SCSS; a mismatch makes layout mode lie to authors.
  • Never style .aem-GridColumn directly for component looks; use the component's own classes.

5. JavaScript that respects the editor

  • Initialise from markup: select [data-cmp-is="hero"], read config from data-* attributes, and re-run init for nodes added later. The page editor replaces component DOM after every dialog save, so bind with a MutationObserver (as Core Components do) or event delegation, not a single DOMContentLoaded pass.
const SEL = '[data-cmp-is="hero"]';
const init = el => { if (el.dataset.cmpInit) return; el.dataset.cmpInit = 'true'; /* ... */ };
const scan = node => {
  if (node.nodeType !== 1) return;
  if (node.matches(SEL)) init(node);
  node.querySelectorAll(SEL).forEach(init);
};
document.addEventListener('DOMContentLoaded', () => {
  scan(document.body);
  new MutationObserver(ms => ms.forEach(m => m.addedNodes.forEach(scan)))
    .observe(document.body, { childList: true, subtree: true });
});
  • In edit mode, disable behaviour that fights authors: autoplaying carousels, scroll hijacking, modals on load, preventDefault on clicks. Emit a flag from HTL (e.g. data-wcmmode="${wcmmode.edit ? 'edit' : ''}" on <body>) rather than sniffing URLs or window.Granite.
  • Preview with ?wcmmode=disabled on author and always test on publish; author injects extra wrappers, overlays and CSS that hide bugs.
  • No global namespace pollution, no jQuery in new code unless the project already depends on it; ES modules bundled by webpack.

6. Front-end pipeline and Site Themes (Cloud)

  • The front-end pipeline runs npm run build on a front-end module and expects a dist folder; it deploys theme artifacts independently, so CSS/JS changes ship without a full-stack deployment. The site must be configured to load the pipeline-deployed theme — check current Adobe docs for archetype versions and settings that enable this.
  • Site Templates themes: local development with aem-site-theme-builder (npm run live proxies a real AEM site and injects your local theme), then deploy through the front-end pipeline.
  • Keep one source of truth: do not mix a pipeline-deployed theme with a clientlib-shipped copy of the same CSS.

7. Performance

  • Budget per page: one site CSS, one deferred site JS, optional small vendor lib. Measure with Lighthouse/CrUX on publish behind the CDN, not on author.
  • Critical CSS: inline a small above-the-fold subset only if measured LCP gains justify the maintenance; otherwise keep one cacheable stylesheet.
  • Images: Core Image component with widths configured in the policy (srcset), lazy loading, and web-optimized image delivery on Cloud (or Dynamic Media / Smart Imaging where licensed). Never serve original DAM binaries or hand-built rendition paths. Give the LCP image eager loading and explicit dimensions.
  • Fonts: self-host in clientlib resources, font-display: swap, preload the one or two critical faces.
  • Third-party tags via a tag manager with async loading; audit them — they usually dominate TBT.

8. Accessibility

Visible focus styles, colour contrast from tokens (checked for each Style System variant), prefers-reduced-motion for animations, keyboard support for custom widgets, and no CSS that hides content screen readers need (display:none on labels). Run axe in CI against publish-mode renders of component library pages.

9. Local development

npm run watch (archetype) rebuilds and syncs clientlibs to a local SDK/Quickstart; the webpack dev server (npm start) serves static markup for isolated styling. Community tools such as aemfed add sync + live reload. Validate against the local AEM SDK Dispatcher before pushing — clientlib proxy, cache headers and filters differ from author.

Deliverable format

For new or changed front-end work, deliver: the SCSS/JS source under ui.frontend (or theme repo), clientlib.config.js or clientlib .content.xml changes with categories and how they are included, the HTL include (if changed), any Style System policy entries (style group, label, class), and a short verification note (author edit mode, publish, breakpoints, Lighthouse/axe). For reviews, list findings by severity (editor breakage, security/proxy, performance, accessibility, maintainability) with file and fix.

Anti-patterns to reject

  • Hand-editing generated clientlib folders; committing dist output alongside source without a reason.
  • Missing allowProxy, or URLs pointing to /apps/... from CSS/JS.
  • Per-component clientlib includes in HTL; long dependencies chains instead of embed.
  • Render-blocking JS in <head>; async on libs that depend on each other.
  • JS that initialises once on page load and breaks after an author edits a component; autoplay or modals active in edit mode.
  • Style System variants implemented as new components or as dialog fields; !important wars with Core CSS.
  • Grid breakpoints in CSS that differ from the template's responsive config.
  • Serving original DAM images; no srcset; lazy-loading the LCP image.
  • Starting a new project on SPA Editor without checking current Adobe guidance on headless + Universal Editor.
  • Author-only scripts in the publish bundle; testing performance only on author.