# 为 Web 框架添加 PWA 支持

> 为 Next.js、Nuxt、SvelteKit 或 Angular 添加 manifest 和 Service Worker，检测注册支持，并保留联网回退路径。

本指南为 Next.js、Nuxt、SvelteKit 或 Angular 项目添加 Web App Manifest 与 Service Worker，各框架保留自己的构建集成，浏览器端产物则使用相同的 Manifest 和 Service Worker API。

MDN 把 Service Worker API 标为广泛可用。注册要求安全上下文，浏览器把 `localhost` 视为本地开发所需的安全上下文，因此示例会在无法注册或注册失败时让应用继续通过网络使用。

## 示例

请先选择负责应用构建的框架小节，再使用最后一个小节，在浏览器无法注册时保留联网路径。

### 在 Next.js App Router 中添加 manifest 路由

Next.js 通过 `app/manifest.ts` 提供带类型的 manifest 路由。其 PWA 指南在 `lib/service-worker.js` 创建自定义 Service Worker，并从客户端组件使用 `new URL("../lib/service-worker.js", import.meta.url)` 注册。

用应用实际使用的字段创建 manifest，并把引用的图标文件放入 `public/`。

```ts
// app/manifest.ts
import type { MetadataRoute } from "next";

export default function manifest(): MetadataRoute.Manifest {
  return {
    name: "Field Notes",
    short_name: "Notes",
    start_url: "/",
    display: "standalone",
    background_color: "#ffffff",
    theme_color: "#12372a",
    icons: [
      { src: "/icon-192.png", sizes: "192x192", type: "image/png" },
      { src: "/icon-512.png", sizes: "512x512", type: "image/png" },
    ],
  };
}
```

添加 `lib/service-worker.js`，再使用下方 Next.js 注册示例进行能力检测和失败处理。

### 配置 Nuxt 模块

Nuxt 模块目录为 Nuxt 3 提供 `@vite-pwa/nuxt`，添加模块后，把 manifest 数据放在 `pwa` 下。

运行 `npx nuxi@latest module add @vite-pwa/nuxt` 安装，然后在 `nuxt.config.ts` 中保存配置。

```ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@vite-pwa/nuxt"],
  pwa: {
    registerType: "autoUpdate",
    manifest: {
      name: "Field Notes",
      short_name: "Notes",
      start_url: "/",
      display: "standalone",
      theme_color: "#12372a",
      icons: [
        { src: "/icon-192.png", sizes: "192x192", type: "image/png" },
        { src: "/icon-512.png", sizes: "512x512", type: "image/png" },
      ],
    },
  },
});
```

该模块依据此配置生成 Service Worker 并注入 Web App Manifest。

### 配置 SvelteKit 插件

Vite PWA 的 SvelteKit 集成提供 `SvelteKitPWA`。其文档在 `vite.config.ts` 中配置插件，并通过 `virtual:pwa-info` 暴露 manifest 标记。

把 `@vite-pwa/sveltekit` 安装为开发依赖，再把插件放在 `sveltekit()` 之后。

```ts
// vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite";
import { SvelteKitPWA } from "@vite-pwa/sveltekit";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    sveltekit(),
    SvelteKitPWA({
      manifest: {
        name: "Field Notes",
        short_name: "Notes",
        start_url: "/",
        display: "standalone",
        theme_color: "#12372a",
        icons: [
          { src: "/icon-192.png", sizes: "192x192", type: "image/png" },
          { src: "/icon-512.png", sizes: "512x512", type: "image/png" },
        ],
      },
    }),
  ],
});
```

按照插件文档，把生成的 manifest 链接加入根布局。

```svelte
<!-- src/routes/+layout.svelte -->
<script>
  import { pwaInfo } from "virtual:pwa-info";
  $: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : "";
</script>

<svelte:head>
  {@html webManifestLink}
</svelte:head>
```

虚拟模块没有 manifest 信息时，空字符串回退让布局保持有效。

### 运行 Angular schematic

Angular 的 `@angular/pwa` schematic 会添加 Service Worker 包、构建支持、注册代码、manifest 链接、图标以及 `ngsw-config.json`。

在项目目录应用 schematic，再创建生产构建。

```bash
ng add @angular/pwa
ng build
```

检查生成的 `manifest.webmanifest`、图标与 `ngsw-config.json`，不要把起始值直接当成应用元数据；Angular 指南在本地演示 Service Worker 时使用生产配置。

### 注册 Next.js worker 并保留联网路径

框架插件自动生成构建产物，但浏览器仍通过 `navigator.serviceWorker` 暴露 Service Worker 能力。下面的 Next.js 客户端组件采用框架指南中的 worker URL。

不支持或注册失败时，普通网络导航保持可用。

把组件保存为 `app/service-worker-registration.tsx`，与上文指定的 `lib/service-worker.js` 配合使用，再从布局或页面渲染 `<ServiceWorkerRegistration />`。

```tsx
// app/service-worker-registration.tsx
"use client";

import { useEffect, useState } from "react";

export default function ServiceWorkerRegistration() {
  const [status, setStatus] = useState("正在检查 Service Worker 支持…");

  useEffect(() => {
    if (!("serviceWorker" in navigator)) {
      setStatus("Service Worker 注册不可用；应用仍可联网使用。");
      return;
    }

    let cancelled = false;

    navigator.serviceWorker
      .register(
        new URL("../lib/service-worker.js", import.meta.url),
        { scope: "/", updateViaCache: "none" },
      )
      .then(() => {
        if (!cancelled) setStatus("Service Worker 注册已就绪。");
      })
      .catch((error) => {
        if (!cancelled) {
          setStatus("Service Worker 注册失败；应用仍可联网使用。");
        }
        console.error(error);
      });

    return () => {
      cancelled = true;
    };
  }, []);

  return <p role="status">{status}</p>;
}
```

Next.js 自定义 worker 使用上述直接注册。Nuxt 与 SvelteKit 插件及 Angular schematic 会提供自己的注册路径；在它们初始化的界面中保留相同的支持与错误状态。

:::caution
[Angular Service Worker 指南](https://angular.dev/ecosystem/service-workers/getting-started)记载，其 worker 可以在更新可用时继续提供已安装的应用版本。请测试第二次部署并选择更新体验，不要假定刷新后会立即得到最新文件。
:::

## 另请参阅

- [入门](/zh/guides/getting-started/)
- [离线支持](/zh/guides/offline/)
- [更新策略](/zh/guides/update-strategies/)
- [How to build a Progressive Web Application with Next.js](https://nextjs.org/docs/app/guides/progressive-web-apps)（nextjs.org）
- [Getting started with Angular service workers](https://angular.dev/ecosystem/service-workers/getting-started)（angular.dev）