mcpField
N
nextjs-pwa

nextjs-pwa

github.com/just-mpm开发65 次安装
代码仓库

关于 nextjs-pwa

面向 Next.js 开发者的渐进式 Web 应用实现指南,帮助选择原生支持或 Serwist。内容说明两种方案的配置、能力、限制及迁移要点,涵盖安装能力、离线使用和缓存。

主要功能

  1. 01比较原生支持与 Serwist
  2. 02配置 Web 应用清单
  3. 03生成并注册 Service Worker
  4. 04使用缓存与离线策略
  5. 05了解 Serwist 版本变更

适用场景

  1. 01为 Next.js 应用添加安装能力
  2. 02添加离线缓存和备用页面
  3. 03为 Pages Router 项目选择实现方案

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/just-mpm/conformai --skill nextjs-pwa

安装后在 Claude Code 中输入/nextjs-pwa确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
implementation-approaches.md
native-nextjs-implementation.md
serwist-implementation.md

详细介绍

Next.js PWA 实现指南(2025)

使用截至 2025 年最新的方法,通过 Next.js 实现渐进式 Web 应用的综合指南。

快速入门决策树

按照此决策树选择合适的实现方式:

Need PWA with Next.js?
│
├─ Basic installability only?
│  └─ Use: Native Next.js PWA Support
│     ✓ Zero dependencies
│     ✓ Built-in App Router support
│     ✓ Simple manifest.ts/json
│     ✗ No offline caching
│
└─ Need offline functionality?
   └─ Use: Serwist Package
      ✓ Advanced caching strategies
      ✓ Service worker management
      ✓ Background sync
      ✓ Push notifications
      ⚠ Requires configuration

方案 1:Next.js 原生 PWA 支持

概述

使用 Next.js 内置功能实现零依赖 PWA。

适用场景
  • ✅ 使用 App Router(/app 目录)
  • ✅ 需要基本的 PWA 功能(可安装性、清单)
  • ✅ 不需要复杂的离线缓存
  • ✅ 希望不使用任何外部依赖
  • ✅ 遵循 Next.js 官方建议
主要功能
  • 自 2024 年秋季起内置支持(已发布官方 PWA 指南)
  • 无需外部包
  • 通过 manifest.ts、manifest.json 或 manifest.webmanifest 生成清单
  • 与 App Router 无缝协作
  • 支持 TypeScript 的 MetadataRoute.Manifest 类型
局限性
  • 不自动支持离线
  • 不会生成服务工作线程
  • 不支持高级缓存策略
  • 离线功能需要手动实现服务工作线程
快速实现

1. 创建清单(app/manifest.ts):

import type { MetadataRoute } from 'next'

export default function manifest(): MetadataRoute.Manifest {
  return {
    name: 'Your App Name',
    short_name: 'App',
    description: 'Your app description',
    start_url: '/',
    display: 'standalone',
    background_color: '#ffffff',
    theme_color: '#000000',
    icons: [
      {
        src: '/icon-192.png',
        sizes: '192x192',
        type: 'image/png',
      },
      {
        src: '/icon-512.png',
        sizes: '512x512',
        type: 'image/png',
      },
    ],
  }
}

2. 添加元标签(app/layout.tsx):

export const metadata = {
  manifest: '/manifest.webmanifest',
  appleWebApp: {
    capable: true,
    statusBarStyle: 'default',
    title: 'Your App Name',
  },
}

3. 使用 HTTPS 部署(生产环境中的 PWA 必需)


方案 2:Serwist 包

概述

高级 PWA 实现,支持离线功能、缓存策略和服务工作线程管理。

适用场景
  • ✅ 需要离线功能
  • ✅ 需要高级缓存策略
  • ✅ 需要后台同步
  • ✅ 需要推送通知
  • ✅ 需要复杂的服务工作线程逻辑
  • ✅ 需要精细控制缓存
版本信息(2025 年 11 月更新)
  • Serwist 最新稳定版: 9.2.1+
  • 预览版本: 10.0.0-preview(开发中)
  • 重大变更: v9.0.0 引入重大 API 变更(2024 年 3 月)
  • 所需 Node.js 版本: 18.0.0+(建议使用 22.x)
  • TypeScript: 5.0.0+
主要功能
  • 自动生成服务工作线程
  • 预缓存静态资源
  • 运行时缓存策略(CacheFirst、NetworkFirst、StaleWhileRevalidate)
  • 后台同步
  • 支持推送通知
  • 离线备用页面
  • 支持 TypeScript
Turbopack 兼容性(2025 年 11 月更新)

重要更新:

✅ 生产构建(next build):完全兼容——可正常与 Turbopack 配合使用 ⚠️ 开发环境(next dev --turbo):会显示警告,但功能完整

开发环境警告的解决方法:

# Option 1: Suppress warning (Recommended for dev)
# Add to .env.local
SERWIST_SUPPRESS_TURBOPACK_WARNING="1"

# Option 2: Use webpack in development
npm run dev -- --webpack

注意:生产构建不需要 --webpack 标志。Serwist 在生产环境中可原生配合 Turbopack 使用。

安装
npm i @serwist/next && npm i -D serwist
# or
yarn add @serwist/next && yarn add -D serwist
# or
pnpm add @serwist/next && pnpm add -D serwist
快速实现

1. 配置 next.config.js:

import withSerwistInit from '@serwist/next'

const withSerwist = withSerwistInit({
  swSrc: 'app/sw.ts',
  swDest: 'public/sw.js',
  cacheOnNavigation: true,
  reloadOnOnline: true,
  disable: process.env.NODE_ENV === 'development', // Optional
})

export default withSerwist({
  // Your Next.js config
})

2. 创建服务工作线程(app/sw.ts):

import { Serwist } from 'serwist'

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  skipWaiting: true,
  clientsClaim: true,
  navigationPreload: true,
  runtimeCaching: [
    {
      urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
      handler: 'CacheFirst',
      options: {
        cacheName: 'google-fonts',
        expiration: {
          maxEntries: 4,
          maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year
        },
      },
    },
    {
      urlPattern: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'static-font-assets',
        expiration: {
          maxEntries: 4,
          maxAgeSeconds: 7 * 24 * 60 * 60, // 1 week
        },
      },
    },
    {
      urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'static-image-assets',
        expiration: {
          maxEntries: 64,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
      },
    },
    {
      urlPattern: /\/_next\/image\?url=.+$/i,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'next-image',
        expiration: {
          maxEntries: 64,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
      },
    },
    {
      urlPattern: /\.(?:js)$/i,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'static-js-assets',
        expiration: {
          maxEntries: 32,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
      },
    },
    {
      urlPattern: /\.(?:css|less)$/i,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'static-style-assets',
        expiration: {
          maxEntries: 32,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
      },
    },
    {
      urlPattern: /\/_next\/data\/.+\/.+\.json$/i,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'next-data',
        expiration: {
          maxEntries: 32,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
      },
    },
    {
      urlPattern: /\/api\/.*/i,
      handler: 'NetworkFirst',
      method: 'GET',
      options: {
        cacheName: 'apis',
        expiration: {
          maxEntries: 16,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
        networkTimeoutSeconds: 10,
      },
    },
    {
      urlPattern: /.*/i,
      handler: 'NetworkFirst',
      options: {
        cacheName: 'others',
        expiration: {
          maxEntries: 32,
          maxAgeSeconds: 24 * 60 * 60, // 1 day
        },
        networkTimeoutSeconds: 10,
      },
    },
  ],
})

serwist.addEventListeners()

3. 注册服务工作线程(app/layout.tsx):

'use client'

import { useEffect } from 'react'

export default function RootLayout({ children }) {
  useEffect(() => {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker
        .register('/sw.js')
        .then((registration) => {
          console.log('Service Worker registered:', registration)
        })
        .catch((error) => {
          console.error('Service Worker registration failed:', error)
        })
    }
  }, [])

  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

4. 更新 tsconfig.json:

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext", "webworker"],
    "types": ["@serwist/next/typings"]
  }
}

5. 创建清单(与原生方案相同)


重大变更:Serwist v9.0.0+

如果从较旧版本迁移,请注意以下重要变更:

1. 导入变更
// ❌ OLD (v8.x and earlier)
import { installSerwist } from '@serwist/sw'

// ✅ NEW (v9.0.0+)
import { Serwist } from 'serwist'
2. 初始化变更
// ❌ OLD
const sw = installSerwist({ /* config */ })

// ✅ NEW
const serwist = new Serwist({ /* config */ })
serwist.addEventListeners() // Required!
3. 包整合
# ❌ OLD
npm i @serwist/precaching @serwist/routing @serwist/strategies

# ✅ NEW (all in one)
npm i -D serwist

对比:原生方案与 Serwist

功能Next.js 原生支持Serwist
设置复杂度⭐ 简单⭐⭐⭐ 中等
依赖项✅ 无⚠️ 2 个包
离线支持❌ 手动实现✅ 自动
缓存策略❌ 无✅ 多种
服务工作线程❌ 手动实现✅ 自动生成
App Router 支持✅ 原生✅ 完整
Pages Router 支持⚠️ 有限✅ 完整
TypeScript✅ 内置✅ 完整
可用于生产环境✅ 是✅ 是
兼容 Turbopack✅ 是✅ 是(需设置环境变量)

常见问题及解决方法

问题 1:服务工作线程未更新

**解决方法:**为服务工作线程添加版本号,或使用 skipWaiting: true

问题 2:缓存内容未更新

解决方案: 实施适当的缓存失效策略,或缩短 maxAgeSeconds

问题 3:开发环境中的 Turbopack 警告

解决方案: 将 SERWIST_SUPPRESS_TURBOPACK_WARNING="1" 添加到 .env.local

问题 4:需要 HTTPS 错误

解决方案: 部署到 HTTPS 域名(测试时可使用 localhost)

问题 5:图标未显示

解决方案: 确保图标位于 public/ 目录中,并且清单路径正确


建议

对于新项目:
  1. 从 Next.js 原生 PWA 开始(简单、零依赖)
  2. 仅在需要离线功能时升级到 Serwist
对于现有项目:
  1. 从 next-pwa 迁移? → 使用 Serwist(可直接替换)
  2. 已经有自定义 service worker? → 保留它,或重构为 Serwist
  3. 使用 Pages Router? → 推荐使用 Serwist(支持更好)
对于生产环境:
  1. ✅ 始终使用 HTTPS
  2. ✅ 彻底测试离线功能
  3. ✅ 实施适当的错误处理
  4. ✅ 监控 service worker 更新
  5. ✅ 为不同资源使用合适的缓存策略

资源


版本历史

  • 2025 年 11 月: 更新 Serwist 版本信息,澄清 Turbopack 兼容性
  • 2024 年秋季: 正式记录 Next.js 原生 PWA 支持
  • 2024 年 3 月: 发布 Serwist v9.0.0,包含不兼容变更
  • 2023 年: 创建 Serwist,作为 Workbox 的现代替代方案

请参阅以下文件,获取完整且最新的信息:

C:\Users\tetu_.claude\skills\nextjs-pwa\references\serwist-implementation.md C:\Users\tetu_.claude\skills\nextjs-pwa\references\native-nextjs-implementation.md C:\Users\tetu_.claude\skills\nextjs-pwa\references\implementation-approaches.md

最后更新: 2025 年 11 月 13 日

评论

支持 Markdown