עם כל הפייתון וה-AI, לא יצא לי לכתוב בכלל על פרונט אנד בזמן האחרון. אחד הדברים הכי שימושיים שיצא לי לעסוק בהם זה Server Side Rendering ועשיתי את זה עם next. אז בואו ונסביר מה זה ומי צריך את זה. ראשית, אני ממליץ לקפוץ לפוסט על next שבו אני כותב כמה קל ונעים להשתמש בו.
אז קודם כל – מה זה רינדור קלאסי? בגדול, עם ריאקט, אנגולר, ויו וחבריהם העליזים (בהמשך הפוסט אני כותב ריאקט, אבל הכוונה היא גם לאחרים), הרינדור נעשה באופן כזה – יש קובץ HTML מינימלי מאד הכולל רק קריאות לג׳אווהסקריפט של האפליקציה. כלומר נניח לקבצי הריאקט שיקראו ויטענו את האפליקציה. כלומר לאחר הבילד יש לי קובץ HTML מינימלי וקבצי ג׳אווהסקריפט שנטענים אליו. מי שעובד קשה פה זה הדפדפן של הלקוח שטוען את כל הקבצים ומריץ אותם. קוד הג׳אווהסקריפט כבר יוצר את ה-HTML המוכר.
עם SSR (שזה ראשי תבות של Server Side Rendering) אני בעצם נותן לשרת לעבוד קשה ומעביר לצד הלקוח HTML מן המוכן, כלומר כזה שכבר ריאקט יצר. יכול להיות שיהיו שינויים ב-HTML לאחר הטעינה, אבל הלקוח מקבל HTML מוכן והדפדפן יכול להציג לו את הכל באופן מושלם.
אני אדגים עם view source של אפליקצית ריאקט. אפשר לראות שחוץ מ div יחיד ובודד בערפל אין בו כלום.
נסתכל עכשיו על HTML שמגיע מנקסט. אפשר לראות שיש בו את המבנה המלא של האפליקציה. למרות שמדובר באותו קוד ריאקטי.
למה זה טוב? זה כמובן מוריד דרמטית את הזמן שבו לקוח מחכה שהאפליקציה תטען ותיצוק את ה-HTML בעצם לפי הקומפוננטות. באתרים כבדים ובאפליקציות כבדות זה לפעמים לוקח הרבה זמן במיוחד אם ללקוח יש מכשיר קצה חלש. זמן שאפשר לטשטש עם אנימצית טעינה אבל זה זמן. שנית – SEO. למרות שמנועי חיפוש יודעים לעבוד עם ג׳אווהסקריפט, יותר טוב ונעים להם לעבוד עם HTML מן המוכן.
בואו ונראה איך זה עובד בפועל. הנה למשל אפליקצית create react app שיצרתי בדמו. האפליקציה היא כבדה למדי:
import React, { useState, useEffect } from "react";
const ComponentA = (): JSX.Element => {
const [renderTime, setRenderTime] = useState(0);
useEffect(() => {
const start = performance.now();
// Simulating a heavy rendering component
for (let i = 0; i < 1000000000; i++) {
// Perform some computations
}
const end = performance.now();
const time = end - start;
setRenderTime(time);
}, []);
return <div>Component A Render Time: {renderTime}ms</div>;
};
const ComponentB = (): JSX.Element => {
const [renderTime, setRenderTime] = useState(0);
useEffect(() => {
const start = performance.now();
// Simulating a heavy rendering component
for (let i = 0; i < 1000000000; i++) {
// Perform some computations
}
const end = performance.now();
const time = end - start;
setRenderTime(time);
}, []);
return <div>Component B Render Time: {renderTime}ms</div>;
};
const App = (): JSX.Element => {
return (
<div>
<h1>Rendering Time Measurement</h1>
<ComponentA />
<ComponentB />
</div>
);
};
export default App;
אם אני אפתח אותה, אפילו ב-dev mode. אני אראה כמה דברים מעניינים. ראשית אני אראה, בטעינה ראשונה עד שהקומפוננטות נכנסות לפעולה, שהזמן הוא 0. אחרי זה אני אראה כמה זמן לקח לרנדר אותן. שזה המון 😱
הנה הטעינה הראשונה, ללא cache:
והנה הזמן שזה לקח:
אם אני מרנדר את זה בצד השרת. אני יכול לחסוך לא מעט זמן יקר של הלקוח. אם יש לי חלקים מסוימים באפליקציה שאני רוצה שיטענו בשיטה הקלאסית, אני אציב בתחילת הקוד של הקומפוננטה שמכילה את הקומפוננטות שאני רוצה שירונדרו על גבי הדפדפן באמצעות:
'use client';
נשמע להיט, לא? אז בואו ונעבור כולנו לצד השרת. אבל רגע רגע רגע. זה עלול להיות פחות טוב כי אם השרת חלש או עמוס (דבר שיכול לקרות כמובן). אם אנחנו מפילים את הרינדור על צד השרת והוא לא משהו. אנחנו נהיה ממש בבעיה.
אני אדגים באמצעות אפליקצית next js שרצה על שרת ישן יחסית. ראשית, יצרתי את האפליקציה עם:
npx create-next-app next-rendering-time
אני אצור תיקית pages ואכניס לשם index.tsx עם הקוד הזה:
import React from "react";
const Home = ({ serverRenderTime }: { serverRenderTime: number }): JSX.Element => {
return (
<div>
<h1>Rendering Time Measurement</h1>
<div>Server Render Time: {serverRenderTime}ms</div>
</div>
);
};
export async function getServerSideProps() {
const start = process.hrtime();
// Simulating a heavy rendering operation on the server side
for (let i = 0; i < 1000000000; i++) {
// Perform some computations
}
const end = process.hrtime(start);
const serverRenderTime = (end[0] * 1000 + end[1] / 1000000).toFixed(2); // Convert to milliseconds and round to 2 decimal places
return {
props: {
serverRenderTime: parseFloat(serverRenderTime), // Convert back to a number
},
};
}
export default Home;
אם השרת חלש, אני אראה שלא חסכתי הרבה זמן במקרה הטוב ובמקרה הרע… לפעמים זה יותר איטי.
אני מקווה שהדגמתי כאן קצת מהעקרונות של SSR מול רינדור קלאסי ועל כך ש-SSR הוא לא כדור כסף. הוא יכול להועיל בנקודות מסוימות אבל להיות לא יעיל בנקודות אחרות. לפי דעתי צריך לשקול במה להשתמש ואפשר לפתור חלק מהחולשות עם SSG -שזה רינדור מראש של התוצר ורק אחרי כן הגשתו ללקוח מתוך cache. על כך אכתוב במאמר המשך.
12 תגובות
המטוטלת נעה הכיוון השני.
לפני שנות ה 90 , הכל היה "צד שרת".
בשנות ה 90 , שרת-לקוח (תוכנת חלונות אל מול DB)
בסוף שנות ה ה 90 , שוב צד שרת (ימי תחילת האינטרנט – קליינט דפדפן "טיפש")
מאמצע/סוף העשור הראשון של המיליניום – RIA – rich internet application + mobile apps – שוב שרת לקוח (אמנם בעיקר דפדפן שיוצר פניות WS לשרת על מנת לאחזר נתונים והצגת בדפדפן)
עכשיו כניסה להרבה מערכות שעם Server side render לוקחות למשתמש פחות משניה , לוקחות שניות ארוכות כי יש טעינה ראשונית איטית ואז חוזרים לצד שרת.
בקיצור , ארכיטקטורה של מערכת צריכה להיות לפי הצרכים ולא לפי הטרנדים.
השרת SSR עדיין רינדר לך ברבע מהזמן הרגיל. איפה החסרון פה?
בבדיקה עם המחשב הביתי – כן? אבל זה לא תמיד ככה. זה משתנה בין שרת לשרת וגם בעומס.
חזי, אני מסכים עם הגישה הכללית, אבל לפני שנות ה 90 , לא הכל היה "צד שרת".
להזכירך IBM PC יצא ב 1981, כמה מאיתנו עוד זוכרים את זה 😉
הי כשאתה משתמש ב getServerSideProps אין client component ו server component הכל זה client. תעשה npx create-next-app@latest ותנסה app-route שם יש לך הבדלים בין server ל client (דרך אגב ברשת נטען ש server component יותר איטי)
לא הבנתי. getServerSideProps זה SSR וכתוב כך במפורש בדוקומנטציה וגם אני רואה את זה ב-network. איפה טעיתי?
אולי אני לא הבנתי את מה שאתה רצית להדגים בגלל הפסקה על use client.
בעיקרון SSR ו RSC (react server components) הם שני דברים שונים, אין use client כשאתה משתמש ב pages route כי הכל זה client, זה לא סותר את העובדה שיש רינדור בצד השרת, ואז הוא מתרנדר שוב בצד לקוח (נו, ריאקט…).
נ.ב. מרגיש לי שאתה לא מעודכן בהמלצות העדכניות של ריאקט. לא אדביק לך פה לינקים, אבל תיכנס לריאקט.דב להוראות של התחלת פרויקט חדש, הם ממש נגד CRA או VITE
ידעתי שהם נגד CRA אבל דווקא על Vite הדעה הרווחת שאפשר להשתמש, לא? 🙂
אולי הייתי יכול להסביר יותר טוב – בגדול בדוגמה ממש רואים את הרינדור שיש גם בצד לקוח יחד עם צד שרת (ה-HTML מרונדר בצד השרת אבל קומפוננטות ספציפיות בצד לקוח). אולי בפוסט ההמשך אני אצליח להבהיר.
ממש מתנצל אבל אני לא רואה כלום… יש כאן אפליקציה ריאקטית פשוטה של צד לקוח, מול אפליקציה של צד שרת. בתוך הדברים יש איזה קטע לא במקום על use client (שאין לו משמעות כשאתה משתמש בתיקיית pages).
בפועל קראתי כותרת על "רינדור של קליינט סייד עם SSR" ולא ראיתי כלום על זה. (חשוב לי להדגיש אני מנסה סה"כ להבין, אני מאוד נהנה מהתכנים של האתר, והם עזרו לי הרבה להתקדם)
חלילה, אין מה להתנצל – להיפך! אתה נותן לי מתנה מאד גדולה כי או שלא הבנתי (זה יכול לקרות) או שלא הסברתי טוב. בקריאה שלישית נראה שלא ניסחתי טוב. אני אנסה לנסח שוב בערב היום כדי שיהיה מובן. אתה רוצה שנמשיך במייל? 🙂 אני מאד אשמח וגם כדי לתת קרדיט. כי זה חשוב.
אשמח לכתוב לך. לאיזה כתובת? (כעת השארתי מייל בתגובה, אני מקוה שזה לא מוצג במקום ציבורי)
כתבתי לך למייל info bar-zik.com