רינדור של קליינט סייד עם SSR

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

עם כל הפייתון וה-AI, לא יצא לי לכתוב בכלל על פרונט אנד בזמן האחרון. אחד הדברים הכי שימושיים שיצא לי לעסוק בהם זה Server Side Rendering ועשיתי את זה עם next. אז בואו ונסביר מה זה ומי צריך את זה. ראשית, אני ממליץ לקפוץ לפוסט על next שבו אני כותב כמה קל ונעים להשתמש בו.

אז קודם כל – מה זה רינדור קלאסי? בגדול, עם ריאקט, אנגולר, ויו וחבריהם העליזים (בהמשך הפוסט אני כותב ריאקט, אבל הכוונה היא גם לאחרים), הרינדור נעשה באופן כזה – יש קובץ HTML מינימלי מאד הכולל רק קריאות לג׳אווהסקריפט של האפליקציה. כלומר נניח לקבצי הריאקט שיקראו ויטענו את האפליקציה. כלומר לאחר הבילד יש לי קובץ HTML מינימלי וקבצי ג׳אווהסקריפט שנטענים אליו. מי שעובד קשה פה זה הדפדפן של הלקוח שטוען את כל הקבצים ומריץ אותם. קוד הג׳אווהסקריפט כבר יוצר את ה-HTML המוכר.

עם SSR (שזה ראשי תבות של Server Side Rendering) אני בעצם נותן לשרת לעבוד קשה ומעביר לצד הלקוח HTML מן המוכן, כלומר כזה שכבר ריאקט יצר. יכול להיות שיהיו שינויים ב-HTML לאחר הטעינה, אבל הלקוח מקבל HTML מוכן והדפדפן יכול להציג לו את הכל באופן מושלם.

אני אדגים עם view source של אפליקצית ריאקט. אפשר לראות שחוץ מ div יחיד ובודד בערפל אין בו כלום.

HTML דליל מאד שאין בו דבר.

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

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. על כך אכתוב במאמר המשך.

פוסטים נוספים שכדאי לקרוא

פייתון

קבצי קונפיגורציה בפואטרי

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

גלילה לראש העמוד