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

לא רינדור בצד השרת אבל עדיין רוצים לזרז את הביצועים של אתר React? כדאי לנסות עיבוד מראש!

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

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

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

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

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

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

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

תגובה-הצמדה

ב-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 כדי לעבד את רכיב התגובה ברמה הבסיסית (root) ישירות ל-DOM, המערכת בודקת אם יש כבר צמתים מסוג צאצא כדי לקבוע אם תוכני HTML עובדו מראש (או עובדו בשרת). במקרה כזה, במקום ליצור פונקציות חדשות, נעשה שימוש ב-ReactDOM.hydrate כדי לצרף מאזינים לאירועים ל-HTML שכבר נוצר.

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

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

הבזק של תוכן לא מעוצב

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

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

"reactSnap": {
  "inlineCss": true
}

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

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

סיכום

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

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