بناء PWA مع Next.js وNext-PWA

مقدمة: بناء PWA مع Next.js وNext-PWA

تقنية التطبيقات الويب التقدمية (PWA) تُمكن التطبيقات من العمل “كَأنّها تطبيقات مضمّنة”، عبر ملف manifest وعامل خدمة (Service Worker) يُوفّر تخزينًا مؤقّتًا للموارد ودعمًا دون اتصال. يلتقط عامل الخدمة طلبات الشبكة ويُعيد ملفات مُخزّنة سابقًا، مما يجعل التطبيق سريعًا وموثوقًا حتى في حالة انقطاع الإنترنت. يسهّل إطار العمل Next.js تحويل التطبيقات إلى PWAs بفضل مكتبة next-pwa القائمة على Workbox، التي تتولى التوليد التلقائي لعامل الخدمة وتكوين استراتيجيات التخزين المؤقت لتحسين الأداء وتجربة المستخدم.

تثبيت وتكوين next-pwa

لبدء استخدام next-pwa، ثبّت الحزمة عبر npm أو yarn وأضفها إلى إعدادات Next.js في next.config.js. مثلاً:

// next.config.js
const withPWA = require('next-pwa')({
  dest: 'public',                              // أين يُنشر عامل الخدمة والملفات المؤقتة
  disable: process.env.NODE_ENV === 'development', // تعطيل PWA في بيئة التطوير
  register: true,                              // تسجيل عامل الخدمة تلقائيًا
  skipWaiting: true,                           // تفعيل التحديث الفوري للعامل
  // خيارات إضافية مثل scope، reloadOnOnline، publicExcludes، إلخ.
});

module.exports = withPWA({
  // إعدادات Next.js العادية
});

في هذا المثال، dest: 'public' تحدد مجلّد النشر (sw.js وملفات Workbox سيتم توليدها فيه). كما نرى، نُفّعّل التسجيل التلقائي والتحديث الفوري للعامل (register: true, skipWaiting: true). يُنصح أيضًا بإغلاق العمل بعامل الخدمة في التطوير لتجنب مشكلات التخزين المؤقت أثناء Hot Reload.

توليد وتكوين Service Worker تلقائيًا

مكتبة next-pwa تقوم بإنشاء وتسجيل ملف service worker تلقائيًا عند بناء التطبيق (build). يستخدم المنهج المفترض Workbox تحت الغطاء، مما يعني أنه يضع ملفات sw.js و workbox-*.js في مجلّد public بعد الأمر next build. هذا العامل المنشأ سيتولّى مهام التخزين المؤقّت المعلّنة في الإعدادات.

يمكن ضبط خيارات Workbox المدمجة بسهولة عبر خاصية workboxOptions في التكوين:

const withPWA = require('next-pwa')({
  dest: 'public',
  workboxOptions: {
    // تخصيص سلوك Workbox (كالملء المسبق precaching)
    // مثل تغيير اسم ملف السجل (sw)، أو إضافة إضافات Workbox أخرى.
  },
});

بهذه الطريقة، يُمكنك تخصيص سلوك عامل الخدمة من ناحية الاستراتيجيات والتخزين المؤقت ضمن واجهات Workbox المألوفة. على سبيل المثال، يمكنك تفعيل خيار reloadOnOnline: true لإعادة تحميل التطبيق عند استعادة الاتصال بالإنترنت أو تعيين نطاق scope إذا كان التطبيق مُضمّنًا في مسار فرعي.

دعم العمل دون اتصال والتخزين المؤقت (Offline & Caching)

next-pwa تأتي مع دعم جاهز لحالات عدم الاتصال: فهي تخزّن مسبقًا (precache) ملفات الصفحات والموارد المهمة، وتعتمد استراتيجيات تلقائية لجلبها عند الطلب. بشكل افتراضي، يتم تخزين ملفات HTML وطلبات RSC (رد الخادم) بشكل منفصل، ويمكن تعديل ذلك عبر workboxOptions.runtimeCaching. على سبيل المثال، يُنصح باستخدام NetworkFirst لطلبات بيانات الـ API الحيوية بحيث يُحاول العامل جلب أحدث البيانات من الشبكة أولاً، وفFallback إلى الكاش في حالة الفشل. أما الموارد الثابتة (صور، خطوط، CSS)، فيمكن تطبيق استراتيجية Stale-While-Revalidate لضمان استجابة سريعة باستخدام النسخة المخزّنة وتحديثها في الخلفية. المثال التالي يبيّن كيف يمكن تعريف هذه الاستراتيجيات في next.config.js:

const withPWA = require('next-pwa')({
  dest: 'public',
  runtimeCaching: [
    {
      urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'images',
        expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 },
      },
    },
    {
      urlPattern: /^https:\/\/api\.example\.com\/.*$/,
      handler: 'NetworkFirst',
      options: {
        cacheName: 'api-cache',
      },
    },
  ],
  fallbacks: {
    document: '/_offline',       // صفحة بديلة عند عدم الاتصال ولم يتم تخزين الصفحة المطلوبة
    image: '/static/offline.png' // صورة بديلة عند خطأ في تحميل الصور
  },
});
module.exports = withPWA({ /* إعدادات Next العادية */ });

هنا نستخدم Stale-While-Revalidate للصور بحيث نُعيد فورًا الصورة المخزّنة إذا كانت موجودة، ثم نحدّثها لاحقًا في الخلفية، بينما نستخدم NetworkFirst لطلبات API (للبيانات الحيّة). خيارات fallbacks تسمح بتحديد صفحات أو موارد احتياطية تُستخدم عند فشل الشبكة تمامًا، مثل صفحة /_offline المسبقة التخزين.

استراتيجيات التخزين المؤقت (Caching Strategies)

  • CacheFirst (Cache Falling Back to Network): يمد التطبيق بالنسخة المخزنة إن وجدت، وإلا يُحمّل من الشبكة ثم يخزّنها. مفيد للموارد التي نثق بثباتها كواجهات التطبيقات والأيقونات.

  • NetworkFirst: يحاول جلب المورد من الشبكة أولًا ثم يُحدّث الكاش، وإن تعذّر ذلك (عند انقطاع الإنترنت) يعود للنسخة المخزّنة. مفيد للمحتوى المتغير مثل بيانات API أو صفحات SSR حساسة للبيانات الحديثة.

  • Stale-While-Revalidate: يُرجع فورًا النسخة المخزّنة (إن وجدت) مع طلب الشبكة موازٍ للتحديث. يحافظ على سرعة الاستجابة مع تحديث الكاش في الخلفية. مثالي للصور والخطوط التي تُستخدم بشكل متكرر.

كل استراتيجية تُنظّم كيف يُجيب عامل الخدمة على طلبات الشبكة: فمثلاً Stale-While-Revalidate تستجيب بالنسخة المخزّنة إذا كانت متاحة وتُحدّثها في الخلفية، بينما NetworkFirst تجلب البيانات من الشبكة إن أمكن وتخزنها، أو تستعمل الكاش عند الفشل. يقدّم Workbox (المُعتمد في next-pwa) تنفيذًا جاهزًا لهذه الاستراتيجيات عبر handler المحدد في إعدادات runtimeCaching.

استراتيجيات التصدير الثابت (next export) وتجاوز القيود

يمكن أيضًا تحويل تطبيق Next.js إلى إصدار ثابت عبر next export مع الحفاظ على وظائف PWA أساسية، لكن هناك قيود: فبعد التصدير ينتج مجلد out/ ستستضيفه صفحة ثابتة دون خادم Node. في هذه الحالة، يظل بإمكان next-pwa توليد ملفات sw.js وWorkbox في مجلّد الإخراج، لكن يجب الانتباه إلى حجم الأصول التي سيتم تخزينها مسبقًا. الحلول: يمكن استخدام Workbox مباشرة من خلال ملف workbox-config.js مخصص (كمبّولر خارجي) لتحديد بالضبط أي ملفات تُخزّن، أو تفعيل خصائص مثل fallbacks لصفحة بديلة (على سبيل المثال /_offline.html) ليُعوض عن أي صفحة ديناميكية عند انقطاع الاتصال.

كما أن حلولًا خارجية كـ netlify-plugin-pwa أو أدوات Workbox CLI قد تساعد في بناء PWA عند التصدير الثابت. مثال على تجاوز: استخدم Workbox في سير العمل لإضافة sw.js إلى مجلد out/ والتأكد من نسخ كل الأيقونات وmanifest.json ضمن هذا المخرَج.

مزايا الأداء وتجربة المستخدم

باستخدام next-pwa، يتحسّن أداء التطبيق بشكل ملحوظ وتزداد نقاط تقييمه في أدوات مثل Google Lighthouse. فالتخزين المؤقت الذكي يقلل زمن التحميل المتكرر ويتيح عرض محتوى الواجهة دون انتظار الشبكة. كما تساعد خاصية التحميل المسبق للمحتوى (Prefetch) والتسجيل التلقائي على جعل التطبيق قابلاً للتثبيت (installable) وسريع الاستجابة. من الناحية العملية، يوفر PWA للمستخدمين امتيازات تطبيقات الهواتف (أيقونة على الشاشة الرئيسية، التحديث التلقائي للتطبيق)، ويضمن سلاسة الاستخدام عند ضعف الاتصال أو انعدامه.

التحديات الشائعة وكيفية تجاوزها

  • التسجيل في التطوير: إذا لم تُعطل next-pwa في وضع التطوير، قد تظهر مشاكل (مثل إعادة تحميل لا نهائية) بسبب الكاش القديم. الحل: تأكد من ضبط disable: process.env.NODE_ENV === 'development' وإلغاء تسجيل عامل الخدمة (unregister) قبل تطوير التطبيق.

  • حجم التخزين المبالغ فيه: كالتعليق على GitHub، التخزين المفرط لكل الموارد قد يتسبب في امتلاء الكاش (خصوصًا فيديوهات من مواقع خارجية). الحل: خصص استراتيجيات التخزين ولا تدمج كل الروابط الخارجية، أو استخدم publicExcludes لاستبعاد ملفات كبيرة من الـ precache.

  • التصدير الثابت (Static Export): كما ذُكر، قد تواجه أخطاء إذا حاول Workbox جلب ملفات غير موجودة (مثل middleware-manifest.json) بعد التصدير. لتجاوز ذلك، يمكن تعديل مسارات العامل (scope) أو استخدام بديل يدوي. مثلاً، إنشاء صفحة /_offline بديلة تضمن أنها مخزنة مسبقًا ليتم عرضها في حالة أخطاء الشبكة.

  • الاندماج مع App Router: النسخة الرسمية من next-pwa قد لا تدعم بشكل كامل /app (App Router) في Next.js 13+. هناك نسخة مفرعة تحت صيانة DuCanhGH تدعم الميزات الحديثة وتحديثات Workbox. يُستحسن استخدام هذه النسخة أو التأكد من توافقها مع بنية المشروع الحالي.

  • التعامل مع SSR: إذا كان التطبيق يستخدم SSR، يحتاج التخزين المؤقت للصفحات إلى استراتيجية مختلفة، إذ إن صفحات SSR لا تُخزّن مسبقًا بالضرورة. إحدى الحلول الذكية هي استخدام استراتيجية NetworkFirst للصفحات، مع وجود صفحة Offline fallback لتغطية الحالات التي يفشل فيها جلب الصفحة من الشبكة.

الدمج الذكي بين SSR وPWA

عند الجمع بين SSR وPWA، يعتمد النهج الأمثل على احتياجات التطبيق. مثلاً، لصفحات SSR يمكن تفعيل caching من جانب السيرفر (Next.js ISR أو التحميل المسبق) ثم تطبيق NetworkFirst عبر العامل لضمان جلب النسخة الأحدث وتعويض الخسارة بالنسخة المخزّنة عند الفشل. كما يمكن تصميم صفحة خاصة لعرضها دون اتصال (مثلاً /_offline.jsx أو app/~offline/page.tsx) بحيث تُخزن تلقائيًا وتُستدعى كبديل عند انعدام الشبكة. بهذه الطريقة، يصبح التطبيق قادرًا على عرض واجهة معقولة للمستخدم حتى لو كان السيرفر معطلًا أو العميل دون اتصال، دون التضحية بفوائد SSR والتحديثات في الخلفية.

توصيات عملية: في النهاية، احرص على اختبار تطبيق PWA في أوضاع مختلفة (مثل انقطاع الإنترنت، البيئات المتعدّدة)، وراقب حجم الكاش بانتظام. استخدم أدوات تقييم الأداء (مثل Lighthouse) للتأكد من تحقيق التحسينات المتوقعة. بفضل next-pwa، تتحوّل تطبيقات Next.js إلى PWAs قويّة بجهد برمجي بسيط نسبيًا، مع إمكانيات تعديل متقدمة عبر Workbox لتحقيق الأداء الأمثل وتجربة مستخدم سلسة.

المصادر: الوثائق الرسمية لـNext.js وnext-pwa، بالإضافة إلى مصادر تعليمية حول Workbox واستراتيجيات التخزين المؤقت.

حول المحتوى:

تقنية التطبيقات الويب التقدمية (PWA) تُمكن التطبيقات من العمل “كَأنّها تطبيقات مضمّنة”، عبر ملف manifest وعامل خدمة (Service Worker) يُوفّر تخزينًا مؤقّتًا للموارد ودعمًا دون اتصال. يلتقط عامل الخدمة طلبات الشبكة ويُعيد ملفات مُخزّنة سابقًا، مما يجعل التطبيق سريعًا وموثوقًا حتى في حالة انقطاع الإنترنت. يسهّل إطار العمل Next.js تحويل التطبيقات إلى PWAs بفضل مكتبة next-pwa القائمة على Workbox، التي تتولى التوليد التلقائي لعامل الخدمة وتكوين استراتيجيات التخزين المؤقت لتحسين الأداء وتجربة المستخدم.