如何在用户交互时离线缓存NextJS PWA的路由树或指定路由?
NextJS PWA 批量预缓存与交互式缓存实现方案
一、构建时批量预缓存路由与媒体资源
针对上百个路由及媒体文件的预缓存需求,可通过脚本生成预缓存清单+自定义Workbox配置实现,无需用户逐个访问触发缓存。
1. 自动生成全路由预缓存清单
编写Node脚本遍历pages目录,生成所有路由的预缓存条目,再导入到NextJS PWA配置中:
// get-routes.js const fs = require('fs'); const path = require('path'); const pagesDir = path.join(__dirname, 'pages'); const routes = []; function traverseDir(dir) { const files = fs.readdirSync(dir); files.forEach(file => { const fullPath = path.join(dir, file); const stats = fs.statSync(fullPath); if (stats.isDirectory()) traverseDir(fullPath); else if (file.match(/\.(js|tsx|jsx)$/)) { let route = fullPath.replace(pagesDir, '').replace(/\.(js|tsx|jsx)$/, ''); route = route === '/index' ? '/' : route; routes.push({ url: route, revision: Date.now().toString() }); } }); } traverseDir(pagesDir); // 可额外遍历public目录下的媒体文件,添加到routes数组 fs.writeFileSync('./routes-manifest.json', JSON.stringify(routes, null, 2));
在next.config.js中引入该清单:
const withPWA = require('next-pwa')({ dest: 'public', register: true, skipWaiting: true, additionalManifestEntries: require('./routes-manifest.json'), }); module.exports = withPWA({ // 你的NextJS基础配置 });
2. 自定义Workbox缓存策略
如果需要更精细的媒体资源缓存控制,可通过customWorkerDir自定义Service Worker:
// next.config.js const withPWA = require('next-pwa')({ dest: 'public', register: true, skipWaiting: true, customWorkerDir: 'src/workers', });
在自定义SW中配置媒体缓存与预缓存:
// src/workers/sw.js import { precacheAndRoute } from 'workbox-precaching'; import { registerRoute } from 'workbox-routing'; import { CacheFirst } from 'workbox-strategies'; // 加载预缓存清单 const precacheManifest = require('../../routes-manifest.json'); precacheAndRoute(precacheManifest); // 媒体资源缓存策略(图片/音频/视频) registerRoute( ({ request }) => ['image', 'audio', 'video'].includes(request.destination), new CacheFirst({ cacheName: 'media-cache', plugins: [{ cacheableResponse: { statuses: [0, 200] } }], }) );
二、交互式离线缓存设置
实现用户可主动触发的批量缓存功能,通过客户端与Service Worker的通信完成缓存控制。
1. 前端触发批量缓存组件
创建客户端组件,让用户手动触发指定资源的缓存,并展示进度:
'use client'; import { useEffect, useState } from 'react'; const CacheControl = () => { const [isCaching, setIsCaching] = useState(false); const [cacheProgress, setCacheProgress] = useState(0); const [failedUrls, setFailedUrls] = useState([]); // 监听Service Worker消息 useEffect(() => { if (!('serviceWorker' in navigator)) return; navigator.serviceWorker.ready.then(reg => { reg.active.addEventListener('message', (event) => { switch (event.data.type) { case 'PROGRESS': setCacheProgress(event.data.value); break; case 'ERROR': setFailedUrls(prev => [...prev, event.data.url]); break; case 'COMPLETE': setIsCaching(false); alert(`缓存完成,失败资源:${failedUrls.join(', ')}`); break; } }); }); }, [failedUrls]); const triggerBatchCache = async () => { setIsCaching(true); setFailedUrls([]); setCacheProgress(0); // 可从API/配置文件动态获取需要缓存的资源列表 const resources = ['/blog/post1', '/blog/post2', '/assets/cover.jpg']; if ('serviceWorker' in navigator) { const reg = await navigator.serviceWorker.ready; reg.active.postMessage({ type: 'START_CACHE', resources }); } }; return ( <div className="cache-control"> <button onClick={triggerBatchCache} disabled={isCaching}> {isCaching ? `缓存中... ${cacheProgress}%` : '离线缓存全部内容'} </button> {failedUrls.length > 0 && ( <div className="mt-4"> <h4>缓存失败资源:</h4> <ul> {failedUrls.map((url, idx) => <li key={idx}>{url}</li>)} </ul> </div> )} </div> ); }; export default CacheControl;
2. Service Worker处理缓存请求
在自定义SW中添加消息监听,处理前端发送的缓存指令:
// src/workers/sw.js // ... 原有预缓存配置 self.addEventListener('message', async (event) => { if (event.data.type === 'START_CACHE') { const { resources } = event.data; const cache = await caches.open('user-initiated-cache'); let completed = 0; for (const url of resources) { try { await cache.add(url); completed++; event.source.postMessage({ type: 'PROGRESS', value: Math.round((completed / resources.length) * 100) }); } catch (err) { event.source.postMessage({ type: 'ERROR', url }); } } event.source.postMessage({ type: 'COMPLETE' }); } });
注意事项
- 批量缓存大量资源时,建议添加WiFi环境检测提示,避免消耗用户移动流量;
- 预缓存资源需设置
revision字段,确保资源更新时能替换旧缓存; - 动态路由的SSR页面预缓存需确保渲染内容可被静态缓存,或采用
StaleWhileRevalidate策略兼顾新鲜度与离线可用性。
内容的提问来源于stack exchange,提问作者Queenvictoria
相关产品推荐
相关产品推荐

