Snapp.ir ‌v2 solution design [SSR without server]

دقیق‌تر بگم Isomorphic با رندرترون. حالا داستان از کجا شروع شد؟

نیاز بیزنس:

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


تیم فنی:

سولوشن مدنظر ما SSR با یه کش معقول. جلسه تموم شد، رفتیم سراغ پیاده سازی نمونه اولیه که ببینیم عملی هست یا نه. عملی نبود :) چرا؟

چون لایبری یو‌آی-کیت اسنپ برای اجرا روی Node طراحی نشده بود :/ یه کم تلاش کردیم که اون جاهایی که توی node ارور میداد رو یه جور دیگه بنویسیم، یه کاری کنیم که روی node بدون مشکل اجرا شه یوآی کیت و بتونیم Nextjs رو بیاریم بالا ولی خیلی دردسر داشت، تغییرات بنیادی لازم بود در حد دوباره نوشتن یوآی کیت از اول. اما چون پلن داشتیم که یوآی کیت ورژن ۲ رو در کوارتر های بعدی بنویسم که خیلی خفن تره، سبک تر، تم-دارک اینا باشه و البته سرور-ساید فرندلی، فعلا بیخیال تغییر ورژن ۱ شدیم و دیگه به ناچار Next (کلا سولوشن هایی که روی Node اجرا میشد و متدهای window رو نداشت) کنار گذاشتیم و رفتیم در جستجو برای سولوشن‌های احتمالی بعدی.


تنها گزینه با شرایط موجود همون CSR یا کلاینت-ساید-رندرینگ بود که وب سایت اسنپ هم یه SPA دیگه بشه برای خودش. برای رفع دشواری سئو در سایت های SPA هم گفتیم که از این سولوشن‌های داینامیک رندرینگ ( rendertron یا prerender.io ) استفاده میکنیم که برای کراولرها صفحه رو استاتیک رندر میکنه و خود گوگل هم تایید کرده بود که مشکلی نداره. رفتیم برای پیاده سازی.

داینامیک رندرینگ
داینامیک رندرینگ


به یه مشکل جدید برخوردیم،. برای اجرای این روش میخواستیم بر اساس User-Agent رکوئست رو تفکیک و هدایت کنیم به سمت رندرترون یا پادهای فرانت تو کلاود که SPA رو سرو میکردن. ولی وب سایت اسنپ پشت آروان بود با یه کش نیم ساعته و کراولرها هم از از اونجا میبینن سایت رو.

از اونجایی ما میخواستیم assetهای متفاوتی بر اساس هدر یوزر ایجنت داشته باشیم باید یه جوری به آروان میگفتیم که روی این هدر خاص کش پالیسی متفاوتی داریم و مثلا اگه گوگل بات اومد، از کش بهش جواب نده، مستقیم بفرسته به کلاود تا ما از رندرترون جوابش رو بدیم، بدون کش، ریسپانس فرش، استاتیک، سئو عالی :)

برای این کار میشه از هدر Vary استفاده کرد، که میاد به کش کلاینت‌ها میگه که اون مقداری که داخل Vary هست رو هم جز کلید-کش قرار بده. اما... vary گذاشتن رو یوزر-ایجینت تقریبا مثل اینه که کلا کش رو غیر فعال کنید. چرا؟

به عنوان مثال فرق این دوتا یوزر ایجنت فقط توی نسخه‌ی iOS هست

Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1

و

Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1

یعنی تقریبا هر دستگاهی یوزر ایجنت خاص خودش رو داره. بیلیون ترکیب مختلف از ورژن انواع دستگاه و نسخه‌ی بروزر و سیستم‌عامل و ...

از اونجایی که رکوئست های اسنپ آی ار خیلی بالاست و کش آروان کمک بزرگی هست در کم کردن بار کلاود، این روش دانامیک رندرینگ هم به خاطر معماری زیرساخت عملا قابل پیاده سازی نبود.


تصمیم بر این شد که همون SPA بزنیم و بیخیال بهینه‌سازی استایتک برا سئو بشیم. ولی این سولوشنی نبود که خیلی دلچسب باشه برامون،‌ کلاس فنی لازم رو نداشت :(

ولی چپتر لید فرانت متعقد بود که میتونیم سورس کد رندرترون رو دستکاری کنیم جوری که فیت نیاز ما بشه، و همین طوری هم بود. چون رندرترون بای دیفالت اسکریت تگ رو حذف میکنه از نتیجه ( فیچر رکوئست آپشنال شدن ) و یه بیس تگ میذاره که لینک ها absolute بشن. که این برای ما دشواری ساز شد بود. چون ما میخواستیم فقط پری-رندر رو داخل کلاود انجام بدیم و بقیه اینترکشن‌های کاربر با جاواسکریپت سمت کلاینت هندل بشه ( مدل Isomorphic ). اینطوری دیگه نیازی به دوتا ورژن مختلف نبود، یکی برای کاربر یکی برای کراولرها. نتیجه رو هم میتونستیم براحتی روی آروان کش کنیم، سئوی بسیار خوبی داره و کاربران عادی هم فواید از ‌صفحات استاتیک-رندر شده بهره میبردند.

خلاصه یه چرخی داخل سورس کد رندرترون زدیم و کاستومایزش‌هایی که لازم داشتیم رو اعمال کردیم و دادیم به دواپس که یه چندتا پاد بیاره بالا ازش داخل کلاود.

قدم بعدی این بود که تمام رکوئست رو از رندرترون رد میکردیم و نتیجه استاتیک رو برگردونیم که راحت کش بشه. که با یه پراکسی-پس تو NGINX انجام شد.

ترسیم ساده‌ شده ی پیاده‌سازی نهایی
ترسیم ساده‌ شده ی پیاده‌سازی نهایی


یه چندتا نکته‌ی امنیتی و best-practice داره این روش در پیاده سازی رندرترون و حالت‌های race condition که ممکنه برای کش کردن نتیجه‌اش هنگام دیپلوی ورژن جدید پیش بیاد و نیازمندی کش انجین ایکس برای جلوی گیری از DDoS هست که چالش‌های بزرگی نبودند و با مشورت تیم سکوریتی هاردن کردیم.


خلاصه برای کسایی که علاقه مند هستند:

  • اتک رندرترون که هاردن کردن اش با استفاده از renderOnly و urlPattern در کانفیگ هست.
  • ریس-کاندیشن های کش در دیپلوی و جلوگیری از دی-داس، جفتش با کش انجینیکس قابل حله، فقط رندرترون نباید از بیرون قابل دیدن باشه و روی اون location یی که توی انجینکیس کانفیگ کردید کشه انجین ایکسی بذارید.


درنهایت

نتیجه‌ی این سولوشن دیزاین و پیاده‌سازیش رو میتونید روی https://snapp.ir مشاهده کنید. هر پیجی رو که رکوئست بدید یک ریزالت سرور-ساید رندر شده تحویل میگیرید، بعد از اون جاواسکریپت لایبری فرانت ( preact ) لود میشه و اپلیکیشن Hydrate و اینترکتیو میشه. از این لحظه به بعد تمام اینترکشن‌های صفحه با کلایت هست و کلایت-ساید روتینگ ( با react-router ) که روت متناظرش lazy-load میشه و صفحه‌ی جدید رندر میشه.

curl headers, homepage snapp.ir
curl headers, homepage snapp.ir


و از لحاظ عملکرد هم نتیجه‌ی لایت هوس روی هوم پیج دیزاین جدید

اگر تمایل داشتید در ادامه، پارت-۲، یه مقاله دیگه درباره‌ی جزئیات پیاده سازیش و نحوه‌ی کانفیگ کردن هم برای کسایی که علاقه مند بودن مینویسم.