Content operations skills for AEM Edge Delivery Services: page auditing, SEO optimization, AI search (GEO), WCAG accessibility, bulk metadata, structured data, sitemap validation, and content diffing. Complements the EDS development skills with operational tooling for content teams.
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
The rule of thumb for Edge Delivery Services: a page scores 100 in PageSpeed Insights on mobile when what it downloads before the LCP stays under 100 KB and comes from one origin (Keeping it 100). This skill measures that payload for a page, names what spends it, and adds a check that keeps it from growing back.
It covers the bytes before LCP only. For CLS, INP, or a slow page without a byte question, use cwv-optimizer. The budget is a rule for Edge Delivery Services; don't apply it to other sites.
Run Lighthouse (it tests a throttled mobile device by default) and list what was requested before the LCP element was ready. scripts/bytes-before-lcp.jq is in this skill's directory.
npx --yes lighthouse@13.6.0 "https://<branch>--<repo>--<owner>.aem.live<path>" \
--only-categories=performance --output=json --output-path=lh.json --quiet \
--chrome-flags="--headless=new"
jq -r -f scripts/bytes-before-lcp.jq lh.jsonThe output has the LCP element, one line per request (transfer bytes, type, SECOND ORIGIN where it applies, URL) and the total. Transfer bytes are compressed, as the browser received them.
.aem.live URL. A second run on the production host shows what the customer's CDN adds.lcpSource (selector) and lcpTarget (URL) facets of @adobe/rum-distiller from RUM data; cwv-optimizer shows how to load the bundles.references/performance-budget-rules.md. A curl measurement has no LCP element: report it as unknown and ask the user for it. Never infer it from the page structure.Go through the request list. Only the HTML, styles.css, aem.js, scripts.js, the CSS and JS of the blocks in the first section, and the LCP image belong there.
| In the list | What it means in Edge Delivery Services | Fix |
|---|---|---|
SECOND ORIGIN | A second connection (DNS, TLS) before LCP. aem.live strongly discourages it. | Serve it from the site's origin in the lazy phase, or load it in the delayed phase. |
Font | The boilerplate loads styles/fonts.css only after the first section is shown, and paints first with size-adjusted fallback fonts. A font before LCP means an @font-face in styles.css or a font preload in head.html. | Move the @font-face rules to styles/fonts.css. Never recommend preloading a font. |
| CSS or JS of a block that is further down the page | The whole first section loads before LCP, and nothing else should. Either the first section holds these blocks, or scripts.js imports them. | Put a section break after the hero, or remove the static import. |
Fetch of JSON, a fragment or .plain.html | Something needed for the first section sits behind a lookup. The header and footer load in the lazy phase, so nav.plain.html here means the header was moved into the eager phase. | Move the lookup to the lazy phase, unless the LCP element itself comes from it. |
| Two large images | Often a desktop and a mobile hero that are both in the document. | Have the block remove the one it doesn't show before the first section loads. |
| A large LCP image | Images from content are resized and converted to WebP by the media pipeline. The bytes follow from the width the <picture> asks for and from the picture itself. | Request a smaller width in the block, or use a simpler image. Don't recommend converting or compressing content images. |
| A tag manager, analytics, consent or chat script | Third-party scripts belong in the delayed phase. | See below. |
Also read head.html: <link rel="preload">, preconnect and fetchpriority="high" spend the budget and don't improve LCP in PageSpeed Insights. Recommend removing them.
Not a defect: every <img> in the delivered HTML has loading="lazy". aem.js sets the first image of the first section to eager at runtime.
Check which of the two the project has before you name a file:
loadDelayed() in scripts.js imports scripts/consent-check.js, which imports scripts/consented.js once the visitor has consented. Third-party scripts go in consented.js. Nothing enforces a delay.loadDelayed() imports scripts/delayed.js after a 3-second timer. Third-party scripts go in delayed.js.The AEM GitHub bot already runs PageSpeed Insights on a pull request that names a test URL in its description, and fails it when the mobile score is below 100, with some buffer. That checks the score, not the bytes.
To fail a pull request over the byte budget, add the workflow in assets/performance-budget.yml to the project:
.github/workflows/performance-budget.yml.scripts/bytes-before-lcp.jq to .github/scripts/bytes-before-lcp.jq.PAGES in the workflow to the paths to check, one per template (home page, an article, a product page).It measures the branch's .aem.page preview with the same command as Step 1, after it has waited for AEM Code Sync. Pull requests from forks are skipped, because their branch has no preview on this site. A branch whose branch--repo--owner is longer than 63 characters has no preview either, and fails the check with that message. Show the user the workflow and ask before you commit it.
.tessl-plugin
skills
accessibility-fix
bulk-metadata
content-audit
content-diff
cwv-optimizer
geo-rewrite
heading-optimizer
image-seo
internal-linking
link-rot-scanner
optel-interpreter
performance-budget
product-page-seo
query-index-optimizer
sitemap-audit
structured-data