עיבוד מראש של מסלולים באמצעות תגובה מהירה

אתם לא משתמשים ב-server-side rendering אבל עדיין רוצים לשפר את הביצועים של אתר React? כדאי לנסות רינדור מראש.

react-snap היא ספרייה של צד שלישי שמבצעת עיבוד מראש של דפים באתר לקובצי HTML סטטיים. כך תוכלו לשפר את זמני הצביעה הראשונית באפליקציה.

הנה השוואה בין אותה אפליקציה עם עיבוד מראש ועם עיבוד מראש לא נטען, בחיבור 3G מדומה ובמכשיר נייד:

השוואה בין טעינה רגילה לטעינה בשיטה חלופית. הגרסה עם עיבוד מראש נטענת מהר יותר ב-4.2 שניות.

למה זה מועיל?

בעיית הביצועים העיקרית באפליקציות גדולות עם דף יחיד היא שהמשתמש צריך להמתין להשלמת ההורדה של חבילות ה-JavaScript שמרכיבות את האתר, לפני שהוא יכול לראות תוכן אמיתי. ככל שהחבילות גדולות יותר, כך המשתמש יצטרך להמתין יותר זמן.

כדי לפתור את הבעיה הזו, מפתחים רבים משתמשים בגישה של עיבוד (רינדרינג) של האפליקציה בשרת במקום רק להפעיל אותה בדפדפן. בכל מעבר לדף או למסלול, ה-HTML המלא נוצר בשרת ונשלח לדפדפן. הפעולה הזו מקצרת את זמני הצגת התמונה הראשונה, אבל מגדילה את זמן ה-First Byte.

עיבוד מראש הוא טכניקה נפרדת שהיא פחות מורכבת מעיצוב בצד השרת, אבל היא גם מספקת דרך לשפר את זמני הצגת התמונה הראשונית באפליקציה. דפדפן ללא ראש, או דפדפן ללא ממשק משתמש, משמש ליצירת קובצי HTML סטטיים של כל מסלול במהלך זמן ה-build. לאחר מכן אפשר לשלוח את הקבצים האלה יחד עם חבילות ה-JavaScript שנדרשות לאפליקציה.

react-snap

react-snap משתמש ב-Puppeteer כדי ליצור קובצי HTML שעברו רינדור מראש של מסלולים שונים באפליקציה. כדי להתחיל, מתקינים אותו כחבילת תלות לפיתוח:

npm install --save-dev react-snap

לאחר מכן מוסיפים סקריפט postbuild ב-package.json:

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

הפקודה react-snap תופעל באופן אוטומטי בכל פעם שייווצר build חדש של האפליקציות (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 שכבר נוצר, במקום ליצור אותו מחדש.

ה-build של האפליקציה יוצר עכשיו קובצי HTML סטטיים כמטען שימושי לכל מסלול שנסרק. כדי לראות איך נראה עומס העבודה של ה-HTML, לוחצים על כתובת ה-URL של הבקשה ל-HTML ואז על הכרטיסייה Previews ב-Chrome DevTools.

השוואה בין המצב לפני ואחרי. בצילום שלאחר מכן רואים שהתוכן עבר רינדור.

הבהוב של תוכן ללא עיצוב

עכשיו HTML סטטי עובר עיבוד כמעט באופן מיידי, אבל הוא עדיין לא מעוצב כברירת מחדל, מה שעלול לגרום לבעיה של הצגת 'פלאש של תוכן ללא עיצוב' (FOUC). המצב הזה בולט במיוחד אם משתמשים בספריית CSS-in-JS כדי ליצור סלקטורים, כי חבילת ה-JavaScript צריכה לסיים את הביצוע שלה לפני שאפשר להחיל סגנונות.

כדי למנוע זאת, אפשר להוסיף את ה-CSS הקריטי, או את כמות ה-CSS המינימלית שנדרשת כדי להציג את הדף הראשוני, ישירות ל-<head> של מסמך ה-HTML. react-snap משתמש בספרייה אחרת של צד שלישי, minimalcss, כדי לחלץ קוד CSS קריטי למסלולים שונים. כדי להפעיל את התכונה הזו, צריך לציין את הפרטים הבאים בקובץ package.json:

"reactSnap": {
 
"inlineCss": true
}

בתצוגה המקדימה של התגובה ב-Chrome DevTools יוצג עכשיו הדף עם העיצוב, עם הקוד החשוב של CSS שמוטמע בקוד.

השוואה בין המצב לפני ואחרי. בתמונה שלאחר מכן מוצג תוכן שעבר רינדור וסוגנן בגלל CSS קריטי שמוטמע בקוד.

סיכום

אם אתם לא מבצעים עיבוד בצד השרת של מסלולים באפליקציה, תוכלו להשתמש ב-react-snap כדי לבצע עיבוד מראש של HTML סטטי למשתמשים.

  1. מתקינים אותו כחבילה תלויה לפיתוח ומתחילים רק עם הגדרות ברירת המחדל.
  2. אם האפשרות הניסיונית inlineCss מתאימה לאתר שלכם, כדאי להשתמש בה כדי להטמיע בקוד נכסי CSS קריטיים.
  3. אם אתם משתמשים בחלוקת קוד ברמת הרכיב בתוך מסלולים כלשהם, חשוב להקפיד לא לבצע עיבוד מראש של מצב טעינה למשתמשים. תוכלו לקרוא על כך בהרחבה בקובץ react-snap README.