feat(pwa): wire up @ducanh2912/next-pwa + register SW in layout
PWA install flow was wired but unusable: pwaUtil.install() calls
deferred.prompt(), but Chrome only fires beforeinstallprompt when a
valid Service Worker is registered + a manifest is linked. The project
had both intent (manifest, usePwaInstall hook) but no live SW —
public/sw.js was a stale workbox build artifact from a previous
attempt with hardcoded chunk URLs that no longer matched any build.
What this commit does:
1. Add @ducanh2912/next-pwa@10.2.9 as devDependency.
2. Wrap nextConfig with withPWA({...}):
- dest: "public" SW outputs to public/sw.js
- disable: true skip SW generation in dev (HMR-friendly)
- register: false don't auto-inject registration
- workboxOptions.skipWaiting: true + clientsClaim: true — new SW
takes over immediately
3. Add --webpack flag to dev/build/dev:proxy scripts.
@ducanh2912/next-pwa is webpack-only; Turbopack (Next 16 default)
doesn't load webpack plugins. Per user decision to accept the perf
trade-off in exchange for a working PWA.
4. Delete stale public/sw.js (workbox will regenerate it at build
time with the correct chunk URLs).
5. Add src/app/_components/core/sw-register.tsx: a no-UI client
component that registers /sw.js in production. Mounted at the end
of <body> in layout.tsx. disable: true means dev mode skips
registration (no /sw.js to fetch anyway).
After this commit + pnpm build, public/sw.js is regenerated with
correct chunk URLs, registered on first prod load, and Chrome fires
beforeinstallprompt — usePwaInstall + pwaUtil.install() then trigger
the native install prompt end-to-end.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
/**
|
||||
* Service Worker 注册器
|
||||
*
|
||||
* 背景:`@ducanh2912/next-pwa` 配置 `register: false`,不自动注入注册脚本。
|
||||
* App Router 下手动注册更可控 —— 不污染 SSR / hydration,可显式做生产 / 开发分流。
|
||||
*
|
||||
* 挂载位置:layout body 末尾,全站生效。
|
||||
*
|
||||
* 行为:
|
||||
* - dev (`NODE_ENV === "development"`):跳过注册
|
||||
* 1. next-pwa `disable: true` 时 dev 不生成 sw.js,注册会 404
|
||||
* 2. dev HMR + 老 SW cache 互相干扰,开发体验差
|
||||
* - 生产:调 `navigator.serviceWorker.register("/sw.js", { scope: "/" })`
|
||||
* - `scope: "/"` 与 manifest.scope 一致
|
||||
* - `updateViaCache: "none"` 强制每次都拉新版 SW(next-pwa 默认就是这行为,显式更清晰)
|
||||
*/
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function SwRegister() {
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
if (!("serviceWorker" in navigator)) return;
|
||||
if (process.env.NODE_ENV !== "production") return;
|
||||
|
||||
void navigator.serviceWorker.register("/sw.js", {
|
||||
scope: "/",
|
||||
updateViaCache: "none",
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 无 UI —— 纯副作用组件
|
||||
return null;
|
||||
}
|
||||
+6
-1
@@ -3,6 +3,7 @@ import { SessionProvider } from "@/providers/session-provider";
|
||||
import { geistSans, geistMono, athelas } from "@/core/fonts";
|
||||
import "./globals.css";
|
||||
|
||||
import { SwRegister } from "@/app/_components/core/sw-register";
|
||||
import { RootProviders } from "@/providers/root-providers";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -49,7 +50,11 @@ export default function RootLayout({
|
||||
<body className="min-h-full flex flex-col">
|
||||
{/* NextAuth SessionProvider:为所有客户端组件提供 useSession() 上下文 */}
|
||||
<SessionProvider>
|
||||
<RootProviders>{children}</RootProviders>
|
||||
<RootProviders>
|
||||
{children}
|
||||
{/* PWA Service Worker 注册器 —— 生产构建挂载 /sw.js(@ducanh2912/next-pwa 生成) */}
|
||||
<SwRegister />
|
||||
</RootProviders>
|
||||
</SessionProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user