תאריך פרסום: 6 בפברואר 2019, תאריך עדכון אחרון: 5 בינואר 2026
אחת ההחלטות החשובות ביותר שמפתחי אתרים צריכים לקבל היא איפה להטמיע את הלוגיקה והרינדור באפליקציה שלהם. זה יכול להיות קשה כי יש כל כך הרבה דרכים לבנות אתר.
ההבנה שלנו בתחום הזה מבוססת על העבודה שלנו ב-Chrome, שבה דיברנו עם אתרים גדולים בשנים האחרונות. באופן כללי, אנחנו ממליצים למפתחים לשקול עיבוד בצד השרת או עיבוד סטטי, ולא גישה של ריהידרציה מלאה.
כדי להבין טוב יותר את הארכיטקטורות שאנחנו בוחרים מביניהן, אנחנו צריכים טרמינולוגיה עקבית ומסגרת משותפת לכל גישה. כך תוכלו להעריך טוב יותר את היתרונות והחסרונות של כל גישת עיבוד מנקודת המבט של מהירות הטעינה של דף.
הסברים על המונחים
קודם כל, נגדיר כמה מונחים שנשתמש בהם.
רינדור
- רינדור בצד השרת (SSR)
- עיבוד של אפליקציה בשרת כדי לשלוח HTML, ולא JavaScript, ללקוח.
- רינדור בצד הלקוח (CSR)
- עיבוד אפליקציה בדפדפן, באמצעות JavaScript לשינוי ה-DOM.
- רינדור מראש
- הפעלת אפליקציה בצד הלקוח בזמן ה-build כדי לתעד את המצב הראשוני שלה כ-HTML סטטי. הערה: "רינדור מראש" במובן הזה שונה מרינדור מראש בדפדפן של ניווטים עתידיים.
- מאזן נוזלים
- הפעלת סקריפטים בצד הלקוח כדי להוסיף מצב אפליקציה ואינטראקטיביות ל-HTML שעבר עיבוד בצד השרת. ההידרציה מניחה שאין שינוי ב-DOM.
- רענון שרתים
- למרות שלעיתים קרובות משתמשים במונח הזה באותה משמעות כמו הידרציה, הידרציה מחדש מרמזת על עדכון קבוע של ה-DOM עם המצב האחרון, כולל אחרי ההידרציה הראשונית.
ביצועים
- המהירות שבה מגיע בייט התגובה הראשון (TTFB)
- הזמן שעובר בין הלחיצה על קישור לבין טעינת הבייט הראשון של התוכן בדף החדש.
- הצגת תוכן ראשוני (FCP)
- הזמן שבו התוכן המבוקש (גוף המאמר וכו') הופך לגלוי.
- מהירות התגובה לאינטראקציה באתר (INP)
- מדד מייצג שבודק אם הדף מגיב במהירות באופן עקבי לקלט של משתמשים.
- סה"כ זמן חסימה (TBT)
- מדד חלופי ל-INP שמחשב כמה זמן ה-thread הראשי נחסם במהלך טעינת הדף.
רינדור בצד השרת
עיבוד בצד השרת יוצר את ה-HTML המלא של דף בשרת בתגובה לניווט. כך נמנעים סבבים נוספים של הלוך ושוב לשליפת נתונים ויצירת תבניות בצד הלקוח, כי מנוע הרינדור מטפל בהם לפני שהדפדפן מקבל תשובה.
עיבוד בצד השרת בדרך כלל מניב FCP מהיר. הפעלת הלוגיקה של הדף והעיבוד בשרת מאפשרת לכם להימנע משליחת הרבה קוד JavaScript ללקוח. כך אפשר להפחית את ה-TBT של הדף, וגם להוריד את ה-INP, כי ה-thread הראשי לא נחסם לעיתים קרובות במהלך טעינת הדף. כשהשרשור הראשי נחסם פחות, לאינטראקציות של המשתמשים יש יותר הזדמנויות לפעול מהר יותר.
זה הגיוני, כי בעיבוד בצד השרת, אתם בעצם שולחים רק טקסט וקישורים לדפדפן של המשתמש. הגישה הזו יכולה להתאים למגוון תנאים של מכשירים ורשתות, והיא פותחת אפשרויות מעניינות לאופטימיזציה של הדפדפן, כמו ניתוח מסמכים בסטרימינג.
כשמשתמשים בעיבוד בצד השרת, יש פחות סיכוי שהמשתמשים יצטרכו לחכות עד ש-JavaScript שקשור למעבד (CPU) יפעל לפני שהם יוכלו להשתמש באתר. גם אם אתם לא יכולים להימנע משימוש ב-JavaScript של צד שלישי, אתם יכולים להשתמש ברינדור בצד השרת כדי להפחית את עלויות ה-JavaScript שלכם, וכך להשאיר יותר תקציב לשאר הדברים. עם זאת, יש פשרה אפשרית אחת בגישה הזו: יצירת דפים בשרת נמשכת זמן, מה שיכול להגדיל את ה-TTFB של הדף.
התשובה לשאלה אם עיבוד בצד השרת מספיק לאפליקציה שלכם תלויה במידה רבה בסוג חוויית השימוש שאתם יוצרים. יש ויכוח ארוך שנים לגבי היישומים הנכונים של עיבוד בצד השרת לעומת עיבוד בצד הלקוח, אבל תמיד אפשר לבחור להשתמש בעיבוד בצד השרת בדפים מסוימים ולא באחרים. יש אתרים שמשתמשים בטכניקות עיבוד היברידיות בהצלחה. לדוגמה, Netflix מבצעת עיבוד בצד השרת של דפי הנחיתה הסטטיים יחסית שלה, וprefetching את ה-JavaScript של דפים עם הרבה אינטראקציות, כדי שלדפים הכבדים יותר שמעובדים בצד הלקוח יהיה סיכוי טוב יותר להיטען במהירות.
במסגרות, בספריות ובארכיטקטורות מודרניות רבות, אפשר לעבד את אותה אפליקציה גם בצד הלקוח וגם בצד השרת. אתם יכולים להשתמש בשיטות האלה לעיבוד בצד השרת. עם זאת, ארכיטקטורות שבהן העיבוד מתבצע גם בשרת וגם בלקוח הן סוג פתרון משלהן, עם מאפייני ביצועים ופשרות שונים מאוד. משתמשי React יכולים להשתמש בממשקי API של DOM בשרת או בפתרונות שמבוססים עליהם, כמו Next.js, לעיבוד בצד השרת. משתמשי Vue יכולים להשתמש במדריך לעיבוד בצד השרת של Vue או ב-Nuxt. ל-Angular יש את Universal.
עם זאת, רוב הפתרונות הפופולריים משתמשים בצורה כלשהי של הידרציה, אז חשוב לשים לב לגישות שבהן הכלי משתמש.
רינדור סטטי
עיבוד סטטי מתרחש בזמן ה-build. הגישה הזו מאפשרת FCP מהיר, וגם TBT ו-INP נמוכים יותר, כל עוד אתם מגבילים את כמות ה-JavaScript בצד הלקוח בדפים שלכם. בניגוד לעיבוד בצד השרת, הוא גם משיג TTFB מהיר באופן עקבי, כי לא צריך ליצור באופן דינמי את ה-HTML של הדף בשרת. באופן כללי, עיבוד סטטי פירושו יצירת קובץ HTML נפרד לכל כתובת URL מראש. כשיוצרים מראש תשובות ב-HTML, אפשר לפרוס עיבודים סטטיים בכמה רשתות CDN כדי לנצל את היתרונות של שמירה במטמון קצה.
יש הרבה פתרונות לעיבוד סטטי. כלים כמו Gatsby נועדו לתת למפתחים תחושה שהאפליקציה שלהם עוברת עיבוד באופן דינמי, ולא נוצרת כשלב build. כלים ליצירת אתרים סטטיים כמו 11ty, Jekyll ו-Metalsmith מתבססים על האופי הסטטי שלהם ומספקים גישה שמבוססת יותר על תבניות.
אחד החסרונות של עיבוד סטטי הוא שצריך ליצור קובצי HTML נפרדים לכל כתובת URL אפשרית. זה יכול להיות מאתגר או אפילו בלתי אפשרי כשצריך לחזות את כתובות ה-URL האלה מראש, ובאתרים עם מספר גדול של דפים ייחודיים.
משתמשי React אולי מכירים את Gatsby, את הייצוא הסטטי של Next.js או את Navi, שכולם מאפשרים ליצור דפים מרכיבים בקלות. עם זאת, רינדור סטטי ורינדור מראש פועלים באופן שונה: דפים שעברו רינדור סטטי הם אינטראקטיביים בלי צורך להריץ הרבה JavaScript בצד הלקוח, ואילו רינדור מראש משפר את ה-FCP של אפליקציית דף יחיד שצריך להפעיל בצד הלקוח כדי שהדפים יהיו אינטראקטיביים באמת.
אם אתם לא בטוחים אם פתרון מסוים הוא רינדור סטטי או רינדור מראש, נסו להשבית את JavaScript ולטעון את הדף שאתם רוצים לבדוק. בדפים שעברו עיבוד סטטי, רוב התכונות האינטראקטיביות עדיין קיימות בלי JavaScript. יכול להיות שבדפים שעברו עיבוד מראש עדיין יהיו כמה תכונות בסיסיות, כמו קישורים עם JavaScript מושבת, אבל רוב הדף לא פעיל.
בדיקה שימושית נוספת היא להשתמש בהגבלת רוחב פס בכלי הפיתוח ל-Chrome ולראות כמה JavaScript יורד לפני שהדף הופך לאינטראקטיבי. בדרך כלל, כדי שרינדור מראש יהיה אינטראקטיבי, צריך יותר JavaScript, וה-JavaScript הזה נוטה להיות מורכב יותר מהגישה של שיפור הדרגתי (progressive enhancement) שמשמשת ברינדור סטטי.
רינדור בצד השרת לעומת רינדור סטטי
עיבוד בצד השרת הוא לא הפתרון הכי טוב לכל דבר, כי האופי הדינמי שלו יכול לגרום לעלויות תקורה משמעותיות של מחשוב. הרבה פתרונות לעיבוד בצד השרת לא מבצעים פלאש מוקדם, מעכבים את ה-TTFB או מכפילים את הנתונים שנשלחים (לדוגמה, מצבים מוטבעים שמשמשים את JavaScript בצד הלקוח). ב-React,
renderToString() יכול להיות איטי כי הוא סינכרוני ופועל בשרשור יחיד.
ממשקי API חדשים יותר של React server DOM תומכים בסטרימינג, שיכול להעביר את החלק הראשוני של תגובת HTML לדפדפן מהר יותר, בזמן ששאר התגובה עדיין נוצרת בשרת.
כדי לבצע עיבוד בצד השרת בצורה נכונה, יכול להיות שתצטרכו למצוא או ליצור פתרון לשמירת רכיבים במטמון, לנהל את צריכת הזיכרון, להשתמש בטכניקות של שמירת תוצאות של פונקציות ועוד. לעתים קרובות אתם מעבדים או בונים מחדש את אותה אפליקציה פעמיים, פעם אחת בצד הלקוח ופעם אחת בצד השרת. עיבוד בצד השרת שמציג תוכן מוקדם יותר לא בהכרח מפחית את כמות העבודה שצריך לעשות. אם יש לכם הרבה עבודה בצד הלקוח אחרי שתגובת HTML שנוצרה על ידי השרת מגיעה ללקוח, עדיין יכול להיות שהערכים של TBT ו-INP באתר יהיו גבוהים יותר.
עיבוד בצד השרת יוצר HTML על פי דרישה לכל כתובת URL, אבל הוא יכול להיות איטי יותר מהצגת תוכן סטטי שעבר עיבוד. אם אתם יכולים להשקיע קצת יותר מאמץ, עיבוד בצד השרת בשילוב עם שמירת HTML במטמון יכולים להפחית משמעותית את זמן הרינדור בשרת. היתרון של עיבוד בצד השרת הוא היכולת לשלוף יותר נתונים "בזמן אמת" ולהגיב לקבוצה מלאה יותר של בקשות, בהשוואה לעיבוד סטטי. דפים שצריך להתאים אישית הם דוגמה קונקרטית לסוג הבקשה שלא עובד טוב עם עיבוד סטטי.
עיבוד בצד השרת יכול גם להציג החלטות מעניינות כשיוצרים PWA. האם עדיף להשתמש בשמירה במטמון של service worker בדף מלא, או בעיבוד בצד השרת של קטעי תוכן נפרדים?
רינדור בצד הלקוח
עיבוד בצד הלקוח הוא עיבוד של דפים ישירות בדפדפן באמצעות JavaScript. כל הלוגיקה, אחזור הנתונים, התבניות והניתוב מטופלים בצד הלקוח ולא בשרת. התוצאה היא שיותר נתונים מועברים למכשיר של המשתמש מהשרת, וזה כרוך בפשרות משלו.
יכול להיות קשה ליצור עיבוד בצד הלקוח ולשמור על מהירות במכשירים ניידים.
אם תשקיעו קצת מאמץ כדי לשמור על תקציב JavaScript מצומצם ולספק ערך בכמה שפחות בקשות הלוך ושוב, תוכלו לגרום לעיבוד בצד הלקוח לשכפל כמעט את הביצועים של עיבוד בצד השרת בלבד. כדי שהמנתח יעבוד מהר יותר, אפשר להעביר סקריפטים ונתונים קריטיים באמצעות <link rel=preload>. מומלץ גם לשקול להשתמש בתבניות כמו PRPL כדי לוודא שהניווט הראשוני והניווטים הבאים יתבצעו באופן מיידי.
החיסרון העיקרי של עיבוד בצד הלקוח הוא שכמות ה-JavaScript שנדרשת נוטה לגדול ככל שהאפליקציה גדלה, וזה יכול להשפיע על ה-INP של הדף. הבעיה מחמירה כשמוסיפים ספריות JavaScript חדשות, polyfills וקוד של צד שלישי, שמתחרים על כוח העיבוד ולעיתים קרובות צריך לעבד אותם לפני שאפשר לעבד את תוכן הדף.
בחוויות שמשתמשות ברינדור בצד הלקוח ומסתמכות על חבילות JavaScript גדולות, כדאי לשקול פיצול קוד אגרסיבי כדי להפחית את ה-TBT וה-INP במהלך טעינת הדף, וגם טעינה מדורגת של JavaScript כדי להציג רק את מה שהמשתמש צריך, כשהוא צריך את זה. בחוויות עם מעט אינטראקטיביות או ללא אינטראקטיביות בכלל, עיבוד בצד השרת יכול לייצג פתרון שניתן להתאמה לבעיות האלה.
אם אתם מפתחים אפליקציות דף יחיד, זיהוי החלקים העיקריים של ממשק המשתמש שמשותפים לרוב הדפים מאפשר לכם להשתמש בטכניקה של שמירת מעטפת האפליקציה (app shell) במטמון. בשילוב עם קובצי שירות (service workers), זה יכול לשפר באופן משמעותי את הביצועים הנתפסים בביקורים חוזרים, כי הדף יכול לטעון את ה-HTML של מעטפת האפליקציה (app shell) ואת יחסי התלות מ-CacheStorage במהירות רבה.
הידרציה משלבת רינדור בצד השרת ובצד הלקוח
הידרציה היא גישה שמשלבת בין עיבוד בצד הלקוח לבין עיבוד בצד השרת, כדי לצמצם את הפשרות שצריך לעשות כשבוחרים באחת מהשיטות. בקשות ניווט, כמו טעינות או טעינות מחדש של דפים מלאים, מטופלות על ידי שרת שמעבד את האפליקציה ל-HTML. לאחר מכן, ה-JavaScript והנתונים שמשמשים לעיבוד מוטמעים במסמך שנוצר. אם עושים את זה בזהירות, אפשר להשיג FCP מהיר כמו עיבוד בצד השרת, ואז "להמשיך" על ידי עיבוד מחדש בצד הלקוח.
זה פתרון יעיל, אבל יכולים להיות לו חסרונות משמעותיים מבחינת הביצועים.
החיסרון העיקרי של עיבוד בצד השרת עם ריהידרציה הוא שהוא יכול להשפיע לרעה באופן משמעותי על TBT ו-INP, גם אם הוא משפר את ה-FCP. דפים שעברו עיבוד בצד השרת יכולים להיראות כאילו הם נטענו וניתן לבצע בהם פעולות, אבל הם לא יכולים להגיב לקלט עד שהסקריפטים בצד הלקוח של הרכיבים מופעלים וגורמים מטפלים באירועים מצורפים. בנייד, זה יכול לקחת כמה דקות, מה שעלול לבלבל ולתסכל את המשתמש.
בעיה בהתייבשות: אפליקציה אחת במחיר של שתיים
כדי ש-JavaScript בצד הלקוח ימשיך בצורה מדויקת מהמקום שבו השרת הפסיק, בלי לבקש מחדש את כל הנתונים שהשרת עיבד את ה-HTML שלהם, רוב הפתרונות לעיבוד בצד השרת מבצעים סריאליזציה של התגובה מהתלות בנתונים של ממשק המשתמש כתגי סקריפט במסמך. מכיוון שהפעולה הזו משכפלת הרבה HTML, היא עלולה לגרום לבעיות נוספות מעבר לעיכוב באינטראקטיביות.
השרת מחזיר תיאור של ממשק המשתמש של האפליקציה בתגובה לבקשת ניווט, אבל הוא גם מחזיר את נתוני המקור ששימשו ליצירת ממשק המשתמש הזה, ועותק מלא של ההטמעה של ממשק המשתמש שמופעלת לאחר מכן אצל הלקוח. ממשק המשתמש לא הופך לאינטראקטיבי עד ש-bundle.js מסיים את הטעינה וההרצה.
מדדי ביצועים שנאספו מאתרים אמיתיים שמשתמשים בעיבוד בצד השרת ובהידרציה מראים שזו לעיתים רחוקות האפשרות הטובה ביותר. הסיבה הכי חשובה היא ההשפעה על חוויית המשתמש, כשדף נראה מוכן אבל אף אחת מהתכונות האינטראקטיביות שלו לא פועלת.
יש תקווה לעיבוד בצד השרת עם ריהידרציה. בטווח הקצר, שימוש ברינדור בצד השרת רק לתוכן שניתן לשמור במטמון יכול להפחית את ה-TTFB, ולהניב תוצאות דומות לרינדור מראש. יכול להיות שהמפתח להפיכת הטכניקה הזו לישימה יותר בעתיד הוא הידרציה מחדש באופן הדרגתי, פרוגרסיבי או חלקי.
רינדור בצד השרת בסטרימינג ורי-הידרציה הדרגתית
בשנים האחרונות חלו כמה התפתחויות בתחום של עיבוד בצד השרת.
עיבוד בצד השרת בסטרימינג מאפשר לשלוח HTML בחלקים שהדפדפן יכול לעבד בהדרגה כשהם מתקבלים. כך אפשר להעביר את תגי העיצוב למשתמשים מהר יותר, ולשפר את ה-FCP. ב-React, הסטרימינג הוא אסינכרוני ב-renderToPipeableStream(), בהשוואה ל-renderToString() הסינכרוני, מה שאומר שהלחץ האחורי מטופל היטב.
כדאי גם לשקול הידרציה הדרגתית (React הטמיעה את זה). בגישה הזו, חלקים נפרדים של אפליקציה שעברה עיבוד בצד השרת "מופעלים" לאורך זמן, במקום הגישה הנפוצה כרגע של הפעלת האפליקציה כולה בבת אחת. כך אפשר להפחית את כמות ה-JavaScript שנדרשת כדי להפוך דפים לאינטראקטיביים, כי אפשר לדחות את השדרוג בצד הלקוח של חלקים בדף עם עדיפות נמוכה, כדי למנוע חסימה של ה-thread הראשי. כך האינטראקציות של המשתמשים יכולות להתרחש מהר יותר אחרי שהמשתמשים מתחילים אותן.
הידרציה הדרגתית יכולה גם לעזור לכם להימנע מאחת מהמלכודות הנפוצות ביותר של הידרציה ברינדור בצד השרת: עץ DOM שעבר רינדור בצד השרת נהרס ואז נבנה מחדש באופן מיידי, לרוב בגלל שהרינדור הסינכרוני הראשוני בצד הלקוח דרש נתונים שלא היו מוכנים, בדרך כלל Promise שעדיין לא נפתר.
החזרה חלקית של נתונים
התברר שקשה להטמיע ריהידרציה חלקית. הגישה הזו היא הרחבה של ריהידרציה הדרגתית, שמנתחת חלקים נפרדים בדף (רכיבים, תצוגות או עצים) ומזהה את החלקים עם מעט אינטראקטיביות או ללא תגובתיות. בכל אחד מהחלקים הסטטיים האלה, קוד ה-JavaScript התואם הופך להפניות לא פעילות ולתכונות דקורטיביות, וכך טביעת הרגל שלהם בצד הלקוח מצטמצמת כמעט לאפס.
לגישת הריאקטיבציה החלקית יש בעיות וחסרונות משלה. הוא מציב כמה אתגרים מעניינים לשמירה במטמון, וניווט בצד הלקוח אומר שאנחנו לא יכולים להניח ש-HTML שעבר רינדור בשרת לחלקים לא פעילים של האפליקציה זמין בלי טעינת דף מלאה.
רינדור טריזומורפי
אם אתם יכולים להשתמש בסקריפטים מסוג Service Worker, כדאי לשקול עיבוד טריזומורפי. השיטה הזו מאפשרת לכם להשתמש בעיבוד בצד השרת בסטרימינג לניווטים ראשוניים או לניווטים שאינם ב-JavaScript, ואז להגדיר ל-Service Worker לעבד את ה-HTML לניווטים אחרי שהוא הותקן. כך אפשר לעדכן את התבניות והרכיבים שנשמרו במטמון, ולהפעיל ניווט בסגנון SPA כדי להציג תצוגות חדשות באותו סשן. הגישה הזו פועלת בצורה הטובה ביותר כשאתם יכולים לשתף את אותו קוד של תבניות וניתוב בין השרת, דף הלקוח ו-Service Worker.
שיקולי SEO
כשבוחרים אסטרטגיית עיבוד לאתר, הצוותים בדרך כלל שוקלים את ההשפעה של SEO. עיבוד בצד השרת הוא בחירה פופולרית להצגת חוויה "במראה שלם" שסורקים יכולים לפרש. סורקים יכולים להבין JavaScript, אבל לעיתים קרובות יש מגבלות על אופן העיבוד שלהם. עיבוד בצד הלקוח יכול לעבוד, אבל לעיתים קרובות נדרשות בדיקות נוספות ועלויות תקורה. לאחרונה, גם רינדור דינמי הפך לאפשרות שכדאי לשקול אם הארכיטקטורה שלכם תלויה במידה רבה ב-JavaScript בצד הלקוח.
סיכום
כשמחליטים על גישה לעיבוד, צריך למדוד ולהבין מהם צווארי הבקבוק. כדאי לבדוק אם עיבוד סטטי או עיבוד בצד השרת יכולים לעזור לכם להשיג את רוב המטרות. אפשר לשלוח בעיקר HTML עם מעט JavaScript כדי ליצור חוויה אינטראקטיבית. הנה אינפוגרפיקה שימושית שמציגה את הספקטרום של השרת-לקוח:
זיכויים
תודה לכל מי שבדק את המדריך ונתן השראה:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson, and Sebastian Markbåge.