AEM Dynamic Include 结合 Cloudflare ESI:构建极致性能的全球品牌站点
发布时间:2026-07-31
作者:邓翀
一、引言
对于一个面向全球用户的品牌官网而言,“快”从来不是一个可选项,而是生命线。据 Google 研究数据,页面加载时间从 1 秒增至 3 秒,跳出率将上升 32%;而每加速 100 毫秒,电商转化率可提升约 1%[1]。
以一家国际律师事务所的官方网站为例,它往往具备以下典型特征:
多语言、多区域:同一套站点需要支撑中、英、日、德、西等多语言版本,访客遍布全球;
内容频繁更新:热点洞察、媒体报道、律师团队动态几乎每天都在变化;
页面结构复杂:页头导航、页脚、面包屑、“相关洞察”、“相关律师”等模块大量复用,且许多模块的内容是由后端动态聚合查询生成的;
品牌体验要求高:首屏加载速度直接影响客户对品牌的专业度认知。
在经典的 AEM(Adobe Experience Manager)架构中,页面由 Dispatcher 整页缓存来扛住流量。然而一旦页面上存在“动态组件”——比如需要根据当前页面上下文实时聚合的“相关洞察”列表——传统的做法只有两个:
1. 整页不缓存,每次请求都回源到 AEM Publish 渲染——性能灾难;
2. 用 Ajax 前端异步加载动态部分——首屏闪烁、SEO 不友好、体验割裂。
有没有第三条路?答案是肯定的:AEM Dynamic Include(SDI)+ Cloudflare ESI。这套组合让我们在某国际律所的全球官网上,实现了“页面主体边缘缓存 + 动态片段边缘拼装”的架构,全球首屏 TTFB 稳定在毫秒级。
二、技术选型:为什么是 SDI + Cloudflare ESI
ESI(Edge Side Includes)是一种在 CDN 边缘节点执行的“服务端包含”标记语言。页面 HTML 中可以嵌入如下标签:
<esi:include src="/content/.../header.nocache.html" onerror="continue"/>
当 CDN 节点向用户返回页面时,会在边缘解析该标签、发起子请求获取片段内容、将片段在边缘侧内联拼装进主文档后再返回给浏览器。浏览器拿到的是一份完整的、无差异的 HTML。
SDI(Sling Dynamic Include)是 Apache Sling 官方开源组件,它在 AEM 渲染阶段介入:对被标记为“动态”的组件,不直接渲染其内容,而是输出 ESI 占位符。SDI 支持三种包含类型:
| 类型 | 执行位置 | 特点 |
| SSI | Apache / Dispatcher | 传统方案,依赖 Apache mod_include,无法利用 CDN 边缘能力 |
| ESI | CDN 边缘节点 | 片段可被 CDN 独立缓存,边缘拼装,全球加速 |
| JSI | 浏览器(Ajax) | 有白屏闪烁,SEO 不友好 |
选择 ESI 的关键优势在于:动态片段本身也可以被 CDN 独立缓存,主页面与动态片段各自独立缓存、独立失效——这是 SSI 方案做不到的。
传统上 ESI 是 Akamai 等商业 CDN 的高端功能。而 Cloudflare 虽然不原生支持 ESI 协议,但其 Workers 无服务器计算平台配合 TransformStream 流式处理能力,让我们可以在边缘实现一个高性能的 ESI 解析器,依托全球 300+ 边缘节点就近接入,Worker 子请求可直接命中边缘缓存,成本远低于传统商业 ESI 方案。
三、架构总览
整体请求链路如下:

(图片展示了AEM Dynamic Include + Cloudflare ESI完整链路)
关键设计要点:
1. 主页面:完整 HTML 在 Cloudflare 边缘与 Dispatcher 双层缓存,绝大部分请求不回源 AEM;
2. [sdi-header] 握手开关(示例名称):Cloudflare 回源主页面时注入该请求头,SDI 才会输出 ESI 占位符;Worker 发起片段子请求时刻意不携带该 header,SDI 便直接渲染组件真实内容——一个 header 区分了“要占位符”与“要真实内容”两种渲染模式;
3. 动态片段:以 nocache selector 独立成 URL,由 Cloudflare 边缘独立缓存(TTL 24 小时);
4. 流式拼装:Worker 使用 TransformStream 逐块处理,不缓冲整页,TTFB 几乎不受影响。
四、落地实战
4.1 AEM 侧:SDI 配置
将 SDI bundle 嵌入 ui.apps 包安装。本项目使用的是社区版 3.3.x 的定制版本,扩展了两项关键能力:required_header(握手开关)和 referParam(上下文传递)。
SDI 以 resourceType 为粒度配置哪些组件走动态包含,在 config.publish 运行模式下为 30 余个组件建立了独立配置。核心配置项包括:
include-type="ESI":输出 <esi:include> 标签,交由 CDN 边缘拼装;
selector="nocache":片段请求带上 .nocache.html selector,与主页面 URL 区分,便于缓存策略分离;
add_comment="true":输出中附带 <!-- SDI include (path: ...) --> 注释,排查问题时一目了然;
required_header(定制扩展):配置一个约定 header 名称(本文示例记作 [sdi-header]),仅当请求携带该 header 时 SDI 才输出 ESI 占位符。直连 Dispatcher 的常规请求依然返回完整渲染页面,只有经过 CDN(显式注入 header)的流量才得到占位符版本——灰度与回滚都极为安全;
referParam(定制扩展):片段 URL 上自动附加当前页面路径作为查询参数,使片段组件在渲染时能感知“自己被包含在哪个页面里”——“相关洞察”、“面包屑”等上下文敏感组件全靠它。
配置覆盖了导航、面包屑、相关洞察、相关律师、首页 Banner、关键联系人等 30 余个组件。
以首页为例,SDI 介入后 AEM 输出的 HTML 中,动态组件位置变成:
<div class="dynamic-container ...">
<!-- SDI include (path: /content/experience-fragments/<site>/<locale>/header/.../dynamic-container.nocache.html?referParam=/content/<site>/<locale>/home.html, resourceType: <project>/components/content/dynamic-container) -->
<esi:include src="/content/experience-fragments/<site>/<locale>/header/.../dynamic-container.nocache.html?referParam=/content/<site>/<locale>/home.html" onerror="continue"/>
</div>
注意头部导航来自 Experience Fragment(体验片段),这意味着全站所有页面共享同一个头部片段 URL——整站头部只缓存一份,更新一次导航,边缘一次失效,全站即时生效。
4.2 Cloudflare 侧:Worker ESI 引擎
Cloudflare Worker 的核心职责:拦截响应流 → 识别 <esi:include> 标签 → 发起子请求 → 将片段内容内联替换 → 流式输出给用户。
流式解析是性能关键。 Worker 使用 TransformStream 逐块处理响应流,而非将整页读入内存。解析器按 <、> 边界切分 HTML 标签流,匹配到 esi:include 标签时提取 src 属性发起子请求,将返回的片段内容内联替换后继续流向用户。这样首字节几乎无延迟地流向浏览器,TTFB 不受拼装逻辑影响。Worker 异常时通过 passThroughOnException() 直接回源,兜底保障可用性。
片段边缘缓存。 子请求通过 cf 属性强制走 Cloudflare 边缘缓存,TTL 24 小时。由于头部、页脚、面包屑等片段被全站海量页面共享,一个片段在边缘缓存命中后,24 小时内所有引用它的页面都无需回源 AEM,Publish 实际负载下降到原来的个位数百分比。
header 差异化处理。 这是整套架构中最容易遗漏、却又决定成败的一步。SDI 只在请求携带约定 header 时才输出 ESI 占位符,因此 Cloudflare 端必须对两类请求做差异化处理:
主页面回源:注入 [sdi-header]=true header,AEM 才会输出含 ESI 占位符的页面。可在 Worker 回源前构造新 Request 注入,或配置 Cloudflare Transform Rule 统一添加。被边缘缓存的主页面必须是“占位符版本”,否则 Worker 在缓存命中时找不到任何 ESI 标签。
片段子请求:刻意不带该 header,仅携带标识性 UA。SDI 检测不到约定 header 时直接渲染组件真实内容,Worker 拿到的就是可内联的 HTML 片段;同时避免片段内部嵌套的动态组件被再次替换成占位符,防止边缘出现多层 ESI 递归解析的失控局面。
4.3 落地踩坑
1. referParam 冗余导致缓存碎片化。 原始实现中,referParam 携带完整页面路径,同一个头部片段被 N 个不同页面引用时会产生 N 个不同的片段 URL,缓存键爆炸。优化方案:对上下文无关的片段(如 Experience Fragment 头尾),将 referParam 截短为语言层级,同一语言版本下头部片段只维护一个缓存条目。
2. 上下文敏感组件不能截短。 “最新洞察”、“媒体中心”等组件依赖完整的 referParam 做上下文聚合,截短会导致内容错乱。最终方案是对这两类路径做白名单豁免,保留完整页面路径。
3. 多层容错兜底。 <esi:include onerror="continue"> 确保片段获取失败时跳过而非报错;Worker passThroughOnException 在自身异常时直接透传回源;required_header 机制保证极端情况下摘除 Cloudflare 层,站点仍由 Dispatcher + AEM 完整渲染兜底,功能零损失。三层兜底缺一不可。
五、案例:某国际律所全球站点的落地效果
以某国际律所全球官网为例。该站点是典型的 AEM 全球品牌站:中/英/日等多语言、内容涵盖洞察文章、律师团队、业务领域、媒体中心等多个高频更新板块,访问者遍布亚太、欧洲与美洲。
改造前的痛点:
l 页头导航、页脚、“相关洞察”等模块动态渲染,整页缓存命中率低;
l 海外用户访问源站(部署于单一区域)延迟高;
l 内容发布后缓存刷新颗粒度粗,全站刷新带来回源风暴。
改造后的效果:
| 维度 | 效果 |
| 主页面缓存 | 整页静态化,Cloudflare 边缘 + Dispatcher 双层缓存,全球就近命中 |
| 动态片段缓存 | 头部/页脚按语言维度全站共享一份边缘缓存,TTL 24h |
| 回源压力 | AEM Publish 渲染 QPS 下降一个数量级以上 |
| 内容发布 | 片段与主页面独立失效,导航更新秒级全球生效 |
| 全球访问 | 依托 Cloudflare 300+ 节点,欧美/亚太首屏 TTFB 均进入毫秒级 |
| 可用性 | Worker 异常透传 + onerror 容错 + 回退整页渲染,三重兜底 |
用户拿到的永远是一份完整、新鲜、极速的 HTML——页面主体作为静态内容被完整缓存,动态模块由边缘拼装,浏览器无感知。

(图片展示了对比效果图)
六、常见问题与排查
| 现象 | 可能原因 | 排查与修复 |
| View Source 里找不到 <!-- SDI include --> 注释 | Cloudflare 回源主页面时未注入约定 header;或“完整渲染版”污染了缓存 | 用 curl -H "[sdi-header]: true" <URL> 直连源站验证 SDI 是否生效;检查 header 注入配置;清空缓存后重试 |
| 页面某区块整块缺失 | 对应片段子请求失败,被 onerror="continue" 静默跳过 | 直接请求片段 URL(.nocache.html 路径)查看 HTTP 状态码;核对 SDI 配置的 resource-types 是否与组件实际 resourceType 一致 |
| 某区块永不更新 | 片段边缘缓存(24h TTL)未失效 | 在 Cloudflare 按 URL 精确 Purge 该片段缓存;重要发布流程中加入片段缓存刷新步骤 |
| 全站页面显示同一处内容(如 A 页面显示 B 页面的面包屑) | referParam 被错误截短,上下文敏感组件的缓存键串扰 | 检查 referParam 截短逻辑与白名单;上下文敏感组件必须保留完整页面路径 |
| 页面返回嵌套的 <esi:include> 标签原样暴露给用户 | 片段子请求错误携带了约定 header,片段内部组件被再次替换成占位符 | 确认片段请求不带该 header(仅带标识性 UA) |
通用排查三板斧:
① 检查 Publish 原始响应——add_comment 输出的 SDI 注释是第一手线索;
② 分段请求——分别请求主页面(带/不带 header 各一次)与片段 URL,对比三份响应即可定位问题层;
③ 看缓存头——通过 cf-cache-status 与 Dispatcher 缓存标记确认命中情况。
七、FAQ
Q1:SDI 和 SSI 有什么区别?为什么选 ESI?
SSI 由 Apache 在每次请求时回源拼装片段,片段没有独立缓存;ESI 由 CDN 边缘拼装,片段在边缘有自己的缓存生命周期,主页面与片段各自独立缓存、独立失效。对于全球品牌站,ESI 能利用 CDN 边缘节点就近拼装,SSI 做不到。
Q2:如果 Cloudflare 出故障,站点会怎样?
三层容错兜底:ESI onerror="continue" 确保片段失败时跳过;Worker passThroughOnException 在异常时直接透传回源;required_header 机制保证摘除 Cloudflare 层后,站点仍由 Dispatcher + AEM 完整渲染,功能零损失。
Q3:哪些组件应该走动态包含,哪些不应该?
只把“上下文敏感 / 高频更新”的组件交给 SDI(如导航、面包屑、相关洞察、相关律师),页面主体坚决静态化。SDI 不是越多越好——每个 ESI 标签都是一次边缘子请求,非必要不动态化。
Q4:片段缓存 24 小时,内容发布后如何即时生效?
发布流程中加入片段缓存刷新步骤:内容发布后通过 Cloudflare API 按 URL 精确 Purge 对应片段缓存。由于头部等共享片段全站只缓存一份,一次 Purge 即可全站生效。
Q5:这套方案能用在非 AEM 的 CMS 上吗?
SDI 是 Apache Sling 组件,与 AEM 深度绑定。但 ESI 是开放标准,任何能在 HTML 中输出 <esi:include> 标签的 CMS 都可以配合 Cloudflare Workers 使用,核心架构思路(边缘缓存 + 边缘拼装 + 握手开关)是通用的。
八、结语
AEM Dynamic Include + Cloudflare ESI 的组合,本质上是一次“渲染职责的重新分层”:AEM 专注内容生产与渲染,CDN 边缘专注缓存与拼装,浏览器拿到完整页面。它让“全球多语言品牌站”这个性能与灵活性难以兼得的命题,得到了一个工程上优雅、成本上可控的解法。
如需了解龙孚信息(Dragon Bravo Corporation)在 AEM 全球站点架构与性能优化方面的更多实践,请访问 www.dragonsoftbravo.com,或联系 sales-support@dragonsoftbravo.com / +86-21-61483130。
参考文献
[1] Google, “Find out how you stack up for new industry benchmarks for mobile web performance”, https://www.thinkwithgoogle.com
[2] W3C, “Edge Side Includes (ESI) Specification”, https://www.w3.org/TR/esi-lang/
[3] Apache Sling, “Sling Dynamic Include Documentation”, https://sling.apache.org/documentation/bundles/dynamic-include.html
[4] Cloudflare, “Workers Documentation — TransformStream”, https://developers.cloudflare.com/workers/
[5] Adobe, “AEM Dispatcher Caching Guide”, https://docs.adobe.com
关于龙孚信息 AEM 解决方案团队
龙孚信息 AEM 解决方案团队深耕 Adobe Experience Cloud 生态,专注于 Adobe Experience Manager(AEM)的企业级实施、性能优化与全球站点架构设计,服务过多家全球知名品牌与专业机构。
团队的核心能力包括:
AEM 全栈实施:站点架构设计、组件体系搭建、多语言/多站点(MSM)治理、Experience Fragment 体系规划;
性能与缓存体系:Dispatcher/CDN 多级缓存设计、Sling Dynamic Include 深度定制、Cloudflare Workers 边缘计算实践;
Adobe 生态集成:Adobe Analytics(AA)数据埋点与用户行为分析体系落地、Launch/Tags 标签管理、AEM 与 AA 联动的内容效果度量,让“极致性能”与“数据驱动”并行不悖;
全球化交付:多区域部署、跨境访问加速、内容合规与发布流程设计。
无论是从零构建全球品牌站点,还是对存量 AEM 平台进行性能治理与架构升级,龙孚信息 AEM 解决方案团队都能为客户提供从咨询规划、落地实施到长期运维的一站式服务,让“内容体验”与“极致性能”真正兼得。
想了解更多产品信息?
我们多年服务世界五百强,为客户提供最灵活的方案选择和一体化的实施部署

