عرض المسارات مسبقًا باستخدام ميزة "تفاعل الانطباق"

هل لا يتم العرض من جهة الخادم ولكنك لا تزال تريد تسريع أداء موقع React الإلكتروني؟ جرِّب العرض المسبق!

react-snap هي مكتبة تابعة لجهة خارجية تعرض صفحات موقعك الإلكتروني مسبقًا في ملفات HTML ثابتة. يمكن أن يؤدي ذلك إلى تحسين مرات First Paint في تطبيقك.

إليك مقارنة للتطبيق نفسه بالعرض المُسبَق الذي تم تحميله من خلال محاكاة لاتصال شبكة الجيل الثالث وعلى جهاز جوّال بدونه:

مقارنة لتحميل الصفحات جنبًا إلى جنب يتم تحميل الإصدار الذي يستخدم ميزة العرض المسبق بسرعة أكبر بمقدار 4.2 ثانية.

لماذا يُعدّ هذا الردّ مفيدًا؟

تكمن مشكلة الأداء الرئيسية في التطبيقات ذات الصفحة الواحدة الكبيرة في أنّ المستخدم يحتاج إلى انتظار انتهاء تنزيل حِزم JavaScript التي تشكل الموقع الإلكتروني قبل أن يتمكّن من رؤية أي محتوى حقيقي. كلما كانت الحزم أكبر، طالت مدة انتظار المستخدم.

ولحل هذه المشكلة، يتّبع العديد من المطورين أسلوب عرض التطبيق على الخادم بدلاً من تشغيله على المتصفح فقط. مع كل عملية انتقال للصفحة/المسار، يتم إنشاء تنسيق HTML الكامل على الخادم وإرساله إلى المتصفح، ما يقلل من أوقات عرض أول بايت، إلا أنّه على حساب بطء في استهلاك أول بايت.

يُعد العرض المسبق أسلوبًا منفصلاً أقل تعقيدًا من العرض على الخادم، ولكنه يوفّر أيضًا طريقة لتحسين أوقات عرض أول محتوى في تطبيقك. يتم استخدام متصفح بلا واجهة مستخدم رسومية، أو متصفح بدون واجهة مستخدم، لإنشاء ملفات HTML ثابتة لكل مسار خلال وقت الإنشاء. ويمكن بعد ذلك شحن هذه الملفات مع حِزم JavaScript اللازمة للتطبيق.

لقطة تفاعلية

يستخدم react-snap برنامج Puppeteer لإنشاء ملفات HTML معروضة مسبقًا لمسارات مختلفة في تطبيقك. للبدء، قم بتثبيته كتبعية للتطوير:

npm install --save-dev react-snap

بعد ذلك، أضِف نصًا برمجيًا من النوع postbuild في package.json:

"scripts": {
  //...
  "postbuild": "react-snap"
}

سيؤدي هذا إلى تشغيل الأمر react-snap تلقائيًا في كل مرة يتم فيها إنشاء إصدار جديد من التطبيقات (npm build).

آخر شيء سيكون عليك فعله هو تغيير طريقة تشغيل التطبيق. اضبط الملف src/index.js على الملف التالي:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
const rootElement = document.getElementById("root");

if (rootElement.hasChildNodes()) {
  ReactDOM.hydrate(<App />, rootElement);
} else {
  ReactDOM.render(<App />, rootElement);
}

وبدلاً من استخدام ReactDOM.render فقط لعرض عنصر React الجذر مباشرةً في DOM، يتم التحقّق مما إذا كانت هناك أي عُقد فرعية متوفرة حاليًا، وذلك لتحديد ما إذا كان قد تم عرض محتوى HTML مسبقًا (أو عرضه على الخادم). في هذه الحالة، يتم استخدام ReactDOM.hydrate بدلاً من ذلك لإرفاق مستمعي الأحداث بمحتوى HTML الذي تم إنشاؤه، بدلاً من إنشائه من جديد.

سيؤدي إنشاء التطبيق الآن إلى إنشاء ملفات HTML ثابتة كحمولات لكل مسار يتم الزحف إليه. يمكنك إلقاء نظرة على شكل حمولة HTML بالنقر على عنوان URL لطلب HTML ثم النقر على علامة التبويب معاينات ضمن "أدوات مطوري البرامج في Chrome".

مقارنة قبل وبعد. تُظهر اللقطة اللاحقة أنّه تم عرض المحتوى.

فلاش من المحتوى غير التقليدي

على الرغم من أن HTML الثابت يتم عرضه الآن على الفور تقريبًا، إلا أنه لا يزال بدون نمط افتراضيًا وهو ما قد يتسبب في مشكلة عرض "وميض من المحتوى غير المصمم" (FOUC). يمكن ملاحظة ذلك بشكل خاص إذا كنت تستخدم مكتبة CSS-in-JS لإنشاء أدوات اختيار لأن حزمة JavaScript يجب أن تنتهي من التنفيذ قبل أن يتم تطبيق أي أنماط.

للمساعدة في منع حدوث ذلك، يمكن تضمين محتوى CSS الحرج أو الحد الأدنى من محتوى CSS المطلوب لعرض الصفحة الأولية مباشرةً في <head> من مستند HTML. يستخدم react-snap مكتبة أخرى تابعة لجهة خارجية، وهي minimalcss، لاستخراج أي محتوى CSS مهم لمسارات مختلفة. يمكنك تفعيل هذه الميزة من خلال تحديد ما يلي في ملف package.json:

"reactSnap": {
  "inlineCss": true
}

بعد إلقاء نظرة على معاينة الردّ في "أدوات مطوري البرامج في Chrome"، سيتم الآن عرض الصفحة ذات التصميم الذي تتضمّن لغة CSS المهمة.

مقارنة قبل وبعد. تُظهر اللقطة اللاحقة أنّه تم عرض المحتوى وتصميمه بسبب تضمين صفحات الأنماط المتتالية (CSS) المهمة.

الخلاصة

إذا لم تكن تعرض مسارات من جهة الخادم في تطبيقك، استخدِم react-snap لعرض محتوى HTML الثابت مسبقًا للمستخدمين.

  1. قم بتثبيته كتبعية للتطوير والبدء بالإعدادات الافتراضية فقط.
  2. استخدِم الخيار inlineCss التجريبي لتضمين محتوى CSS المهمّ إذا كان مناسبًا لموقعك الإلكتروني.
  3. وإذا كنت تستخدم تقسيم الرموز على مستوى مكون ضمن أي مسارات، احرص على عدم العرض المسبق لحالة التحميل للمستخدمين. يتناول react-snap README هذا الموضوع بمزيد من التفصيل.