עיבוד באינטרנט

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

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

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

רינדור בצד השרת (SSR)
עיבוד אפליקציה בצד הלקוח או אפליקציה אוניברסלית ל-HTML בשרת.
עיבוד בצד הלקוח (CSR)
עיבוד אפליקציה בדפדפן, שימוש ב-JavaScript כדי לשנות את ה-DOM.
התייבשות
"שיפור" תצוגות JavaScript בלקוח כדי לעשות שימוש חוזר בעץ ה-DOM של ה-HTML שבעיבוד השרת ובנתוניו.
עיבוד מראש
להפעיל אפליקציה בצד הלקוח בזמן ה-build כדי לתעד את המצב הראשוני כ-HTML סטטי.

ביצועים

Time to First Byte (TTFB)
הזמן שחולף מרגע הלחיצה על קישור ועד לבייט הראשון של תוכן שנטען בדף החדש.
הצגת תוכן ראשוני (FCP)
השעה שבה התוכן המבוקש (גוף המאמר וכו') הופך לגלוי.
אינטראקציה עם הצגת התמונה הבאה (INP)
מדד מייצג שמעריך אם דף מגיב במהירות לקלט של משתמשים באופן עקבי.
זמן חסימה כולל (TBT)
מדד proxy ל-INP שמחשב את משך הזמן שבו ה-thread הראשי נחסם במהלך טעינת הדף.

רינדור בצד השרת

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

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

תרשים
    של רינדור בצד השרת וביצוע JS שמשפיעים על FCP ו-TTI.
FCP ו-TTI עם רינדור בצד השרת.

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

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

הרבה ספריות, ארכיטקטורות ומסגרות מודרניות מאפשרות לעבד את אותה האפליקציה גם בלקוח וגם בשרת. אפשר להשתמש בשיטות האלה לעיבוד בצד השרת. עם זאת, ארכיטקטורות שבהן הרינדור מתבצע גם בשרת וגם אצל הלקוח, הן סוג של פתרונות משלה, עם מאפייני ביצועים וחסרונות שונים מאוד. משתמשי React יכולים להשתמש ב-API ל-DOM של השרת או בפתרונות שמבוססים עליהם, כמו Next.js, לעיבוד בצד השרת. משתמשי Vue יכולים להשתמש במדריך לעיבוד בצד השרת או ב-Nuxt. ב-Agular יש את האפשרות Universal. עם זאת, רוב הפתרונות הפופולריים משתמשים במצב של מאזן הנוזלים, לכן חשוב לשים לב לגישות שבהן הכלי שלכם משתמש.

רינדור סטטי

רינדור סטטי מתרחש בזמן ה-build. הגישה הזו מציעה FCP מהיר, וגם TBT ו-INP נמוכים יותר, כל עוד אתם מגבילים את כמות ה-JS בצד הלקוח בדפים שלכם. בשונה מעיבוד בצד השרת, הוא גם משיג TTFB מהיר ועקבי, כי אין צורך ליצור באופן דינמי את ה-HTML של דף בשרת. באופן כללי, עיבוד סטטי פירושו יצירת קובץ HTML נפרד לכל כתובת URL מראש. עם תגובות HTML שנוצרות מראש, תוכלו לפרוס עיבודים סטטיים ל-CDN מרובים כדי לנצל את השמירה במטמון קצה.

תרשים
    של רינדור סטטי וביצוע אופציונלי של JS שמשפיעים על FCP ו-TTI.
FCP ו-TTI עם רינדור סטטי.

פתרונות לעיבוד סטטי קיימים בכל הצורות והגדלים. כלים כמו Gatsby נועדו לגרום למפתחים להרגיש שהאפליקציה שלהם מעובדת באופן דינמי, ולא נוצרת כשלב build. כלים סטטיים ליצירת אתרים כמו 11ty , Jekyll ו-Metalsmith מאמצים את האופי הסטטי שלהם ומספקים גישה מבוססת-תבניות יותר.

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

יכול להיות שמשתמשי React כבר מכירים את Gatsby, את Next.js static export או את Navi, ובעזרתם קל ליצור דפים מרכיבים. עם זאת, עיבוד סטטי ועיבוד מראש מתנהגים באופן שונה: דפים שרונדרו באופן סטטי הם אינטראקטיביים בלי שצריך להפעיל הרבה JavaScript בצד הלקוח, ואילו העיבוד מראש משפר את ה-FCP של אפליקציית דף יחיד שחייבים להפעיל בצד הלקוח כדי להפוך את הדפים לאינטראקטיביים באמת.

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

בדיקה שימושית נוספת היא שימוש בויסות רשת (Network throttling) בכלי הפיתוח של Chrome, ובודקים כמה הורדות JavaScript יורדות לפני שהדף הופך לאינטראקטיבי. בדרך כלל, כדי שעיבוד מראש יהיה אינטראקטיבי, נדרש JavaScript נוסף כדי להפוך אותו לאינטראקטיבי, ו-JavaScript נוטה להיות מורכב יותר מהגישה של שיפור הדרגתי שבו נעשה שימוש בעיבוד סטטי.

רינדור בצד השרת לעומת רינדור סטטי

רינדור בצד השרת הוא לא הפתרון הכי טוב לכל דבר, כי האופי הדינמי שלו עלול לגרום לעלויות גבוהות של תקורה מחשוב. פתרונות רבים לעיבוד בצד השרת לא מתבטלים בשלב מוקדם, מעכבים את TTFB או מכפילים את הנתונים שנשלחים (לדוגמה, מצבים מוטבעים שמשמשים את JavaScript בצד הלקוח). ב-React, renderToString() יכול להיות איטי כי הוא סינכרוני עם שרשור יחיד. ממשקי API חדשים יותר ל-DOM של שרת תגובה תומכים בסטרימינג, שיכול להעביר את החלק הראשוני של תגובת ה-HTML לדפדפן מוקדם יותר, בזמן ששאר החלקים עדיין נוצרים בשרת.

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

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

גם רינדור בצד השרת יכול להציג החלטות מעניינות כשיוצרים PWA: האם עדיף להשתמש בדף מלא של service worker במטמון, או רק לקטעי תוכן בודדים בלנדר?

רינדור בצד הלקוח

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

כשמדובר במכשירים ניידים, לפעמים קשה ליצור ולשמור על העיבוד בצד הלקוח במהירות. בעזרת עבודה קלה כדי לשמור על תקציב JavaScript צמוד ולספק ערך בכמה שפחות פעולות דו-כיווניות, תוכלו להשיג רינדור בצד הלקוח כמעט כדי לשכפל את הביצועים של רינדור טהור בצד השרת. כדי לגרום למנתח לעבוד מהר יותר, תוכלו להעביר סקריפטים ונתונים קריטיים באמצעות <link rel=preload> מומלץ גם להשתמש בדפוסים כמו PRPL כדי להבטיח שהניווטים הראשונים והניווטים הבאים ירגישו מיד.

תרשים
    של רינדור בצד הלקוח שמשפיע על FCP ועל TTI.
FCP ו-TTI עם רינדור בצד הלקוח.

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

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

לאנשים שיוצרים אפליקציות עם דף יחיד, זיהוי חלקים עיקריים בממשק המשתמש שמשותפים לרוב הדפים מאפשר להשתמש בשיטה שמירה במטמון של מעטפת האפליקציה. בשילוב עם Service Workers, הפעולה הזו יכולה לשפר משמעותית את הביצועים בביקורים חוזרים, כי הדף יכול לטעון במהירות רבה את ה-HTML של מעטפת האפליקציה ואת יחסי התלות מ-CacheStorage.

רה-הידרציה משלבת רינדור בצד השרת ועיבוד בצד הלקוח

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

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

בעיית התייבשות: אפליקציה אחת במחיר של שניים

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

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

השרת מחזיר תיאור של ממשק המשתמש של האפליקציה בתגובה לבקשת ניווט, אבל הוא גם מחזיר את נתוני המקור ששימשו לכתיבת ממשק המשתמש הזה, ועותק מלא של ההטמעה של ממשק המשתמש שמתבצעת הפעלה של הלקוח. ממשק המשתמש לא הופך לאינטראקטיבי עד שמסיימים את הטעינה וההפעלה של bundle.js.

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

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

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

הזרימו את העיבוד בצד השרת וביצעו רענון בהדרגה

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

סטרימינג של רינדור בצד השרת מאפשר לשלוח HTML במקטעים, שהדפדפן יכול לעבד בהדרגה עם קבלתו. כך ניתן לשלוח למשתמשים תגי עיצוב מהר יותר, ולהאיץ את תהליך ה-FCP. ב-React, שידורי סטרימינג אסינכרוניים ב-renderToPipeableStream() בהשוואה ל-renderToString() סינכרוני, המשמעות היא שהלחץ העורפי מטופל היטב.

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

בנוסף, תהליך הידרציה הדרגתי יכול לעזור להימנע מהתקלה הנפוצות ביותר בתהליך הרינדור של רינדור בצד השרת: עץ DOM שמעובד על ידי שרת מושמד ואז נבנה מחדש באופן מיידי, לרוב כי העיבוד הסינכרוני הראשוני בצד הלקוח דורש נתונים שלא היו מוכנים בהכרח, לעיתים קרובות Promise שעדיין לא טופל.

התייבשות חלקית

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

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

עיבוד טריסומורפי

אם יש לכם אפשרות להשתמש ב-service worker, כדאי לשקול רינדור טריזומורפי. זו שיטה שמאפשרת להשתמש בסטרימינג בצד השרת כדי לבצע ניווטים ראשוניים או שאינם של JS, ואז לבקש מ-Service Worker לקבל רינדור HTML לצורך ניווטים לאחר ההתקנה. כך אפשר לעדכן את הרכיבים והתבניות שנשמרו במטמון, ולהפעיל ניווטים בסגנון SPA לעיבוד תצוגות חדשות באותה סשן. הגישה הזו פועלת בצורה הטובה ביותר כשאפשר לשתף את אותו תבניות וקוד ניתוב בין השרת, דף הלקוח ו-Service Worker.

תרשים רינדור טריסומורפי, שבו רואים דפדפן ו-Service Worker בתקשורת עם השרת.
תרשים שממחיש איך פועל רינדור טריסומורפי.

שיקולים בנוגע לאופטימיזציה למנועי חיפוש

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

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

צילום מסך של ממשק המשתמש של בדיקת ההתאמה לניידים.
ממשק המשתמש של בדיקת ההתאמה לניידים.

סיכום

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

אינפוגרפיקה שמציגה את מגוון האפשרויות שמתוארות במאמר הזה.
אפשרויות רינדור והיתרונות שלהן.

זיכויים

תודה לכולם על הביקורות ועל ההשראה שהם פרסמו:

ג'פרי פוסניק, חוסיין דג'ירד, שובי פאניקר, כריס הרלסון וסבסטיאן מרקבוג'