راهاندازی کامل PWA در Next.js: از Service Worker تا Push Notification و Background Sync

اپلیکیشنهای وب پیشرو (PWA) مرز بین «سایت» و «اپلیکیشن نصبشده» رو کمرنگ میکنن. توی Next.js، رسیدن به یه PWA واقعاً آمادهی production یعنی چهارتا بخش با هم درست کار کنن: یه manifest، یه Service Worker با استراتژی کش هدفمند، Push Notification، و Background Sync برای لحظههایی که کاربر آفلاینه. اینجا میبینیم این چهارتا چطور کنار هم جفتوجور میشن.
۱. Manifest — قابلنصب کردن اپ
فایل manifest.json به مرورگر (و پرامپت نصب سیستمعامل) میگه اپ چه اسمی داره، چه شکلیه، و بعد از نصب چطور رفتار میکنه.
// public/manifest.json
{
"name": "Ali Rahimi — Frontend Developer",
"short_name": "Ali Rahimi",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#4338CA",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}توی root layout بهش لینک بده، همراه با theme-color:
// src/app/layout.tsx
export const metadata = {
manifest: "/manifest.json",
themeColor: "#4338CA",
};۲. Service Worker — موتور اصلی ماجرا
Service Worker یه اسکریپته که توی یه ترد جدا اجرا میشه، درخواستهای شبکه رو قطع میکنه، و تصمیم میگیره باهاشون چیکار کنه. یهبار، بعد از hydrate شدن صفحه، ثبتش کن:
// src/lib/register-sw.ts
if (typeof window !== "undefined" && "serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js").catch(console.error);
});
}۳. استراتژیهای کش — جایی که اکثراً اشتباه میکنن
همهی درخواستها نباید یهجور کش بشن. انتخاب استراتژی درست برای هر نوع درخواست، فرقیه بین اپی که حس آنی داره و اپی که دادهی بیات تحویل میده.
استراتژی | رفتار | مناسب برای |
|---|---|---|
Cache First | اول از کش سرو کن؛ فقط موقع miss شدن کش برو سراغ شبکه | فونتها، آیکونها، فایلهای استاتیک نسخهدار |
Network First | اول شبکه؛ اگه fail شد برگرد سراغ کش | صفحات HTML، دادهی APIای که مدام تغییر میکنه |
Stale-While-Revalidate | نسخهی کششده رو فوری سرو کن، پشتصحنه کش رو آپدیت کن | آواتارها، پاسخهای API غیرحیاتی |
Network Only | هیچوقت کش نکن — همیشه برو سراغ شبکه | پرداخت، endpoint های احرازهویت، هرچی حساسه |
Cache Only | هیچوقت سراغ شبکه نرو — کش باید از قبل پر شده باشه | اسکلت اپ بعد از نصب |
یه Service Worker ساده که دوتا از اینها رو پیاده میکنه:
// public/sw.js
const STATIC_CACHE = "static-v1";
const APP_SHELL = ["/", "/offline", "/icons/icon-192.png"];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(STATIC_CACHE).then((cache) => cache.addAll(APP_SHELL))
);
self.skipWaiting();
});
self.addEventListener("fetch", (event) => {
const { request } = event;
// Cache-first برای فایلهای استاتیک
if (request.destination === "image" || request.destination === "font") {
event.respondWith(
caches.match(request).then((cached) => cached || fetch(request))
);
return;
}
// Network-first برای navigation (صفحات HTML)، با fallback به صفحهی آفلاین
if (request.mode === "navigate") {
event.respondWith(
fetch(request).catch(() => caches.match("/offline"))
);
}
});توی عمل، بهجای دستساز نوشتنش، از next-pwa یا Serwist استفاده کن — اینها از روی یه config یه Service Worker تنظیمشده میسازن و edge caseهایی (مثل cache-busting بر اساس hash بیلد) رو خودشون مدیریت میکنن. فهمیدن استراتژیهای بالا دقیقاً همون چیزیه که کمک میکنه درست پیکربندیشون کنی.
۴. Push Notification
سه بخش متحرک داره: یه subscription که توی مرورگر ساخته میشه، کلیدهای VAPID برای احراز هویت سرورت، و یه push handler توی Service Worker.
// کلاینت: اجازه بگیر و subscribe کن
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!),
});
// subscription رو بفرست سمت سرور تا برای این کاربر ذخیره بشه
await fetch("/api/push/subscribe", {
method: "POST",
body: JSON.stringify(subscription),
});// public/sw.js — گرفتن رویداد push
self.addEventListener("push", (event) => {
const data = event.data?.json() ?? {};
event.waitUntil(
self.registration.showNotification(data.title ?? "بهروزرسانی جدید", {
body: data.body,
icon: "/icons/icon-192.png",
data: { url: data.url ?? "/" },
})
);
});
self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});سمت سرور، با کتابخونهای مثل web-push و جفتکلید VAPID خودت، پیام واقعی رو به subscription ذخیرهشده میفرستی — همون چیزی که رویداد push بالا رو trigger میکنه.
۵. Background Sync — دووم آوردن جلوی اتصال ناپایدار
Background Sync بهت اجازه میده یه درخواست failشده (مثل ارسال فرم موقع آفلاین بودن) رو به تعویق بندازی، و مرورگر خودش دوباره امتحانش میکنه بهمحض برگشتن اتصال — حتی اگه کاربر تب رو بسته باشه.
// کلاینت: موقع submit، اگه آفلاین بودیم بهجای fail کردن یه sync tag ثبت کن
async function submitMessage(data: FormData) {
try {
await fetch("/api/contact", { method: "POST", body: data });
} catch {
const registration = await navigator.serviceWorker.ready;
await saveToIndexedDB(data); // اول توی صف محلی ذخیرهش کن
await registration.sync.register("sync-contact-form");
}
}// public/sw.js
self.addEventListener("sync", (event) => {
if (event.tag === "sync-contact-form") {
event.waitUntil(flushQueuedFormsFromIndexedDB());
}
});Background Sync (نوع one-off بالا) تا زمان نوشتن این متن فقط روی مرورگرهای Chromium کار میکنه — باهاش مثل یه بهبود تدریجی رفتار کن، نه تنها استراتژی آفلاینت. کاربرهای Safari/Firefox رتری خودکار نمیگیرن؛ مطمئن شو مسیر رتری دستی هم براشون کار میکنه.
چکلیست
manifest.json لینک و validate شده (تست installability توی Lighthouse پاس میشه)
Service Worker بعد از hydrate ثبت شده، نه قبلش که جلوی اولین رندر رو بگیره
استراتژی کش هدفمند برای هر نوع درخواست — نه یه قانون کلی برای همه
کلیدهای VAPID ساخته شده، subscription سمت سرور ذخیره شده، push handler روی یه دستگاه واقعی تست شده
Background sync بهعنوان بهبود تدریجی، با یه مسیر رتری دستی برای مرورگرهای پشتیبانینشده
این چهارتا رو درست انجام بده، فرقش فوری معلومه: بارگذاری آنیِ بازدیدهای بعدی، نوتیفیکیشنهایی که کاربر رو برمیگردونن، و فرمهایی که با یه اتصال ناپایدار داده از دست نمیدن.
مطالب بیشتر

احرازهویت با better-auth در Next.js: یه راهاندازی دستاول
راهاندازی دقیق پشت لاگین ادمین همین سایت: کانفیگ سرور، route handler catch-all در Next.js، کلاینت، و محافظت دولایهای middleware + layout که واقعاً /admin رو امن میکنه.

Recharts در Next.js و Tailwind: تمدهی و استایلدهی سفارشی
طبیعیکردن Recharts با سیستم طراحی Tailwind: مرز درست Client Component، وصلکردن رنگها به CSS variable برای dark mode خودکار، tooltip و legend سفارشی، و مدیریت لوکیلهای RTL.