如何在AMP文件中使用Service Worker缓存CDN文件及解决跨域问题
解决Service Worker缓存CDN跨域资源+AMP适配的完整方案
这个问题我之前帮不少开发者踩过坑,核心就是跨域资源的CORS配置+Service Worker请求模式的正确设置,再结合AMP的特殊要求,咱们一步步来解决:
一、先搞定CDN的CORS配置(最关键的前提)
Service Worker是在你的根域名下运行的脚本,请求CDN资源属于跨域操作,浏览器会严格校验CORS响应头。你必须在CDN的后台配置以下响应头:
Access-Control-Allow-Origin: https://your-root-domain.com(替换成你的实际根域名,别用*,虽然省事但不安全)Access-Control-Allow-Methods: GET, HEAD(静态资源只需要这两个请求方法)Access-Control-Allow-Headers: Cache-Control(确保缓存相关的请求头能正常传递)
不同CDN的配置位置不一样:比如Cloudflare在「规则→转换规则→修改响应头」里加;阿里云CDN在「域名管理→HTTP头设置」里添加。配置完后,可以用浏览器DevTools的Network面板查看CDN资源的响应头,确认这些头存在。
二、Service Worker(swAmp.js)的缓存逻辑
核心是在fetch事件中处理CDN资源时,必须用mode: 'cors'发起请求,同时实现缓存策略。下面是完整的示例代码,包含缓存预加载、请求拦截、缓存更新:
// 定义缓存名称和需要缓存的CDN资源列表 const CACHE_NAME = 'amp-cdn-cache-v1'; const CDN_ASSETS = [ 'https://your-cdn-domain.com/css/amp-style.css', 'https://your-cdn-domain.com/js/amp-script.js', 'https://your-cdn-domain.com/images/hero.jpg' ]; // 安装阶段:预缓存CDN资源 self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME) .then((cache) => { // 注意:请求CDN资源时必须加mode: 'cors' return Promise.all( CDN_ASSETS.map((url) => { return fetch(url, { mode: 'cors' }) .then((response) => { if (response.ok) { return cache.put(url, response); } throw new Error(`Failed to cache ${url}`); }); }) ); }) .then(() => self.skipWaiting()) // 强制激活新的Service Worker ); }); // 激活阶段:清理旧缓存 self.addEventListener('activate', (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ); }).then(() => self.clients.claim()) // 控制所有打开的页面 ); }); // fetch事件:拦截请求,优先返回缓存,缓存不存在则请求网络并更新缓存 self.addEventListener('fetch', (event) => { // 只处理CDN资源的请求(可以根据URL前缀判断) const isCDNAsset = CDN_ASSETS.some((url) => event.request.url.startsWith(new URL(url).origin)); if (isCDNAsset) { event.respondWith( caches.match(event.request) .then((cachedResponse) => { // 如果缓存有,先返回缓存,同时后台发起网络请求更新缓存 if (cachedResponse) { // 后台更新缓存,不阻塞当前响应 fetch(event.request, { mode: 'cors' }) .then((networkResponse) => { if (networkResponse.ok) { caches.open(CACHE_NAME).then((cache) => { cache.put(event.request, networkResponse); }); } }); return cachedResponse; } // 缓存不存在,发起网络请求,成功后存入缓存 return fetch(event.request, { mode: 'cors' }) .then((networkResponse) => { if (!networkResponse.ok) { return new Response('Resource not found', { status: 404 }); } // 克隆响应,因为响应只能被读取一次 const responseToCache = networkResponse.clone(); caches.open(CACHE_NAME).then((cache) => { cache.put(event.request, responseToCache); }); return networkResponse; }); }) ); } });
三、AMP页面中注册Service Worker
AMP不允许直接用原生JS注册Service Worker,必须使用官方的<amp-install-serviceworker>组件,具体代码如下:
<!doctype html> <html ⚡> <head> <meta charset="utf-8"> <title>AMP + Service Worker CDN Cache</title> <link rel="canonical" href="https://your-root-domain.com/amp-page.html"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> <script async src="https://cdn.ampproject.org/v0.js"></script> <!-- 引入AMP Service Worker组件 --> <script async custom-element="amp-install-serviceworker" src="https://cdn.ampproject.org/v0/amp-install-serviceworker-0.1.js"></script> </head> <body> <!-- 页面内容 --> <h1>AMP Page with CDN Caching via Service Worker</h1> <amp-img src="https://your-cdn-domain.com/images/hero.jpg" width="800" height="450" layout="responsive" alt="Hero Image"></amp-img> <!-- 注册Service Worker --> <amp-install-serviceworker src="/swAmp.js" data-iframe-src="/install-sw.html" layout="nodisplay"> </amp-install-serviceworker> </body> </html>
补充说明:install-sw.html的作用
AMP的amp-install-serviceworker需要一个iframe来完成注册(因为AMP页面的沙箱限制),这个文件内容很简单,只需要注册Service Worker即可:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Install Service Worker</title> </head> <body> <script> if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/swAmp.js') .then((registration) => { console.log('Service Worker registered:', registration.scope); }) .catch((error) => { console.error('Service Worker registration failed:', error); }); } </script> </body> </html>
四、缓存后如何提供文件?
在上面的Service Worker代码中,fetch事件的逻辑已经处理了:
- 当用户请求CDN资源时,先检查缓存中是否存在匹配的响应
- 如果有,直接返回缓存的响应,同时后台发起网络请求更新缓存(保证用户拿到最新资源)
- 如果缓存中没有,就发起跨域请求获取资源,成功后存入缓存,再返回给用户
常见坑点提醒
- 一定要确保CDN的CORS头配置正确,否则即使Service Worker加了
mode: 'cors'也会报错 - AMP页面必须使用
<amp-install-serviceworker>组件,不能用原生JS注册,否则会被AMP的校验拦截 - 缓存名称要加版本号(比如
amp-cdn-cache-v1),这样更新缓存时只需要修改版本号,Service Worker会自动清理旧缓存
内容的提问来源于stack exchange,提问作者Crio
相关产品推荐
相关产品推荐

