Performance
EDS is designed to ship a Lighthouse score of 100 out of the box. The pipeline, the boilerplate, and the conventions all push you toward Core Web Vitals compliance unless you actively work against them.
Three-phase loading strategy
The boilerplate splits page loading into three phases ("E-L-D"):
- Eager -
styles.cssis loaded, the first section is decorated, and the runtime waits for the first image (the LCP candidate), switching it toloading="eager". Keep the payload before LCP small: the aem.live guidance is roughly 100KB aggregated. - Lazy - right after LCP, the remaining sections and their blocks are decorated, the
header and footer are loaded, and
lazy-styles.cssand fonts are fetched. Web fonts are normally loaded in this phase, with size-adjusted fallback fonts instyles.css, so they don't delay LCP or cause layout shift. - Delayed -
loadDelayed()importsconsent-check.js, which importsconsented.jsonce consent is granted (projects created before August 2026:delayed.jsafter a 3-secondsetTimeout). The boilerplate no longer enforces a minimum delay. Analytics, chat widgets, and marketing tags belong here.
The phases are wired in scripts/scripts.js via its own loadEager(), loadLazy(), and
loadDelayed() functions, built on helpers from aem.js. See Blocks
for how a block participates and Customizing for how
to extend each phase.
What EDS does automatically
- Image optimisation - authored images are served from the media bus as WebP with a
JPEG/PNG fallback, in several widths (
createOptimizedPicture()builds the<picture>withsrcsetandloading="lazy"by default). - CSS / JS code splitting - each block's styles and scripts load independently.
- Small critical path - only
styles.css,aem.js,scripts.js, and the first section's block code are needed before LCP; everything else loads later. - Minimal DOM - clean semantic HTML with no framework overhead.
- Aggressive caching - long CDN cache TTLs with push invalidation on publish.
LCP optimisation
The single biggest determinant of Lighthouse score is the LCP element. The runtime waits for
the first image in the first section and switches it to eager loading. When you build your own
hero markup, keep that first image eager (do not add fetchpriority="high" or preload hints):
<picture>
<source type="image/webp" srcset="..." />
<img loading="eager" alt="..." />
</picture>
Things that hurt LCP:
- Hero images served from a third-party domain (an extra connection on the critical path)
- Custom fonts loaded synchronously in the eager phase
- Render-blocking client-side JS in the eager phase
- Block decoration that wipes and rewrites the DOM (each rewrite triggers a layout)
Measuring on every PR
The AEM GitHub bot runs PageSpeed Insights against the test URLs in the pull request description (the boilerplate's PR template has a "Test URLs" section for this) and fails the PR if the score is below 100 (with a small buffer).
The delayed-loading rule of thumb
Anything not needed for the first meaningful paint belongs in consented.js:
- Analytics (Adobe Analytics, Adobe Launch, Google Analytics, Plausible, etc.)
- Chat widgets (Intercom, Drift)
- A/B testing scripts that run client-side
- Social-share widgets
- Cookie-consent bars (carefully - some compliance regimes require these to render immediately)
- Recommendations / personalisation that doesn't drive layout
Common performance regressions
| Symptom | Likely cause | Fix |
|---|---|---|
| LCP > 2.5s | LCP image is not in the first section, or too much code runs before it | Make sure the hero block is the first block on the page; keep pre-LCP payload small |
| CLS > 0.1 | Block decoration adds elements after layout | Reserve space with CSS (min-height, aspect-ratio) before decoration runs |
| TBT > 200ms | Heavy JS in eager phase | Move work to lazy or delayed |
| FCP slow | Render-blocking third-party script in <head> | Move it to consented.js |
| Score drops on one page only | A specific block regressed | Bisect by removing blocks until score recovers |
See also
- Blocks - block lifecycle and the eager / lazy / delayed phases
- Customizing -
head.html,scripts.js,consented.js - Architecture - pipeline-level optimisations
- Keeping it 100 (aem.live) - official performance guidance and Core Web Vitals targets
- web.dev Lighthouse - measure and analyse Lighthouse scores
- Core Web Vitals (web.dev) - LCP, CLS, INP reference