How it works
Same repo, same blocks, one extra step.
loom sits between the Adobe CDN and aem.live for HTML only. Each site brings its own hooks. The platform brings the engine.
What the page gets
Delivered in the first response.
Not after JavaScript runs. Everything below is in the HTML a crawler receives.
Rendered blocks
Accordions, product blocks and any block you opt in. The block’s own decorate() runs at the edge.
Navigation and footer
Rendered into the first response, so internal links and site context are visible on first crawl.
Inline fragments
Shared content is resolved at the edge instead of being fetched by the browser.
Correct lang, checked canonical
lang is set at the edge. The canonical check warns but never rewrites.
One clean JSON-LD graph
FAQPage from an FAQ accordion, product data and more. Google retired FAQ rich results in 2026, so the FAQ markup is for machines, not for a rich result. Duplicate types are dropped and < is escaped. Nothing is invented: FAQ text is exactly the visible text.
/llms.txt from the query index
A current map of the site for AI assistants, generated from /query-index.json. Nav, footer, fragments and noindex pages are left out.
A Markdown twin of every page
/about.md is the rendered page as clean Markdown with front matter, for LLMs and other machine readers. Off until a site opts in.
Everything else stays fast
Assets, media, JSON and .plain.html bypass the function and go straight to aem.live.
Hooks
Two kinds of hook. Both are small.
A block hook is one line. There is no second implementation to maintain: the browser and the edge run the same code, and parity tests check they produce the same output. A hook that is not listed or not present never runs, and the Markdown twin stays off until the site sets "markdown": true in scripts/edge-pipeline.json.
export { default as decorate } from './accordion.js';
export const attrs = { 'data-edge': '1' };
export function jsonld(block) { return []; }
Re-exports the block’s own decorate(). Optional: JSON-LD from the block’s visible text.
export const transform = (html, ctx) => html;
export const graph = (html, ctx) => [];
export const prefetch = (ctx) => {};
Html in, html out, in the order listed in scripts/edge-pipeline.json.
Safe by design
Built to fail quietly.
Fail open
If a hook throws, loom skips it and ships the page. A block whose decorate() throws stays exactly as authored.
Inert until switched on
Until a site’s route to loom is configured, its hooks do nothing and nothing breaks.
Build-time guardrails
Loader limit violations fail in the bundler on the developer’s machine, not on the live site.
Branch preview before merge
?branch= runs that branch’s hooks uncached and with noindex. Switch it off per site with allowBranch: false.
Site mapping owned by the CDN
Which EDS org and repo a host belongs to is declared once in sites.yaml. The CDN passes it to loom as request headers, after stripping any such header a client sends. Visitors cannot choose a site or switch on branch previews. Changing the mapping is a config pipeline run, with no function redeploy.
Fail closed on unknown hosts
A request without valid site headers is refused with 421. loom never falls back to a default site.
Observable
The X-Edge-Function: loom@ header and per-step Server-Timing show which version served a page and where the time went.
Check it yourself
Two commands, one answer.
Every response says which loom version served it.
$ U="https://<host>/<page>?v=$(date +%s)" curl -sI "$U" | grep -i -E 'x-edge-function|server-timing' # loom@<version> curl -s "$U" | grep -o 'data-edge="1"' | wc -l # edge-rendered blocks
Limits
Known limits, stated up front.
Violations fail the bundler at build time, not on the live site. A block that imports aem.js cannot run at the edge. The Markdown twin converts the origin HTML, so nav, footer and content that only an edge block hook generates are not in it.
1
fetch for all hooks
one bundle per site, no module limit
0
dynamic imports
no
import(), no bare specifiers60 s
HTML cache
a DA publish did not purge function-wrapped HTML in our tests
3
edge functions per program
one loom serves many sites
Be the answer, not just a page.
Content unchanged. Authors unchanged. Crawlers and AI assistants finally read all of it.