Content Indexing API 是元数据注册器而非索引器,仅向浏览器 UI 暴露可点击条目;它不读/验/触发缓存,add() 调用成功仅表示“挂名”,离线加载依赖 Service Worker 的 fetch 事件命中缓存 HTML 响应。

Content Indexing API 本身不支持异步索引原始 DOM 页面,它也不处理“原始 DOM”——它只注册已缓存的、完整响应的 URL(通常是 HTML 文档),且必须满足严格前提。所谓“异步索引”是常见误解:API 的 add() 调用是同步 JavaScript 方法,是否生效完全取决于底层缓存是否就绪、路径是否匹配、状态码是否为 200。
真正起作用的是缓存先行 + 索引声明的配合逻辑,而非 API 自身具备异步能力。
✅ 正确理解 Content Indexing API 的定位
- 它不是索引器,而是元数据注册器:仅向浏览器 UI(如 Chrome 的「离线页面」列表)暴露一条可点击条目
- 它不读缓存、不验缓存、不触发缓存;调用
index.add()成功 ≠ 页面已缓存,只是“挂了个名” - 浏览器点击该条目时,会发起标准导航请求(
mode: 'navigate'),能否离线加载,全靠 Service Worker 的fetch事件能否命中并返回缓存的 HTML 响应
✅ 确保目标页面是“离线可用的原始 DOM 页面”
原始 DOM 页面指服务端返回的、未被 JS 动态渲染的完整 HTML 文档(即 SSR 或静态生成页)。要让它离线可用,需:
- 在 Service Worker 中明确缓存 HTML 响应(不能只缓存 JSON 或 JS)
- 缓存响应的
status必须为200(重定向、404、500 均不可索引) - 响应头需含
content-type: text/html - 缓存路径(如
/article/123/)必须与index.add({ url: '...' })中的url完全一致(含尾部斜杠、大小写、查询参数)
示例缓存操作:
caches.open('pages-v1').then(cache =>
cache.addAll([
'/article/123/',
'/offline.html',
'/'
])
);✅ 在合适时机注册索引(非 install 阶段)
install 阶段缓存可能尚未完成,fetch 中动态缓存又难保证顺序。推荐在 activate 后检查缓存并注册:
self.addEventListener('activate', event => {
event.waitUntil((async () => {
const cache = await caches.open('pages-v1');
const response = await cache.match('/article/123/');
if (response && response.status === 200) {
const reg = await navigator.serviceWorker.getRegistration();
if (reg.index) {
await reg.index.add({
id: 'article-123',
url: '/article/123/',
title: '标题已缓存',
description: '原始 HTML 页面',
icons: [{ src: '/icon.png', sizes: '192x192', type: 'image/png' }]
});
}
}
})());
});✅ 让点击索引条目真正加载离线 DOM
用户点击列表项后,浏览器发起导航请求。Service Worker 必须拦截并返回缓存 HTML:
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
}
});关键点:
- 必须监听
mode === 'navigate',不是destination: 'document'(旧写法已不推荐) -
caches.match()要能精确匹配到你缓存的 URL(注意路径规范) - 不建议 fallback 到网络,否则离线时会失败;若需优雅降级,应预置
/offline.html并确保它也在缓存中
不复杂但容易忽略


















