یکی از تصمیمات اصلی که توسعهدهندگان وب باید بگیرند این است که منطق و رندرینگ را در کجای برنامه خود پیادهسازی کنند. این میتواند دشوار باشد زیرا روشهای زیادی برای ساخت یک وبسایت وجود دارد.
درک ما از این فضا، حاصل کار ما در کروم و صحبت با سایتهای بزرگ در چند سال گذشته است. به طور کلی، ما توسعهدهندگان را تشویق میکنیم که رندر سمت سرور یا رندر استاتیک را به رویکرد بازیابی کامل دادهها ترجیح دهند.
برای درک بهتر معماریهایی که هنگام تصمیمگیری از بین آنها انتخاب میکنیم، به اصطلاحات منسجم و یک چارچوب مشترک برای هر رویکرد نیاز داریم. سپس، میتوانید از منظر عملکرد صفحه، مزایا و معایب هر رویکرد رندرینگ را بهتر ارزیابی کنید.
اصطلاحات
ابتدا، برخی از اصطلاحاتی را که استفاده خواهیم کرد، تعریف میکنیم.
رندرینگ
- رندر سمت سرور (SSR)
- رندر کردن یک برنامه روی سرور برای ارسال HTML، به جای جاوا اسکریپت، به کلاینت.
- رندر سمت کلاینت (CSR)
- رندر کردن یک برنامه در مرورگر، با استفاده از جاوا اسکریپت برای تغییر DOM.
- پیشرندرینگ
- اجرای یک برنامه سمت کلاینت در زمان ساخت برای ثبت حالت اولیه آن به عنوان HTML استاتیک.
- هیدراتاسیون
- اجرای اسکریپتهای سمت کلاینت برای اضافه کردن وضعیت برنامه و تعامل به HTML رندر شده توسط سرور. Hydration فرض میکند که DOM تغییر نمیکند.
- آبرسانی مجدد
- اگرچه اغلب به معنای مشابه هیدراتاسیون استفاده میشود، اما رهیدراسیون به معنای بهروزرسانی منظم DOM با آخرین وضعیت، از جمله پس از هیدراتاسیون اولیه است.
عملکرد
- زمان رسیدن به اولین بایت (TTFB)
- زمان بین کلیک روی یک لینک و بارگذاری اولین بایت محتوا در صفحه جدید.
- اولین رنگ محتوایی (FCP)
- زمانی که محتوای درخواستی (متن مقاله و غیره) قابل مشاهده میشود.
- تعامل با رنگ بعدی (INP)
- یک معیار نماینده که ارزیابی میکند آیا یک صفحه به طور مداوم به ورودیهای کاربر سریع پاسخ میدهد یا خیر.
- کل زمان مسدود کردن (TBT)
- یک معیار پروکسی برای INP که محاسبه میکند چه مدت نخ اصلی در طول بارگذاری صفحه مسدود شده است.
رندر سمت سرور
رندر سمت سرور، HTML کامل یک صفحه را در سرور در پاسخ به ناوبری تولید میکند. این کار از رفت و برگشتهای اضافی برای واکشی دادهها و قالببندی در سمت کلاینت جلوگیری میکند، زیرا رندرکننده قبل از دریافت پاسخ توسط مرورگر، آنها را مدیریت میکند.
رندر سمت سرور معمولاً FCP سریعی ایجاد میکند. اجرای منطق صفحه و رندر آن روی سرور به شما امکان میدهد از ارسال مقدار زیادی جاوا اسکریپت به کلاینت جلوگیری کنید. این به کاهش TTBT صفحه کمک میکند، که میتواند منجر به INP پایینتر نیز شود، زیرا رشته اصلی در طول بارگذاری صفحه به دفعات مسدود نمیشود. وقتی رشته اصلی کمتر مسدود شود، تعاملات کاربر فرصتهای بیشتری برای اجرا سریعتر دارند.
این منطقی است، زیرا با رندر سمت سرور، شما در واقع فقط متن و لینکها را به مرورگر کاربر ارسال میکنید. این رویکرد میتواند برای انواع دستگاهها و شرایط شبکه به خوبی کار کند و بهینهسازیهای جالبی را برای مرورگر، مانند تجزیه و تحلیل اسناد استریمینگ، فراهم کند.

با رندرینگ سمت سرور، احتمال کمتری وجود دارد که کاربران قبل از استفاده از سایت شما، منتظر اجرای جاوا اسکریپت وابسته به CPU بمانند. حتی زمانی که نمیتوانید از جاوا اسکریپت شخص ثالث اجتناب کنید، استفاده از رندرینگ سمت سرور برای کاهش هزینههای جاوا اسکریپت شخص ثالث میتواند بودجه بیشتری برای بقیه به شما بدهد. با این حال، یک معامله بالقوه با این رویکرد وجود دارد: تولید صفحات روی سرور زمان میبرد، که میتواند TTFB صفحه شما را افزایش دهد.
اینکه آیا رندر سمت سرور برای برنامه شما کافی است یا خیر، تا حد زیادی به نوع تجربهای که ایجاد میکنید بستگی دارد. بحث طولانی مدتی در مورد کاربردهای صحیح رندر سمت سرور در مقابل رندر سمت کلاینت وجود دارد، اما همیشه میتوانید برای برخی صفحات از رندر سمت سرور استفاده کنید و برای برخی دیگر نه. برخی سایتها تکنیکهای رندر ترکیبی را با موفقیت به کار گرفتهاند. به عنوان مثال، نتفلیکس صفحات فرود نسبتاً استاتیک خود را رندر سرور میکند، در حالی که جاوا اسکریپت را برای صفحات سنگین تعاملی از قبل واکشی میکند و به این صفحات رندر شده توسط کلاینت سنگینتر، شانس بیشتری برای بارگیری سریع میدهد.
با وجود بسیاری از چارچوبها، کتابخانهها و معماریهای مدرن، میتوانید یک برنامه را هم روی کلاینت و هم روی سرور رندر کنید. میتوانید از این تکنیکها برای رندر سمت سرور استفاده کنید. با این حال، معماریهایی که رندر هم روی سرور و هم روی کلاینت اتفاق میافتد، کلاس راهحل خود را با ویژگیهای عملکردی و بدهبستانهای بسیار متفاوت دارند. کاربران React میتوانند از APIهای DOM سرور یا راهحلهای ساخته شده روی آنها مانند Next.js برای رندر سمت سرور استفاده کنند. کاربران Vue میتوانند از راهنمای رندر سمت سرور Vue یا Nuxt استفاده کنند. Angular دارای Universal است.
با این حال، اکثر راهحلهای محبوب از نوعی هیدراتاسیون استفاده میکنند، بنابراین از رویکردهایی که ابزار شما استفاده میکند، آگاه باشید.
رندر استاتیک
رندر استاتیک در زمان ساخت اتفاق میافتد. این رویکرد، FCP سریع و همچنین TBT و INP پایینتری را ارائه میدهد، البته تا زمانی که میزان جاوا اسکریپت سمت کلاینت را در صفحات خود محدود کنید. برخلاف رندر سمت سرور، این روش همچنین به TTFB پیوسته و سریعی دست مییابد، زیرا HTML یک صفحه لازم نیست به صورت پویا روی سرور تولید شود. به طور کلی، رندر استاتیک به معنای تولید یک فایل HTML جداگانه برای هر URL از قبل است. با پاسخهای HTML که از قبل تولید میشوند، میتوانید رندرهای استاتیک را در چندین CDN مستقر کنید تا از ذخیره سازی لبهای (edge caching) بهره ببرید.

راهحلهای رندرینگ استاتیک در اشکال و اندازههای مختلفی وجود دارند. ابزارهایی مانند Gatsby به گونهای طراحی شدهاند که توسعهدهندگان احساس کنند برنامهشان به صورت پویا رندر میشود، نه اینکه به عنوان یک مرحله ساخت تولید شود. ابزارهای تولید سایت استاتیک مانند 11ty ، Jekyll و Metalsmith ماهیت استاتیک خود را پذیرفته و رویکردی مبتنی بر الگو ارائه میدهند.
یکی از معایب رندر استاتیک این است که باید برای هر URL ممکن، فایلهای HTML جداگانه تولید کند. این امر میتواند چالش برانگیز یا حتی غیرممکن باشد، به خصوص زمانی که نیاز به پیشبینی آن URLها از قبل دارید و برای سایتهایی با تعداد زیادی صفحه منحصر به فرد.
کاربران React ممکن است با Gatsby، Next.js static export یا Navi آشنا باشند که همه آنها ایجاد صفحات از کامپوننتها را راحت میکنند. با این حال، رندر استاتیک و پیشرندرینگ رفتار متفاوتی دارند: صفحات رندر شده استاتیک بدون نیاز به اجرای زیاد جاوا اسکریپت سمت کلاینت، تعاملی هستند، در حالی که پیشرندرینگ، FCP یک برنامه تک صفحهای را که باید روی کلاینت بوت شود تا صفحات واقعاً تعاملی باشند، بهبود میبخشد.
اگر مطمئن نیستید که راهحل ارائه شده رندر استاتیک است یا پیشرندر، جاوا اسکریپت را غیرفعال کنید و صفحهای را که میخواهید آزمایش کنید بارگذاری کنید. برای صفحات رندر استاتیک، اکثر ویژگیهای تعاملی بدون جاوا اسکریپت نیز وجود دارند. صفحات پیشرندر شده ممکن است هنوز برخی از ویژگیهای اساسی مانند پیوندها را با غیرفعال کردن جاوا اسکریپت داشته باشند، اما بیشتر صفحه بیاثر است.
یک آزمایش مفید دیگر، استفاده از کنترل سرعت شبکه در Chrome DevTools و مشاهده میزان دانلود جاوا اسکریپت قبل از تعاملی شدن یک صفحه است. پیشرندر معمولاً برای تعاملی شدن به جاوا اسکریپت بیشتری نیاز دارد و جاوا اسکریپت معمولاً پیچیدهتر از رویکرد بهبود تدریجی مورد استفاده در رندر استاتیک است.
رندر سمت سرور در مقابل رندر استاتیک
رندر سمت سرور بهترین راه حل برای همه چیز نیست، زیرا ماهیت پویای آن میتواند هزینههای سربار محاسباتی قابل توجهی داشته باشد. بسیاری از راهحلهای رندر سمت سرور، زود خالی نمیشوند، TTFB را به تأخیر نمیاندازند یا دادههای ارسالی را دو برابر نمیکنند (برای مثال، حالتهای درونخطی که توسط جاوا اسکریپت در کلاینت استفاده میشوند). در React، renderToString() میتواند کند باشد زیرا همزمان و تکرشتهای است. APIهای DOM جدیدتر سرور React از استریمینگ پشتیبانی میکنند که میتواند بخش اولیه یک پاسخ HTML را زودتر به مرورگر برساند در حالی که بقیه آن هنوز در سرور تولید میشود.
رندرینگ سمت سرور «درست» میتواند شامل یافتن یا ساختن راهحلی برای ذخیرهسازی کامپوننت ، مدیریت مصرف حافظه، استفاده از تکنیکهای Memoization و سایر موارد باشد. شما اغلب یک برنامه را دو بار، یک بار روی کلاینت و یک بار روی سرور، پردازش یا بازسازی میکنید. رندرینگ سمت سرور که محتوا را زودتر نشان میدهد، لزوماً کار کمتری برای انجام دادن به شما نمیدهد. اگر پس از رسیدن پاسخ HTML تولید شده توسط سرور به کلاینت، کار زیادی روی کلاینت داشته باشید، این امر همچنان میتواند منجر به TBT و INP بالاتر برای وبسایت شما شود.
رندر سمت سرور، HTML را بر اساس تقاضا برای هر URL تولید میکند، اما میتواند کندتر از ارائه محتوای رندر شده استاتیک باشد. اگر بتوانید زحمت اضافی بکشید، رندر سمت سرور به همراه ذخیرهسازی HTML میتواند زمان رندر سرور را به میزان قابل توجهی کاهش دهد. مزیت رندر سمت سرور، توانایی دریافت دادههای "زنده" بیشتر و پاسخ به مجموعهای کاملتر از درخواستها نسبت به رندر استاتیک است. صفحاتی که نیاز به شخصیسازی دارند، نمونه بارزی از نوع درخواستی هستند که با رندر استاتیک به خوبی کار نمیکنند.
رندر سمت سرور همچنین میتواند هنگام ساخت یک PWA تصمیمات جالبی ارائه دهد. آیا بهتر است از ذخیرهسازی تمام صفحه توسط سرویس ورکر استفاده کنیم یا از رندر سرور برای تک تک محتوا؟
رندر سمت کلاینت
رندر سمت کلاینت به معنای رندر کردن صفحات مستقیماً در مرورگر با جاوا اسکریپت است. تمام منطق، واکشی دادهها، قالببندی و مسیریابی به جای سرور، روی کلاینت انجام میشود. نتیجهی مؤثر این است که دادههای بیشتری از سرور به دستگاه کاربر منتقل میشود و این با مجموعهای از بدهبستانهای خاص خود همراه است.
رندر سمت کلاینت میتواند برای دستگاههای تلفن همراه دشوار باشد و سرعت آن را حفظ کند. با کمی تلاش برای محدود کردن بودجه جاوا اسکریپت و ارائه ارزش در کمترین زمان ممکن، میتوانید رندر سمت کلاینت را تقریباً به عملکردی مشابه رندر سمت سرور خالص تبدیل کنید. میتوانید با ارائه اسکریپتها و دادههای حیاتی با استفاده از <link rel=preload> تجزیهگر را سریعتر برای خود به کار بگیرید. ما همچنین توصیه میکنیم از الگوهایی مانند PRPL استفاده کنید تا مطمئن شوید که پیمایشهای اولیه و بعدی فوری به نظر میرسند.

عیب اصلی رندرینگ سمت کلاینت این است که با رشد برنامه، مقدار جاوا اسکریپت مورد نیاز نیز افزایش مییابد که میتواند بر INP صفحه تأثیر بگذارد. این امر به ویژه با اضافه شدن کتابخانههای جدید جاوا اسکریپت، polyfillها و کدهای شخص ثالث که برای قدرت پردازش رقابت میکنند و اغلب باید قبل از رندر شدن محتوای صفحه پردازش شوند، دشوارتر میشود.
تجربههایی که از رندرینگ سمت کلاینت استفاده میکنند و به بستههای بزرگ جاوا اسکریپت متکی هستند، باید تقسیم کد تهاجمی را برای کاهش TBT و INP در حین بارگذاری صفحه، و همچنین بارگذاری تنبل جاوا اسکریپت را برای ارائه فقط آنچه کاربر نیاز دارد، در زمانی که به آن نیاز دارد، در نظر بگیرند. برای تجربههایی با تعامل کم یا بدون تعامل، رندرینگ سمت سرور میتواند یک راه حل مقیاسپذیرتر برای این مشکلات باشد.
برای افرادی که برنامههای تک صفحهای میسازند، شناسایی بخشهای اصلی رابط کاربری که توسط اکثر صفحات به اشتراک گذاشته شده است، به شما امکان میدهد تکنیک ذخیرهسازی پوسته برنامه را اعمال کنید. این روش در ترکیب با سرویس ورکرها، میتواند عملکرد مشاهده شده در بازدیدهای مکرر را به طرز چشمگیری بهبود بخشد، زیرا صفحه میتواند HTML پوسته برنامه و وابستگیهای آن را خیلی سریع از CacheStorage بارگیری کند.
Rehydration رندر سمت سرور و سمت کلاینت را ترکیب میکند
هیدراتاسیون رویکردی است که با انجام هر دو، بدهبستان بین رندر سمت کلاینت و سمت سرور را کاهش میدهد. درخواستهای ناوبری، مانند بارگذاری کامل صفحه یا بارگذاری مجدد، توسط سروری که برنامه را به HTML رندر میکند، مدیریت میشوند. سپس جاوا اسکریپت و دادههای مورد استفاده برای رندر در سند حاصل جاسازی میشوند. هنگامی که با دقت انجام شود، به یک رندر سمت سرور سریع مانند FCP دست مییابد، سپس با رندر مجدد روی کلاینت، "بهبود" مییابد.
این یک راه حل مؤثر است، اما میتواند معایب عملکردی قابل توجهی داشته باشد.
عیب اصلی رندر سمت سرور با استفاده از rehydration این است که میتواند تأثیر منفی قابل توجهی بر TBT و INP داشته باشد، حتی اگر FCP را بهبود بخشد. صفحات رندر شده سمت سرور میتوانند بارگذاری شده و تعاملی به نظر برسند، اما تا زمانی که اسکریپتهای سمت کلاینت برای اجزا اجرا نشوند و event handlerها متصل نشوند، نمیتوانند به ورودی پاسخ دهند. در موبایل، این کار میتواند چند دقیقه طول بکشد و کاربر را گیج و کلافه کند.
مشکل کمآبی بدن: یک اپلیکیشن به قیمت دو اپلیکیشن
برای اینکه جاوا اسکریپت سمت کلاینت بتواند به طور دقیق از جایی که سرور متوقف شده بود، بدون درخواست مجدد تمام دادههایی که سرور HTML خود را با آنها رندر کرده بود، ادامه دهد، اکثر راهحلهای رندر سمت سرور، پاسخ را از وابستگیهای دادهای رابط کاربری به عنوان تگهای اسکریپت در سند سریالی میکنند. از آنجا که این کار مقدار زیادی HTML را کپی میکند، بازیابی مجدد میتواند مشکلات بیشتری نسبت به تأخیر در تعامل ایجاد کند.

سرور در پاسخ به درخواست ناوبری، توصیفی از رابط کاربری برنامه را برمیگرداند، اما همچنین دادههای منبع مورد استفاده برای ساخت آن رابط کاربری و یک کپی کامل از پیادهسازی رابط کاربری را که سپس روی کلاینت بوت میشود، برمیگرداند. رابط کاربری تا زمانی که بارگذاری و اجرای bundle.js تمام نشده باشد، تعاملی نمیشود.
معیارهای عملکردی جمعآوریشده از وبسایتهای واقعی با استفاده از رندرینگ و بازیابی اطلاعات سمت سرور نشان میدهد که این روش به ندرت بهترین گزینه است. مهمترین دلیل، تأثیر آن بر تجربه کاربری است، زمانی که یک صفحه آماده به نظر میرسد اما هیچ یک از ویژگیهای تعاملی آن کار نمیکند.

امیدی برای رندر سمت سرور با استفاده از rehydration وجود دارد. در کوتاه مدت، فقط استفاده از رندر سمت سرور برای محتوای با قابلیت ذخیره سازی بالا میتواند TTFB را کاهش دهد و نتایج مشابهی با prerendering ایجاد کند. rehydration به صورت تدریجی، پیشرونده یا جزئی ممکن است کلید عملیتر شدن این تکنیک در آینده باشد.
رندر سمت سرور را استریم کنید و به تدریج آن را آبرسانی کنید
رندرینگ سمت سرور در طول چند سال گذشته پیشرفتهای زیادی داشته است.
رندرینگ سمت سرور به صورت استریمینگ به شما امکان میدهد HTML را در بخشهایی ارسال کنید که مرورگر میتواند به تدریج هنگام دریافت، آن را رندر کند. این میتواند نشانهگذاری را سریعتر به کاربران شما برساند و سرعت FCP شما را افزایش دهد. در React، استریمها در renderToPipeableStream() ناهمگام هستند، در مقایسه با renderToString() همگام، به این معنی است که فشار برگشتی به خوبی مدیریت میشود.
همچنین میتوان به رهیدراسیون پیشرونده (Progressive rehydration) نیز توجه کرد ( React آن را پیادهسازی کرده است ). با این رویکرد، بخشهای جداگانه یک برنامه رندر شده توسط سرور، به مرور زمان "بوت" میشوند، به جای رویکرد رایج فعلی که کل برنامه را به طور همزمان مقداردهی اولیه میکند. این میتواند به کاهش میزان جاوا اسکریپت مورد نیاز برای تعاملی کردن صفحات کمک کند، زیرا به شما امکان میدهد ارتقاء سمت کلاینت بخشهای کماهمیت صفحه را به تعویق بیندازید تا از مسدود شدن ترد اصلی جلوگیری شود و به تعاملات کاربر اجازه میدهد زودتر از شروع آنها توسط کاربر اتفاق بیفتد.
رهیدراسیون پیشرونده همچنین میتواند به شما کمک کند تا از یکی از رایجترین مشکلات رهیدراسیون رندر سمت سرور اجتناب کنید: یک درخت DOM رندر شده توسط سرور نابود میشود و سپس بلافاصله بازسازی میشود، که اغلب به این دلیل است که رندر همزمان اولیه سمت کلاینت به دادههایی نیاز داشت که کاملاً آماده نبودند، اغلب یک Promise که هنوز حل نشده است.
آبرسانی جزئی
پیادهسازی روش بازیابی جزئی منابع (rehydration) دشوار بوده است. این رویکرد، توسعهای از بازیابی تدریجی منابع (progressive rehydration) است که بخشهای مجزای صفحه (کامپوننتها، نماها یا درختها) را تجزیه و تحلیل میکند و بخشهایی را که تعامل کمی دارند یا اصلاً واکنشپذیر نیستند، شناسایی میکند. برای هر یک از این بخشهای عمدتاً ایستا، کد جاوا اسکریپت مربوطه به ارجاعات بیاثر و ویژگیهای تزئینی تبدیل میشود و ردپای آنها را در سمت کلاینت تقریباً به صفر میرساند.
رویکرد بازیابی جزئی منابع، مسائل و محدودیتهای خاص خود را دارد. این رویکرد چالشهای جالبی را برای ذخیرهسازی (caching) ایجاد میکند و پیمایش سمت کلاینت به این معنی است که نمیتوانیم فرض کنیم که HTML رندر شده توسط سرور برای بخشهای غیرفعال برنامه، بدون بارگذاری کامل صفحه، در دسترس هستند.
رندر سهریختی
اگر سرویس ورکرها برای شما یک گزینه هستند، رندر سهریختی (trisomorphic rendering) را در نظر بگیرید. این تکنیک به شما امکان میدهد از رندرینگ سمت سرور برای ناوبریهای اولیه یا غیر جاوا اسکریپتی استفاده کنید و سپس سرویس ورکرهای شما پس از نصب، رندرینگ HTML را برای ناوبریها بر عهده بگیرند. این میتواند کامپوننتها و قالبهای ذخیره شده را بهروز نگه دارد و ناوبریهای به سبک SPA را برای رندر کردن نماهای جدید در همان جلسه فعال کند. این رویکرد زمانی بهترین عملکرد را دارد که بتوانید کد قالببندی و مسیریابی یکسانی را بین سرور، صفحه کلاینت و سرویس ورکرها به اشتراک بگذارید.

ملاحظات سئو
هنگام انتخاب استراتژی رندر وب، تیمها اغلب تأثیر سئو را در نظر میگیرند. رندر سمت سرور یک انتخاب محبوب برای ارائه یک تجربه "کاملاً شبیه" است که خزندهها میتوانند آن را تفسیر کنند. خزندهها میتوانند جاوا اسکریپت را درک کنند ، اما اغلب محدودیتهایی در نحوه رندر آنها وجود دارد. رندر سمت کلاینت میتواند کار کند، اما اغلب به آزمایش و سربار اضافی نیاز دارد. اخیراً، رندر پویا نیز به گزینهای تبدیل شده است که اگر معماری شما به شدت به جاوا اسکریپت سمت کلاینت وابسته است، ارزش بررسی دارد.
وقتی شک دارید، ابزار تست سازگاری با موبایل راهی عالی برای آزمایش این است که آیا رویکرد انتخابی شما همان چیزی را که انتظار دارید انجام میدهد یا خیر. این ابزار پیشنمایشی بصری از نحوه نمایش هر صفحه برای خزنده گوگل، محتوای HTML سریالی شدهای که پس از اجرای جاوا اسکریپت پیدا میکند و هرگونه خطایی که هنگام رندر شدن با آن مواجه میشوید را نشان میدهد.

نتیجهگیری
هنگام تصمیمگیری در مورد رویکرد رندرینگ، گلوگاههای خود را اندازهگیری و درک کنید. در نظر بگیرید که آیا رندرینگ استاتیک یا رندرینگ سمت سرور میتواند شما را تا حد زیادی به هدفتان برساند. برای داشتن یک تجربه تعاملی، اشکالی ندارد که HTML را عمدتاً با حداقل جاوا اسکریپت ارائه دهید. در اینجا یک اینفوگرافیک مفید وجود دارد که طیف سرور-کلاینت را نشان میدهد:

اعتبارات {:#اعتبارات}
با تشکر از همه برای نظرات و الهاماتشان:
جفری پوسنیک، حسین جیرده، شوبهی پانیکر، کریس هارلسون و سباستین مارکبگه.