You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在用户交互时离线缓存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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 02:20:39