אחת מההחלטות המרכזיות שמפתחי אתרים צריכים לקבל היא איפה להטמיע את הלוגיקה והעיבוד באפליקציה שלהם. זה יכול להיות קשה כי יש כל כך הרבה דרכים לבנות אתר.
ההבנה שלנו בתחום הזה מבוססת על העבודה שלנו ב-Chrome ועל השיחות שלנו עם אתרים גדולים בשנים האחרונות. באופן כללי, אנחנו ממליצים למפתחים לשקול רינדור בצד השרת או רינדור סטטי במקום גישה של התאמה מחדש מלאה.
כדי להבין טוב יותר את הארכיטקטורות שאנחנו בוחרים מתוכן כשאנחנו מקבלים את ההחלטה הזו, אנחנו צריכים הבנה מעמיקה של כל גישה ושל מונחים עקביים לשימוש כשאנחנו מדברים עליהן. ההבדלים בין הגישות לעיבוד עוזרים להמחיש את הפשרות של העיבוד באינטרנט מנקודת המבט של ביצועי הדף.
הסברים על המונחים
קודם כל, נגדיר כמה מונחים שבהם נשתמש.
רינדור
- עיבוד בצד השרת (SSR)
- עיבוד (רינדור) של אפליקציה בשרת כדי לשלוח HTML ללקוח, במקום JavaScript.
- רינדור בצד הלקוח (CSR)
- עיבוד (רינדור) של אפליקציה בדפדפן, באמצעות JavaScript כדי לשנות את DOM.
- שתייה
- 'הפעלה' של תצוגות JavaScript בצד הלקוח כדי שיעשה שימוש חוזר בנתונים ובעץ ה-DOM של ה-HTML שעבר עיבוד בשרת.
- עיבוד מראש
- הרצת אפליקציה בצד הלקוח בזמן ה-build כדי לתעד את המצב הראשוני שלה כ-HTML סטטי.
ביצועים
- זמן לקבלת בייט התגובה הראשון (TTFB)
- הזמן שחלף בין הלחיצה על הקישור לטעינת הבייט הראשון של התוכן בדף החדש.
- הצגת תוכן ראשוני (FCP)
- הזמן שבו התוכן המבוקש (גוף המאמר וכו') הופך להיות גלוי.
- מהירות התגובה לאינטראקציה באתר (INP)
- מדד מייצג שמאפשר להעריך אם הדף מגיב במהירות ובאופן עקבי לקלט של משתמשים.
- זמן החסימה הכולל (TBT)
- מדד proxy ל-INP שמחשב את משך הזמן שבו התהליך הראשי היה חסום במהלך טעינת הדף.
רינדור בצד השרת
עיבוד בצד השרת יוצר את קוד ה-HTML המלא של דף בשרת בתגובה לניווט. כך אפשר למנוע נסיעות הלוך ושוב נוספות של אחזור נתונים ויצירת תבניות בצד הלקוח, כי ה-renderer מטפל בהן לפני שהדפדפן מקבל תשובה.
בדרך כלל, עיבוד בצד השרת מניב זמן FCP מהיר. הפעלת הלוגיקה של הדף והעיבוד שלו בשרת מאפשרת לכם להימנע משליחת הרבה JavaScript ללקוח. כך אפשר לצמצם את זמן הטעינה של הדף, וגם להפחית את מספר הבקשות הנכנסות (INP), כי החוט הראשי לא נחסם בתדירות גבוהה כל כך במהלך טעינת הדף. כאשר החוט הראשי חסום בתדירות נמוכה יותר, יש יותר הזדמנויות להריץ אינטראקציות של משתמשים מוקדם יותר. זה הגיוני, כי כשמשתמשים ברינדור בצד השרת, בעצם שולחים טקסט וקישורים לדפדפן של המשתמש. הגישה הזו יכולה להתאים למגוון תנאים של מכשירים ורשתות, ומאפשרת לבצע אופטימיזציה מעניינת של הדפדפן, כמו ניתוח מסמכים בסטרימינג.
כשמשתמשים ברינדור בצד השרת, יש פחות סיכוי שהמשתמשים יצטרכו להמתין להרצה של JavaScript שמקושר ל-CPU כדי שיוכלו להשתמש באתר. גם אם אי אפשר להימנע משימוש ב-JavaScript של צד שלישי, שימוש ברינדור בצד השרת כדי לצמצם את העלויות של JavaScript מהדומיין הנוכחי יכול להגדיל את התקציב לרכיבים האחרים. עם זאת, יש יתרון אחד פוטנציאלי לגישה הזו: יצירת דפים בשרת אורכת זמן, ויכול להיות שהיא תגדיל את זמן אחזור ה-First Byte של הדף.
השאלה אם עיבוד בצד השרת מספיק לאפליקציה שלכם תלויה במידה רבה בסוג החוויה שאתם מפתחים. יש ויכוח ארוך שנים לגבי היישומים הנכונים של עיבוד בצד השרת לעומת עיבוד בצד הלקוח, אבל תמיד אפשר לבחור להשתמש בעיבוד בצד השרת בדפים מסוימים ולא בדפים אחרים. בחלק מהאתרים השתמשו בשיטות עיבוד היברידיות בהצלחה. לדוגמה, Netflix מבצעת עיבוד שרת של דפי הנחיתה הסטטיים יחסית שלה, וprefetching את ה-JS לדפים עם אינטראקציה רבה. כך יש סיכוי טוב יותר שהדפים הכבדים האלה, שמתבצע בהם עיבוד לקוח, ייטענו במהירות.
יש הרבה מסגרות, ספריות וארכיטקטורות מודרניות שמאפשרות להציג את אותה אפליקציה גם בצד הלקוח וגם בצד השרת. אפשר להשתמש בשיטות האלה לעיבוד בצד השרת. עם זאת, ארכיטקטורות שבהן הרינדור מתבצע גם בשרת וגם בלקוח הן סוג נפרד של פתרון, עם מאפייני ביצועים ומאזני יתרונות וחסרונות שונים מאוד. משתמשי React יכולים להשתמש בממשקי API של DOM בצד השרת או בפתרונות שמבוססים עליהם, כמו Next.js, כדי לבצע רינדור בצד השרת. משתמשי Vue יכולים להשתמש במדריך לעיבוד בצד השרת של Vue או ב-Nuxt. ב-Angular יש את Universal. עם זאת, ברוב הפתרונות הפופולריים נעשה שימוש בצורה כלשהי של הידרציה, לכן חשוב לבדוק את הגישות שבהן הכלי שלכם משתמש.
עיבוד נתונים סטטי
עיבוד סטטי מתבצע בזמן ה-build. הגישה הזו מאפשרת זמן FCP מהיר, וגם זמן TBT ו-INP נמוכים יותר, כל עוד מגבילים את כמות ה-JS בצד הלקוח בדפים. בניגוד לעיבוד בצד השרת, גם זמן אחזור ה-TTFB מהיר באופן עקבי, כי לא צריך ליצור את ה-HTML של הדף באופן דינמי בשרת. באופן כללי, עיבוד סטטי הוא יצירה מראש של קובץ HTML נפרד לכל כתובת URL. כשהתגובות ב-HTML נוצרות מראש, אפשר לפרוס עיבודים סטטיים במספר רשתות CDN כדי לנצל את היתרונות של שמירת פריטים במטמון קצה.
יש פתרונות רבים לעיבוד סטטי בכל הצורות והגדלים. כלים כמו Gatsby נועדו לגרום למפתחים להרגיש שהאפליקציה שלהם עוברת עיבוד דינמי, ולא נוצרת כשלב build. כלים ליצירת אתרים סטטיים כמו 11ty, Jekyll ו-Metalsmith מתבססים על האופי הסטטי שלהם ומספקים גישה שמבוססת יותר על תבניות.
אחד החסרונות של עיבוד סטטי הוא שהוא חייב ליצור קובצי HTML נפרדים לכל כתובת URL אפשרית. יכול להיות שהפעולה הזו תהיה מאתגרת או בלתי אפשרית אם אי אפשר לחזות מראש מה יהיו כתובות ה-URL האלה, או לאתרים עם מספר גדול של דפים ייחודיים.
משתמשי React אולי מכירים את Gatsby, את האפשרות לייצא סטטי ב-Next.js או את Navi, שבעזרתם אפשר ליצור דפים בקלות מרכיבים. עם זאת, יש הבדל בין עיבוד סטטי לעיבוד מראש: דפים שעברו עיבוד סטטי הם אינטראקטיביים בלי צורך להריץ הרבה JavaScript בצד הלקוח, ואילו עיבוד מראש משפר את ה-FCP של אפליקציה עם דף יחיד (SPA) שצריך להפעיל בצד הלקוח כדי שהדפים יהיו אינטראקטיביים באמת.
אם אתם לא בטוחים אם פתרון מסוים הוא עיבוד סטטי או עיבוד מראש, נסו להשבית את JavaScript ולטעון את הדף שרוצים לבדוק. בדפים שעברו עיבוד סטטי, רוב התכונות האינטראקטיביות עדיין קיימות ללא JavaScript. יכול להיות שבדפים שעברו עיבוד מראש עדיין יהיו תכונות בסיסיות כמו קישורים עם JavaScript מושבת, אבל רוב הדף לא פעיל.
בדיקה שימושית נוספת היא להשתמש בבקרת תעבורת נתונים ברשת בכלים למפתחים ב-Chrome ולבדוק כמה הורדות של JavaScript מתבצעות לפני שהדף הופך לאינטראקטיבי. בדרך כלל, כדי שהדף יהיה אינטראקטיבי, צריך להוסיף לו יותר קוד JavaScript, והקוד הזה בדרך כלל מורכב יותר מהגישה של שיפור הדרגתי שמשמשת ברינדור סטטי.
עיבוד בצד השרת לעומת עיבוד סטטי
עיבוד בצד השרת הוא לא הפתרון הטוב ביותר לכל דבר, כי לאופי הדינמי שלו יכולים להיות עלויות משמעותיות של תקורה מחשובית. פתרונות רבים לעיבוד בצד השרת לא מבצעים שטיפה מוקדמת, לא מעכבים את זמן אחזור הבקשה (TTFB) או מכפילים את הנתונים שנשלחים (לדוגמה, מצבים שמוטמעים בקוד ומשמשים את JavaScript בצד הלקוח). ב-React, הפונקציה renderToString()
יכולה להיות איטית כי היא סינכרונית וחד-ליבה.
ממשקי ה-API החדשים יותר של DOM בשרת של React תומכים בסטרימינג, שמאפשר להעביר את החלק הראשוני של תגובה ב-HTML לדפדפן מוקדם יותר, בזמן ששאר התגובה עדיין נוצרת בשרת.
כדי לבצע עיבוד בצד השרת בצורה 'נכונה', יכול להיות שתצטרכו למצוא או ליצור פתרון לאחסון ב-cache של רכיבים, לנהל את צריכת הזיכרון, להשתמש בשיטות של memoization ועוד. לרוב מעבדים או יוצרים מחדש את אותה אפליקציה פעמיים, פעם אחת בצד הלקוח ופעם אחת בצד השרת. עיבוד בצד השרת שמציג תוכן מוקדם יותר לא בהכרח מפחית את כמות העבודה שלכם. אם יש לכם הרבה עבודה בצד הלקוח אחרי שהתגובה ב-HTML שנוצרה על ידי השרת מגיעה ללקוח, עדיין יכול להיות שהיא תוביל לעלייה ב-TBT וב-INP של האתר.
עיבוד בצד השרת יוצר קובץ HTML על פי דרישה לכל כתובת URL, אבל הוא עשוי להיות איטי יותר מאשר הצגת תוכן סטטי שעבר עיבוד. אם אתם יכולים להשקיע את המאמץ הנוסף, עיבוד בצד השרת בשילוב עם אחסון ב-cache של HTML יכולים לצמצם באופן משמעותי את זמן העיבוד בשרת. היתרון של עיבוד בצד השרת הוא היכולת למשוך יותר נתונים 'חיים' ולהגיב לקבוצה מלאה יותר של בקשות, בהשוואה לעיבוד סטטי. דפים שצריך להתאים אישית הם דוגמה קונקרטית לסוג הבקשה שלא פועלת טוב עם עיבוד סטטי.
כשמפתחים PWA, אפשר גם להחליט אם להשתמש בגישה של עיבוד בצד השרת: האם כדאי להשתמש במטמון של שירות עבודה לדף שלם, או רק לעבד בצד השרת קטעי תוכן נפרדים?
רינדור בצד הלקוח
עיבוד מצד הלקוח (client-side rendering) הוא עיבוד דפים ישירות בדפדפן באמצעות JavaScript. כל הלוגיקה, אחזור הנתונים, יצירת התבניות והניתוב מתבצעים בצד הלקוח במקום בשרת. התוצאה בפועל היא שהשרת מעביר יותר נתונים למכשיר של המשתמש, ויש לכך מחיר משלו.
יכול להיות שיהיה קשה לבצע עיבוד מצד הלקוח ולשמור על מהירות גבוהה במכשירים ניידים.
אם תשקיעו קצת עבודה כדי לשמור על תקציב JavaScript קפדני ולספק ערך בפחות נסיעות הלוך ושוב, תוכלו להגיע לרמת רינדור בצד הלקוח שתשיג ביצועים כמעט זהים לאלה של רינדור בצד השרת. כדי שהניתוח יפעל מהר יותר, אפשר לשלוח סקריפטים ונתונים קריטיים באמצעות <link rel=preload>
. מומלץ גם להשתמש בתבניות כמו PRPL כדי לוודא שהניווט הראשוני והניווטים הבאים יהיו מיידיים.
החיסרון העיקרי של עיבוד בצד הלקוח הוא שככל שהאפליקציה גדלה, כך עולה גם כמות ה-JavaScript הנדרשת, וזה עלול להשפיע על מדד ה-INP של הדף. הבעיה הזו מתעצמת כשמוסיפים ספריות JavaScript חדשות, polyfills וקוד של צד שלישי, שמתחרים על כוח העיבוד ולעיתים קרובות צריך לעבד אותם לפני שאפשר לרנדר את תוכן הדף.
בחוויות שמשתמשות ברינדור בצד הלקוח ומסתמכות על חבילות JavaScript גדולות, כדאי להשתמש בחלוקה אגרסיבית של קוד כדי להקטין את TBT ואת INP במהלך טעינת הדף, וגם בטעינה איטית של JavaScript כדי להציג רק את מה שהמשתמש צריך, כשהוא צריך. לחוויות עם אינטראקציה מועטה או ללא אינטראקציה, עיבוד בצד השרת יכול להיות פתרון גמיש יותר לבעיות האלה.
אם אתם מפתחים אפליקציות של דף יחיד, זיהוי החלקים המרכזיים בממשק המשתמש שרוב הדפים משתפים מאפשר לכם להשתמש בשיטה של אחסון במטמון של מעטפת האפליקציה. בשילוב עם קובצי שירות, הפתרון הזה יכול לשפר באופן משמעותי את הביצועים של הדף בביקורים חוזרים, כי הדף יכול לטעון את קובץ ה-HTML של מעטפת האפליקציה ואת יחסי התלות מ-CacheStorage
במהירות רבה.
תהליך ההרטבה מחדש משלב רינדור בצד השרת ורינדור בצד הלקוח
החזרה למצב פעיל היא גישה שמנסה לטשטש את הפשרות בין עיבוד מצד הלקוח לבין עיבוד מצד השרת, על ידי ביצוע של שניהם. בקשות ניווט, כמו טעינה מלאה של דף או טעינת דף מחדש, מטופלות על ידי שרת שמרינדור את האפליקציה ל-HTML. לאחר מכן, ה-JavaScript והנתונים ששימשו לרינדור מוטמעים במסמך שנוצר. אם מבצעים את הפעולה הזו בזהירות, אפשר לקבל עיבוד מהיר בצד השרת כמו ב-FCP, ואז 'להמשיך' על ידי עיבוד חוזר בצד הלקוח. זהו פתרון יעיל, אבל יכולים להיות לו חסרונות משמעותיים בביצועים.
החיסרון העיקרי של עיבוד בצד השרת עם התאוששות הוא שיכולה להיות לו השפעה שלילית משמעותית על TBT ו-INP, גם אם הוא משפר את FCP. דפים שעבר עיבוד בצד השרת עשויים להיראות נטענים ואינטראקטיביים, אבל הם לא יכולים להגיב לקלט עד שהסקריפטים של הרכיבים מצד הלקוח מופעלים וגורמים מטפלים באירועים מצורפים. בנייד, התהליך הזה עשוי להימשך דקות, ולבלבל את המשתמש ולגרום לו תסכול.
בעיה בהחזרת מים לגוף: אפליקציה אחת במחיר של שתיים
כדי ש-JavaScript בצד הלקוח 'ימשיך' בצורה מדויקת מהמקום שבו השרת הפסיק, בלי לבקש מחדש את כל הנתונים שבהם השרת השתמש כדי ליצור את ה-HTML, רוב הפתרונות לעיבוד בצד השרת מסדרים בסדרת רצף את התגובה מהתלות הנתונים של ממשק המשתמש בתור תגי סקריפט במסמך. מכיוון שהפעולה הזו יוצרת הרבה כפילויות של HTML, תהליך ההחזרה למצב פעיל עלול לגרום לבעיות נוספות מלבד עיכוב האינטראקטיביות.
השרת מחזיר תיאור של ממשק המשתמש של האפליקציה בתגובה לבקשת ניווט, אבל הוא מחזיר גם את נתוני המקור ששימשו ליצירת ממשק המשתמש הזה, וכן עותק מלא של הטמעת ממשק המשתמש, שמופעל לאחר מכן בצד הלקוח. ממשק המשתמש הופך לאינטראקטיבי רק אחרי ש-bundle.js
מסיים את הטעינה וההפעלה.
מדדי הביצועים שנאספים מאתרים אמיתיים באמצעות עיבוד ורטרו-פוזיציה (rehydration) בצד השרת מצביעים על כך שזו לרוב לא האפשרות הטובה ביותר. הסיבה החשובה ביותר היא ההשפעה על חוויית המשתמש, כשדף נראה מוכן אבל אף אחת מהתכונות האינטראקטיביות שלו לא פועלת.
עם זאת, יש תקווה לעיבוד בצד השרת עם התחדשות (rehydration). בטווח הקצר, שימוש ברינדור בצד השרת רק לתוכן שקל לשמור במטמון יכול לצמצם את זמן אחזור הבקשה הראשונית (TTFB) ולהניב תוצאות דומות לעיבוד מראש. יכול להיות ששימוש בהידרציה מצטברת, הדרגתית או חלקית הוא המפתח לשיפור היעילות של השיטה הזו בעתיד.
סטרימינג של עיבוד בצד השרת והחזרת לחות באופן הדרגתי
בשנים האחרונות התרחשו כמה התפתחויות בתחום ה-rendering בצד השרת.
עיבוד בזמן אמת בצד השרת מאפשר לשלוח HTML בקטעים שהדפדפן יכול לעבד באופן הדרגתי בזמן שהוא מתקבל. כך אפשר להעביר את ה-Markup למשתמשים מהר יותר, וכך להאיץ את ה-FCP. ב-React, כשהסטרים אסינכרונים ב-renderToPipeableStream()
, בהשוואה ל-renderToString()
הסינכרוני, המשמעות היא שהלחץ החוזר מטופל בצורה טובה.
כדאי גם לשקול שימוש בהידרוליזציה הדרגתית, ו-React הטמיע אותה. בגישה הזו, חלקים נפרדים של אפליקציה שעבר רינדור בשרת 'מתחילים לפעול' לאורך זמן, במקום הגישה הנפוצה הנוכחית של איפוס האפליקציה כולה בבת אחת. כך אפשר לצמצם את כמות ה-JavaScript שנדרשת כדי להפוך דפים לאינטראקטיביים, כי אפשר לדחות את השדרוג של חלקים בדף בעדיפות נמוכה בצד הלקוח כדי למנוע חסימת השרשור הראשי, וכך אינטראקציות של משתמשים יכולות להתרחש מוקדם יותר אחרי שהמשתמשים מתחילים אותן.
שיקום הדרגתי יכול גם לעזור לכם להימנע מאחד מהמלכודות הנפוצות ביותר של שיקום הדרגתי של עיבוד בצד השרת: עץ DOM שעבר עיבוד בצד השרת נהרס ולאחר מכן נבנה מחדש באופן מיידי. ברוב המקרים, הסיבה לכך היא שהעיבוד הסינכרוני הראשוני בצד הלקוח דרש נתונים שלא היו מוכנים לגמרי, לרוב Promise
שעדיין לא נפתר.
התאוששות חלקית
התברר שקשה להטמיע התאוששות חלקית. הגישה הזו היא הרחבה של תהליך ההחזרה ההדרגתית של הנתונים, שמנתח חלקים נפרדים בדף (רכיבים, תצוגות או עצים) ומזהה את החלקים עם אינטראקטיביות מועטה או ללא תגובה. לכל אחד מהחלקים הסטטיים האלה, קוד ה-JavaScript התואם מומר להפניות פסיביות ולתכונות דקורטיביות, וכך מפחית את טביעת הרגל שלהם בצד הלקוח כמעט לאפס.
לגישה של העשרת דפי HTML חלקית יש בעיות וחסרונות משלה. הדבר מציב כמה אתגרים מעניינים לגבי שמירת נתונים במטמון, והניווט מצד הלקוח אומר שאי אפשר להניח ש-HTML שעבר עיבוד בשרת לחלקים סטטיים של האפליקציה זמין בלי טעינת דף מלאה.
רינדור טריסומורפי
אם אתם יכולים להשתמש בשירותי עבודה, כדאי לשקול עיבוד טריסומוריפי. זוהי טכניקה שמאפשרת להשתמש ברינדור בצד השרת בסטרימינג לניווטים ראשוניים או לניווטים שאינם ב-JS, ולאחר מכן לאפשר ל-service worker לבצע רינדור של HTML לניווטים אחרי שהוא מותקן. כך אפשר לשמור על עדכניות של רכיבים ותבניות ששמורים במטמון, ולאפשר ניווט בסגנון SPA לצורך עיבוד (רינדור) של תצוגות חדשות באותו סשן. הגישה הזו הכי מתאימה כשאפשר לשתף את אותו קוד תבניות וניתוב בין השרת, דף הלקוח ו-service worker.
שיקולים לגבי אופטימיזציה למנועי חיפוש (SEO)
כשצוותים בוחרים אסטרטגיית עיבוד לאינטרנט, הם בדרך כלל מביאים בחשבון את ההשפעה של אופטימיזציה למנועי חיפוש. עיבוד בצד השרת הוא בחירה פופולרית להצגת חוויה 'שלמה למראה' שסורקים יכולים לפרש. כלי הסריקה יכולים להבין JavaScript, אבל לעיתים קרובות יש מגבלות על אופן העיבוד שלהם. עיבוד בצד הלקוח יכול לפעול, אבל בדרך כלל נדרש לו עוד בדיקה ועלות נוספת. לאחרונה, עיבוד דינמי הפך גם הוא לאפשרות ששווה לשקול אם הארכיטקטורה שלכם תלויה במידה רבה ב-JavaScript בצד הלקוח.
אם אתם לא בטוחים, הכלי לבדיקת ההתאמה לניידים הוא דרך מצוינת לבדוק שהגישה שבחרתם עושה את מה שאתם רוצים. התצוגה המקדימה הזו מראה איך כל דף נראה לסורק של Google, את תוכן ה-HTML בסדרה שהוא מוצא אחרי ביצוע ה-JavaScript ואת כל השגיאות שנמצאו במהלך העיבוד.
סיכום
כשמחליטים איזו גישה לבחור לעיבוד, חשוב למדוד את נקודות החולשה ולנסות להבין מהן. כדאי לבדוק אם רינדור סטטי או רינדור בצד השרת יכולים לעזור לכם להגיע לשם. אפשר לשלוח בעיקר HTML עם כמות מינימלית של JavaScript כדי ליצור חוויה אינטראקטיבית. הנה אינפוגרפיקה שימושית שמראה את הספקטרום של השרת-הלקוח:
זיכויים
תודה לכולם על הביקורות וההשראה:
ג'פרי פונסניק, חסין דירדה, שובי פאניקר, כריס הרלסון וסמואל מרקבאג'