Skip to main content

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​

1. Eager: first section + LCPimage2. Lazy: remaining sections,header, footer, fonts3. Delayed:consent-check.js /consented.js

The boilerplate splits page loading into three phases ("E-L-D"):

  1. Eager - styles.css is loaded, the first section is decorated, and the runtime waits for the first image (the LCP candidate), switching it to loading="eager". Keep the payload before LCP small: the aem.live guidance is roughly 100KB aggregated.
  2. Lazy - right after LCP, the remaining sections and their blocks are decorated, the header and footer are loaded, and lazy-styles.css and fonts are fetched. Web fonts are normally loaded in this phase, with size-adjusted fallback fonts in styles.css, so they don't delay LCP or cause layout shift.
  3. Delayed - loadDelayed() imports consent-check.js, which imports consented.js once consent is granted (projects created before August 2026: delayed.js after a 3-second setTimeout). 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> with srcset and loading="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​

SymptomLikely causeFix
LCP > 2.5sLCP image is not in the first section, or too much code runs before itMake sure the hero block is the first block on the page; keep pre-LCP payload small
CLS > 0.1Block decoration adds elements after layoutReserve space with CSS (min-height, aspect-ratio) before decoration runs
TBT > 200msHeavy JS in eager phaseMove work to lazy or delayed
FCP slowRender-blocking third-party script in <head>Move it to consented.js
Score drops on one page onlyA specific block regressedBisect by removing blocks until score recovers

See also​