rel="manifest"仅声明pwa元数据并触发安装提示,不参与缓存或service worker注册;必须配合navigator.serviceworker.register()及正确配置start_url、display等字段,才能实现离线功能。

rel="manifest" 是 PWA 启动的前提,不是缓存逻辑本身
rel="manifest" 这个 <link> 属性只负责告诉浏览器:“这里有个 manifest.json,请读取它来定义应用身份和安装行为”。它不参与任何缓存决策、不触发 Service Worker 注册、也不决定资源是否离线可用。很多人误以为加了这行就等于“开了离线功能”,其实只是迈出了第一步。
常见错误现象包括:页面能被添加到主屏幕,但断网后白屏或 404;manifest.json 被浏览器成功加载(DevTools → Application → Manifest 可见),但 Network 标签里所有请求仍走网络——说明缓存机制根本没生效。
- 必须配合
navigator.serviceWorker.register()才能启用离线能力 -
manifest.json中的start_url必须是能被 Service Worker 拦截的路径(比如"start_url": "/",而不是"start_url": "index.html") - 路径必须绝对且可访问:如果
href="/manifest.json"返回 404 或 MIME 类型不是application/manifest+json,Chrome 会静默忽略,不报错也不提示
manifest.json 的 display 和 start_url 直接影响离线首屏行为
用户点击桌面图标启动 PWA 时,浏览器会发起对 start_url 的导航请求——这个请求会被已激活的 Service Worker 拦截。如果该 URL 没被预缓存,又没配置 fallback 逻辑,就会直接失败。
关键配置项及其影响:
-
display: "standalone"或"minimal-ui":让应用以独立窗口打开,避免地址栏干扰;但若start_url对应页面未缓存,用户看到的就是空白页或 network error -
start_url: "/"是最稳妥的选择,因为根路径通常对应index.html,也最容易在install阶段加入缓存列表 - 避免使用带查询参数的
start_url(如"start_url": "/?utm_source=pwa"),Service Worker 默认不会自动缓存带 query 的 URL,除非显式写进urlsToCache
Service Worker 激活前,manifest 的作用仅限于安装提示
从用户首次访问到真正获得离线能力,存在明确的时间差:rel="manifest" 让浏览器知道“可以安装”,但 Service Worker 的 install 和 activate 事件必须完成,缓存才就位。这个过程无法跳过,也不能靠刷新页面加速。
典型时间线:
- 首次访问:加载
manifest.json→ 浏览器识别为可安装应用(但尚未注册 SW) - 执行
navigator.serviceWorker.register('/sw.js')→ 触发install事件 → 开始缓存urlsToCache列表中的资源 - 缓存完成 →
activate事件触发 → 清理旧缓存 → 此时 fetch 请求才开始被拦截并返回缓存内容 - 第二次访问(或刷新)→ 用户才真正获得离线可用体验
所以,如果你改了 manifest.json 却发现离线没变化,大概率是因为新版本的 Service Worker 还没激活,或者缓存策略没更新。
HTTPS 环境下 rel="manifest" 才被浏览器信任
本地开发时 localhost 是豁免的,但一旦部署到真实域名,rel="manifest" 对应的 manifest.json 文件必须通过 HTTPS 提供,否则 Chrome/Safari 直接忽略该标签,连“添加到主屏幕”提示都不会出现。
验证方法很简单:
- 打开 DevTools → Application → Manifest,如果显示 “No manifest detected”,先检查 Network 标签里
manifest.json是否返回 200 - 用 curl 检查响应头:
curl -I https://yoursite.com/manifest.json,确认Content-Type: application/manifest+json - 确保服务器没有重定向到 HTTP 版本(比如
http://→https://的跳转会破坏 manifest 加载)
离线缓存真正起效的地方永远在 Service Worker 里,rel="manifest" 只是那个“敲门的人”——门开不开,取决于后面有没有人应答。











