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

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

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

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

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

למה זה שימושי?

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

כדי לפתור את הבעיה הזו, מפתחים רבים משתמשים בגישה של עיבוד (render) האפליקציה בשרת במקום רק להפעיל אותה בדפדפן. בכל מעבר לדף או למסלול, ה-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 תוכלו לראות עכשיו את הדף המעוצב עם טקסט קריטי של CSS.

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

סיכום

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

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