نمط PRPL (PRPL Pattern)
PRPL — الدفع، العرض، التخزين المسبق، التحميل الكسول (Push, Render, Pre-cache, Lazy-load) — هي استراتيجية طورتها Google عام 2016 لجعل تطبيقات الويب قابلة للاستخدام على شبكات الهاتف المحمول المتقطعة والهواتف ضعيفة الإمكانات. كان هذا النمط سائدًا في التسليم ذو الأولوية الأولى من نحو 2016 حتى 2019؛ ولا تزال أعمدتها نصائح جيدة اليوم، لكن التنفيذات تطورت. زال دفع الخادم (server push) من Chrome، وامتزجت هياكل التطبيقات (app shells) إلى حد كبير مع SSR الحديث مع البث، وأصبحت المقاييس التي نقيسها LCP وINP بدلاً من تقديرات time-to-interactive.
يحافظ هذا المقال على تأطير الأعمدة الأربعة الأصلي لأنه لا يزال قائمة تحقق مفيدة، ثم يوضح شكل كل عمود اليوم.
مراجعة سريعة للأعمدة الأربعة
| العمود | نية عام 2016 | التنفيذ الحالي |
|---|---|---|
| الدفع | تسليم الموارد الحرجة إلى جانب المستند عبر دفع خادم HTTP/2 | ، ، و103 Early Hints عبر HTTP |
| العرض | جعل أول مسار تفاعلي في أقرب وقت ممكن | تحسين LCP وINP عبر SSR والبث وتقسيم الشيفرة على مستوى المسار |
| التخزين المسبق | استخدام عامل خدمة لملء ذاكرة المسارات المرجّحة التالية | ذاكرة Workbox وقت التشغيل، ``، والجلب المسبق على مستوى الإطار (Next/Remix/SvelteKit) |
| التحميل الكسول | تأجيل كل ما ليس ضروريًا لأول رسم | import() الديناميكي، التحميل الكسول الأصلي (loading="lazy")، تلميحات الأولوية (fetchpriority)، content-visibility |
لماذا وُجد PRPL في المقام الأول
تحميل صفحة ساذج هو سلسلة من رحلات الذهاب والإياب. يطلب المتصفح HTML، يحلله، يكتشف ورقة أنماط، يطلبها، يكتشف سكربتًا، يطلبه، يكتشف خطًا، وهكذا. في اتصال 4G بزمن استجابة دائري يزيد على 100ms، تكون كل خطوة ظاهرة للمستخدم. كان PRPL استجابة لذلك: افعل كل ما تستطيع لتقليل تلك الرحلات، ثم خزّن النتيجة بقوة بحيث تدفع الزيارة التالية تكلفة أقل.
اعتمد مقال PRPL الأصلي بقوة على دفع خادم HTTP/2 كحل لمشكلة رحلات الذهاب والإياب. لقد تغيرت هذه القصة.
الدفع: من دفع الخادم إلى Early Hints
عند نشر PRPL، كانت التوصية بدفع CSS الحرج وأجزاء JavaScript الأولية مع استجابة HTML كي لا يضطر المتصفح إلى اكتشافها وطلبها. كانت الآلية هي دفع خادم HTTP/2.
دفع الخادم مات عمليًا. عطّل Chrome دفع خادم HTTP/2 افتراضيًا في الإصدار 106 (أواخر 2022) بعد أن أظهرت سنوات من القياس أنه لم يكن ناجحًا؛ إذ استخدمه نحو 1.25% فقط من مواقع HTTP/2، وفي كثير من الحالات كررت الموارد المدفوعة بايتات كانت الذاكرة المؤقتة تحتوي عليها بالفعل. وتخلت مواصفة HTTP/3 من الدفع إلى حد كبير.
ما الذي حل محل ذلك:
`` يعلن جلبًا عالي الأولوية مباشرة في HTML. إنه الأداة الأساسية لإخبار المتصفح: «سوف تحتاج إلى هذا الخط أو السكربت أو الصورة؛ لا تنتظر حتى تحلل ما يكفي لاكتشافه».
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="modulepreload" href="/assets/app.js">
103 Early Hints عبر HTTP هو الوريث الحديث لدفع الخادم، وهو الحل الذي يعالج فعلاً مشكلة رحلات الذهاب والإياب التي كان PRPL يهتم بها. قبل أن ينتهي الخادم من إنشاء الاستجابة الكاملة، يمكنه إرسال استجابة مؤقتة 103 مع رؤوس Link تخبر المتصفح بما يبدأ في جلبه:
HTTP/1.1 103 Early Hints
Link: </assets/app.js>; rel=preload; as=script
Link: </assets/app.css>; rel=preload; as=style
HTTP/1.1 200 OK
Content-Type: text/html
...
على عكس دفع الخادم، تترك Early Hints المتصفح مسؤولًا: يمكنه تجاهل التلميح إذا كان المورد مخزنًا بالفعل، فلا تهدر بايتات. وهي مدعومة في Chrome وEdge وFirefox، وتتاح عبر Cloudflare وFastly وVercel.
تلميحات الأولوية (fetchpriority="high") و`` تكملان مجموعة الأدوات. استخدم preconnect مع مضيفات الأصول عبر النطاقات، واستخدم fetchpriority لدفع صورة LCP إلى مقدمة قائمة الانتظار.
العرض: تحسين LCP، لا «التفاعلية»
كان حرف «R» في PRPL يعني عرض المسار الأولي في أسرع وقت ممكن. النصيحة نفسها؛ لكن المقياس قد تغير.
الأرقام المهمة حاليًا هي Core Web Vitals: LCP (Largest Contentful Paint، الهدف ≤ 2.5s)، وINP (Interaction to Next Paint، الذي حل محل FID في مارس 2024، الهدف ≤ 200ms)، وCLS (Cumulative Layout Shift، الهدف ≤ 0.1). LCP هو المقياس الأكثر تأثرًا بعمود العرض.
أساليب ملموسة تحسّن LCP اليوم:
- اعرض المحتوى الموجود أعلى الطية من جانب الخادم بحيث يكون عنصر LCP في HTML الأولي. تفعل أطر مثل Next.js وRemix وSvelteKit وNuxt وAstro ذلك افتراضيًا.
- بث الاستجابة كي يبدأ المتصفح في تحليل الأصول واكتشافها قبل أن ينتهي الخادم من العرض. تجعل
renderToPipeableStreamفي React 18 و`` الحدود هذا التقدم تدريجيًا: تُشحن القشرة فورًا وتصل الأجزاء الأبطأ مع حلها. - ضمّن CSS الحرج الخاص بأنماط أعلى الطية كي لا يتأخر أول رسم بسبب رحلة ذهاب وإياب إلى ورقة أنماط.
- استضف الخطوط محليًا وحمّلها مسبقًا مع
crossorigin؛ فالخطوط على الويب سبب شائع للتراجع في LCP. - امنح صورة LCP أولوية
fetchpriority="high"وتجاوز التحميل الكسول لها.
كُتب مقال PRPL الأصلي قبل أن يصبح لـ«render» معايير نجاح موحدة. الارتباط بـ LCP/INP يجعل العمود قابلًا للاختبار؛ فيمكنك إثبات أن التغيير ساعد بدل التخمين.
التخزين المسبق: عامل الخدمة، الجلب المسبق، وحواف CDN
يستخدم العمود الثالث وقت الخمول للتحضير لطلب المستخدم التالي. هناك ثلاث طبقات يجدر استخدامها معًا:
التخزين المؤقت وقت التشغيل باستخدام عامل خدمة. Workbox هي المكتبة الفعلية: اختر استراتيجية لكل مسار (StaleWhileRevalidate لهيكل التطبيق، وCacheFirst مع انتهاء الصلاحية للصور، وNetworkFirst لاستجابات API)، ويولّد Workbox عامل الخدمة نيابةً عنك. هذه هي الطبقة التي توفر لك تجربة غير متصلة مفيدة.
import { registerRoute } from "workbox-routing";
import { StaleWhileRevalidate, CacheFirst } from "workbox-strategies";
import { ExpirationPlugin } from "workbox-expiration";
registerRoute(
({ request }) => request.destination === "script",
new StaleWhileRevalidate({ cacheName: "scripts" })
);
registerRoute(
({ request }) => request.destination === "image",
new CacheFirst({
cacheName: "images",
plugins: [new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 })],
})
);
الجلب المسبق للروابط على مستوى الإطار. جلب Next.js وRemix وSvelteKit وNuxt حزم JavaScript للروابط الموجودة في إطار العرض (أو عند التحويم) كي يبدو التنقل فوريًا. لا تحتاج عادةً إلى إعداد ذلك؛ فهو مفعّل افتراضيًا. إذا كنت تبني يدويًا، فإن `` هي الأولية منخفضة المستوى.
التخزين المؤقت على حافة CDN. الكثير مما كان يفعله إعداد هيكل التطبيق مع عامل الخدمة صار الآن مشمولًا عبر وضع ذاكرة مؤقتة على الحافة (Cloudflare أو Fastly أو Vercel أو Netlify) أمام المصدر وتخزين HTML الخاص بـ SSR طالما تسمح البيانات بذلك. أصبحت ISR (Next.js) والتخزين مع إعادة التحقق على الحافة هما البديلان الحديثان لـ«تسخين الصفحة التالية في الخلفية».
التحميل الكسول: الأوليات الأصلية تكفي عادةً
كان التحميل الكسول أكثر الأعمدة تطلبًا للعمل عام 2016؛ إذ كان يعني تقسيم الشيفرة حسب المسار، والاستيرادات الديناميكية اليدوية، وتحميل الصور بواسطة IntersectionObserver. معظم ذلك مدمج الآن في منصة الويب.
import()الديناميكي هو الطريقة الأصلية لتقسيم JavaScript وقت التشغيل. تحوّل أدوات التجميع كل استدعاءimport()تلقائيًا إلى جزء منفصل.** و**يؤجلان الوسائط خارج الشاشة من دون سطر واحد من JavaScript. ادمجهما معdecoding="async"للصور.content-visibility: autoيتيح للمتصفح تخطي التخطيط والرسم للأقسام خارج الشاشة، وهو مكسب كبير للصفحات الطويلة.- التقسيم على مستوى المسار هو الوضع الافتراضي في كل إطار عمل حديث؛ ونادرًا ما تحتاج إلى تهيئته.
المكان الوحيد الذي لا يزال التحميل الكسول يحتاج فيه إلى العناية هو عنصر LCP. لا تحمّل صورة الغلاف كسولًا أبدًا، ولا تقسم المكوّن الذي يحتويها إلى أجزاء أبدًا.
هل ما زال هيكل التطبيق موجودًا؟
اعتمد العرض الأصلي لـ PRPL على نموذج هيكل التطبيق: شحن قشرة HTML+JS دنيا أولًا، ثم عرض الهياكل العظمية، ثم ملء المحتوى. كان منطقيًا لتطبيقات SPA التي يعرضها العميل على هواتف بطيئة.
حاليًا، حُلّ هيكل التطبيق إلى حد كبير بواسطة:
- SSR مع البث (React 18 وSolid وAstro وQwik) — الخادم هو القشرة، وحواجز Suspense هي الهيكل العظمي.
- PWA مع Workbox لتجربة عدم الاتصال والتثبيت.
- العرض على الحافة كي تكون البايت الأول سريعًا بغض النظر عن مسافة المستخدم عن المصدر.
لا يزال بإمكانك بناء PWA بهيكل تطبيق، وللطبيقات الويب شديدة التفاعل (المحررات التعاونية ولوحات المعلومات) غالبًا ما يكون الخيار الصحيح، لكنه لم يعد الإجابة الافتراضية لـ«اجعل هذا التحميل سريعًا».
هل ما زال PRPL الإطار الصحيح؟
يمكن فهم PRPL على أفضل صورة كحزمة أفكار من عام 2016 لا تزال كل فكرة منها صحيحة منفردة. لكن الحزمة نفسها تبدو قديمة: زال دفع الخادم، واندمجت رواية هيكل التطبيق في SSR المتدفق، وانتقل ما نقيسه من «زمن حتى التفاعل» إلى LCP وINP.
لا تزال الأعمدة الأربعة قائمة تحقق مفيدة عند تدقيق صفحة:
- الدفع: هل تستخدم
preloadوmodulepreloadوEarly Hints للموارد التي تعرف أن المتصفح سيحتاجها؟ - العرض: هل عنصر LCP موجود في HTML الأولي، وهل LCP أقل من 2.5 ثانية عند المئين الـ75 من بيانات المستخدمين الحقيقيين؟
- التخزين المسبق: هل التنقل المرجح التالي مجلب مسبقًا؟ وهل لديك عامل خدمة للزيارات المتكررة؟
- التحميل الكسول: هل كل ما ليس ضروريًا لأول تفاعل مؤجل، وهل عنصر LCP ليس في هذه المجموعة؟
إذا أردت الصيغة الحديثة: ابنِ على إطار عمل حديث يوفر SSR متدفقًا، وضعه خلف CDN حافي، وفعّل Early Hints، واتبع Core Web Vitals، لتحصل على PRPL دون التفكير فيه بوصفه PRPL.
المراجع
- إزالة دفع خادم HTTP/2 من Chrome - Chrome Developers
- Early Hints - Chrome Developers
- التفاعل حتى الرسم التالي (INP) - web.dev
- Workbox - Chrome Developers
- نمط PRPL - web.dev (مؤرشف)