ข้ามไปยังเนื้อหา

Update Prompt UI

เมื่อตั้งค่า registerType เป็น 'prompt' นั้น vite-plugin-pwa จะไม่เรียก skipWaiting() โดยอัตโนมัติ แต่จะให้ hook useRegisterSW() จาก virtual module virtual:pwa-register/react แทน เพื่อให้คุณสร้าง update UI ของตัวเองได้ วิธีนี้ให้ผู้ใช้ควบคุมว่าจะให้ service worker ใหม่ activate และโหลดหน้าใหม่เมื่อใด

ดึง hook จาก virtual module ที่ vite-plugin-pwa เปิดเผยในช่วง build time:

import { useRegisterSW } from 'virtual:pwa-register/react';

hook นี้คืนค่าสามอย่าง:

  • needRefresh — tuple แบบ [boolean, setter] ค่า boolean จะเป็น true เมื่อ service worker ใหม่ติดตั้งแล้วและกำลังรอ activate
  • offlineReady — tuple แบบ [boolean, setter] ค่า boolean จะเป็น true เมื่อ asset ทั้งหมดของแอปถูก cache ครบแล้วและแอปสามารถทำงานแบบออฟไลน์ได้
  • updateServiceWorker(reloadPage?: boolean) — เรียกด้วย true เพื่อส่ง skipWaiting() ไปยัง service worker ที่กำลังรอและโหลดหน้าใหม่

component ด้านล่างอ่านค่าทั้งสามนั้นและ render toast banner เฉพาะเมื่อมีสิ่งที่ต้องแจ้งผู้ใช้

import { useRegisterSW } from 'virtual:pwa-register/react';
export function ReloadPrompt() {
const {
offlineReady: [offlineReady, setOfflineReady],
needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker,
} = useRegisterSW();
if (!offlineReady && !needRefresh) {
return null;
}
return (
<div
style={{
position: 'fixed',
bottom: '1rem',
right: '1rem',
padding: '1rem',
background: '#1e1e2e',
color: '#cdd6f4',
borderRadius: '0.5rem',
boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
zIndex: 9999,
}}
>
{offlineReady ? (
<p>App ready to work offline.</p>
) : (
<p>New content available, click Reload to update.</p>
)}
{needRefresh && (
<button onClick={() => updateServiceWorker(true)}>Reload</button>
)}
<button
onClick={() => {
setOfflineReady(false);
setNeedRefresh(false);
}}
>
Close
</button>
</div>
);
}

นำ ReloadPrompt ไป import ใน App.tsx ของคุณและ render ไว้ที่ส่วนล่างสุดของ component tree เพื่อให้ prompt ลอยอยู่เหนือเนื้อหาอื่นทั้งหมด:

import { ReloadPrompt } from './ReloadPrompt';
export default function App() {
return (
<>
{/* your app content */}
<ReloadPrompt />
</>
);
}

หาก TypeScript รายงานว่าไม่พบ module virtual:pwa-register/react ให้เพิ่ม triple-slash reference ไปยัง client types ของ vite-plugin-pwa ที่ด้านบนสุดของ vite-env.d.ts (หรือไฟล์ .d.ts ใดก็ได้ในโปรเจกต์):

/// <reference types="vite-plugin-pwa/client" />

วิธีนี้จะบอก TypeScript เกี่ยวกับ virtual module declaration ที่ vite-plugin-pwa มาพร้อม ซึ่งจะแก้ปัญหา import error โดยไม่ต้องติดตั้ง package เพิ่มเติม

ตัวเลือกBenefitCost
แสดง toast/banner ให้ผู้ใช้กด “Update now” เองผู้ใช้ควบคุม timing ไม่โดนตัดงานกลางคันผู้ใช้ที่ไม่สนใจ toast อาจติดเวอร์ชันเก่าไปนานมาก
Auto-reload ทันทีที่มี update (ไม่ถาม)ผู้ใช้อยู่กับเวอร์ชันล่าสุดตลอดเวลาโดยไม่ต้องทำอะไรเสี่ยง reload กลางที่ผู้ใช้กำลังกรอกฟอร์มหรือทำงานค้างอยู่
  • ไม่ debounce/dedupe การแสดง update prompt ทำให้ toast โผล่ซ้ำหลายอันถ้า service worker event ยิงมากกว่าหนึ่งครั้ง
  • ปิด prompt ทิ้งโดยไม่เก็บ state ทำให้ผู้ใช้กดปิดไปแล้วแอปไม่มีทางเตือนซ้ำอีกเลยแม้จะยังใช้เวอร์ชันเก่าอยู่
  • ไม่ผูกปุ่ม “Update” เข้ากับ updateServiceWorker() ของ plugin จริงๆ ทำให้กดแล้วไม่มีอะไรเกิดขึ้น

💡 ตัวอย่างจากของจริง

Vite PWA ecosystem (Elk) — ใช้ virtual:pwa-register hook แสดง toast “New content available, click to reload” เป็น pattern มาตรฐาน

Google Docs — แสดง banner แจ้งเตือนอัปเดตให้ผู้ใช้กดเองแทนที่จะ force reload ระหว่างที่กำลังแก้เอกสารอยู่

virtual module ใดที่ให้ hook useRegisterSW?
needRefresh[0] === true หมายความว่าอะไร?
ควรส่ง argument ใดไปยัง updateServiceWorker() เพื่อ activate SW และโหลดหน้าใหม่?
เหตุใด vite-plugin-pwa จึงต้องการ TypeScript shim สำหรับ virtual:pwa-register/react?