۳ روش ساده برای پیاده سازی رندرینگ سمت سرور در React

25 خرداد 1400, خواندن در 8 دقیقه

در این مقاله قصد پیاده سازی ssr برای دریافت پاسخ (response) HTML ای از سرور برای درخواست (request) URL ای کاربر یا crawler داریم و مابقی درخواست‌ها نیز در سمت کاربر مدیریت خواهند شد.

در ابتدا به چرایی نیاز به این عملکرد می‌پردازیم.

تفاوت csr و ssr چیست؟

در client-side rendering یا همان رندر سمت کاربر، مرورگر شما صفحه‌ی مینیمال ‌HTML ای را دانلود خواهد کرد و سپس جاوا اسکریپت به کامل کردن محتوای صفحه می‌پردازد.

اما در server-side rendering یا همان رندر سمت سرور، کامپوننت‌های ری اکت در سرور رندر خواهند شد و خروجی نهایی HTML ای به کاربر ارسال می‌شود.

با ترکیب این دو رویکرد می‌توانید به ساخت اپ‌های isomorphic بپردازید.

معایب رندر React در سرور

  • SSR باعث افزایش کارایی و پرفورمنس اپ‌های کوچک می‌شود ولی ممکن است در اپ‌های سنگین باعث افت کارایی شود.
  • مدت زمان پاسخ یا ریسپانس را افزایش می‌دهد.
  • حجم فایل‌های پاسخ یا ریسپانس را افزایش می‌دهد و در نتیجه بارگذاری صفحه بیش‌تر طول می‌کشد.
  • پیچیدگی اپ شما را افزایش می‌دهد.

چه زمان از رندرنیگ سمت سرور استفاده کنیم؟

جدای از عواقب یاد شده در بالا برای استفاده از رندرینگ سمت سرور، مواردی وجود دارد که استفاده از آن واجب است:

1- Seo سئو

تمامی وب‌سایت‌ها علاقه‌مندند که در نتایج جست ‌و جوی موتورهای جست و جو نمایش داده شوند. متاسفانه هنوز خزنده (crawler) ‌های موتورهای جست و جو درک درستی از جاوا اسکریپت ندارند و این بدان معناست که آن‌ها صفحات رندر شده در سمت کاربر مانند اپ‌های معمول react را به شکل صفحه ای خالی می‌بینند!

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

برای مثال من اپ خود را بر روی heroku منتشر کرده‌ام و مشاهده می‌کنید که گوگل چگونه سایت من را می‌بیند

و به همین دلیل است که ما باید از سرور ساید رندرنیگ استفاده کنیم؛ مخصوصا برای لندینگ پیج‌ها یا بلاگ‌ها.

برای اطمینان از نحوه‌ی رندر شدن سایت شما توسط گوگل می‌توانید به این بخش بروید:

Search Console Dashboard > Crawl > Fetch as Google

و سپس آدرس مورد نظرتان را وارد کنید و در نهایت دکمه‌ی FETCH AND RENDER را بزنید.

۲- افزایش پرفورمنس

در ssr پرفورمنس اپ شما وابسته به منابع سرورتان و همچنین سرعت شبکه‌ کاربرتان است. این اتفاق برای اپ‌هایی با محتوای سنگین بسیار مفید خواهد بود.

برای درک بهتر این موضوع مثالی میاوریم: تصور کنید یک گوشی موبایل با قیمتی متوسط و اینترنتی با سرعت پایین دارید و قصد مشاهده‌ی سایتی را دارید که نیاز به دانلود کردن ۴ مگابایت داده پیش از نمایش محتوا به شما دارد. در این مورد شما به مدت ۲ تا ۴ ثانیه صفحه‌ای خالی را مشاهده خواهید کرد. پس دیگر رغبتی به بازگشت به این سایت نخواهید داشت.

مورد دیگری که ممکن است برای ما مهم باشد، بهبود در زمان اولین تعامل کاربر با اپ است که مدت زمان لود شدن صفحه را کاهش می‌دهد.

در زیر مقایسه ای که در mac انجام شده است را مشاهده می‌کنید.

react رندر شده در سرور

مدت زمان اولین تعامل ۳۰۰ میلی ثانیه است.

react رندر شده در مرورگر کاربر

مدت زمان اولین تعامل ۴۰۰ میلی ثانیه است.

همان‌طور که دیدید اختلاف ۱۰۰ میلی ثانیه‌ای در مقایسه بالا برای یک اپ بسیار کوچک مشهود است.

مراحل ساخت اپ

  • ساخت store ریداکس جدید برای هر request یا درخواست.
  • Dispatch (ارسال) برخی از action ها.
  • خارج کردن state از store و انجام رندرینگ سمت سرور
  • ارسال استیتی که در مرحله قبل بدست آمد.

از state ارسال شده در ریسپانس برای ساخت state اولیه‌ی سمت کاربر استفاده می‌شود.

قبل از شروع می‌توانید این پروژه را از گیت‌ هاب دانلود کنید.

شروع پیاده سازی اپ

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

npm init --yes

پکیج‌های مورد استفاده در این پروژه که باید نصب کنید را در زیر مشاهده می‌کنید:

شما باید babel و webpack را هم کانفیگ کنید تا اسکریپت build کار کند.

یک فایل جدید با نام babelrc بسازید و قطعه کد زیر را درون آن قرار دهید:

{
  "presets": ["@babel/env", "@babel/react"]
}

وب پک اپ ما و dependency های آن را تبدیل به یک فایل می‌کند. فایلی دیگر با نام webpack.config.js با کد زیر بسازید:

const path = require('path');module.exports = {
    entry: {
        client: './src/client.js',
        bundle: './src/bundle.js'
    },
    output: {
        path: path.resolve(__dirname, 'assets'),
        filename: "[name].js"
    },
    module: {
        rules: [
            { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
        ]
    }
}

حال فرآیند build پروژه ما دو خروجی خواهد داشت:

  • Assets/bundle.js :‌ اپی که کاملا سمت کاربر پردازش می‌شود.
  • Assets/client.js : اپی که به همراه ssr کار خواهد کرد.

فولدر src شامل سورس کد است. فایل‌های کامپایل شده‌ی babel نیز درون views قرار خواهند گرفت. این فولدر به صورت خودکار ساخته می‌شود.

چرا به کامپایل سورس کد نیاز داریم؟

دلیل عمده این کار استفاده از import و export در هنگام کار با ری اکت و ریداکس است و برای قابل استفاده بودن این کلیدواژه‌ها از کامپایل کردن استفاده می‌کنیم. babel در این زمینه به ما کمک می‌کند و اسکریپت زیر به آن اعلام می‌کند که تمامی فایل‌ها را در فولدر src کامپایل کند و در views قرار دهد.

"babel": "babel src -d views",

فایل‌های استاتیک و از پیش نوشته شده را کپی کنید

اگر مخزن گیت‌هاب این پروژه را دانلود کرده‌اید می‌توانید از آن برای این قسمت استفاده کنید یا از این‌جا موارد مورد نیاز را دانلود کنید و فایل دانلود شده را استخراج یا اکسترکت کنید و این سه فولدر را درون اپ خود بگذارید که شامل موارد زیر اند:

  • App و src/components که شامل کامپوننت‌های ری اکتی می‌شوند.
  • فایل‌های ریداکس در src/redux/
  • فایل‌های استاتیک مثل style.css و تصاویر که در assets/ & media/ قرار دارند.

بخش سرور

دو فایل جدید با نام‌های server.js و template.js درون فولدر src بسازید.

1- Src/server.js

عمده‌ی کار ما در این قسمت و قطعه کدی که در پایین مشاهده می‌کنید، انجام می‌گیرد:

import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import App from './components/app';

module.exports = function render(initialState) {
  // Model the initial state  
  const store = configureStore(initialState);
  let content = renderToString(<Provider store={store} ><App /></Provider>);
  const preloadedState = store.getState();
  return {
    content,
    preloadedState
  };
};

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

  • Intialstate را به configurestore() پاس دهید تا نمونه‌ای جدید از store را باز گرداند. آن مقدار را در متغیر store نگه‌ دارید.
  • متد rederToString() را فراخوانی کنید تا ورودی App به‌وجود بیاید. اپ شما در سمت سرور اجرا خواهد شد و HTML تولید شده باز گردانده می‌شود. حال متغیر content فایل HTML را ذخیره خواهد کرد.
  • استیت را از ریداکس با استفاده از getState() گرفته و درون متغیر preloadedState قرار می‌دهیم.
  • Content و preloadedState را return می‌کنیم و آن‌ها را به تمپلیت پاس می‌دهیم تا HTML نهایی ساخته شود.

2- Src/template.js

Template.js فانکشنی را اکسپورت می‌کند که title و state و content را به عنوان ورودی می‌پذیرد و آن‌ها را به تمپلیت تزریق کرده و HTML نهایی را باز می‌گرداند.

تمپلت استیت را  در window.__STATE__ از طریق تگ اسکریپت قرار می‌دهد.

حال می‌توانید مقدار state را در سمت کاربر از طریق این متغیر در دسترس داشته باشید.

علاوه بر این بخش SSR، assets/client.js، را با استفاده تگ اسکریپت دیگری قرار می‌دهیم.

اگر نسخه‌ای که کاملا در سمت کاربر پردازش می‌شود را بخواهید، تنها assets/bundle.js در تگ اسکریپت قرار خواهد گرفت.

بخش کلاینت

بخش سمت کاربر آسان‌تر است.

1- Src/bundle.js

در این بخش تکنیک خاصی پیاده نمی‌شود و اپ سمت کلاینت را پیاده می‌کنیم که توسط provider ریداکس wrap شده است.

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import App from './components/app';

const store = configureStore();
render(
  <Provider store={store} > <App /> </Provider>,
  document.querySelector('#app')
);

2- Src/client.js

در این بخش نیز هیچ کار جدیدی نمی‌کنیم؛ البته به جز استفاده از window.__STATE__. تنها باید مقدار این متغیر را به عنوان state اولیه دریافت کنیم و سپس آن را به فانکشن configureStore() به عنوان state اولیه پاس دهیم.

import React from 'react';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import App from './components/app';

const state = window.__STATE__;
delete window.__STATE__;
const store = configureStore(state);
hydrate(
  <Provider store={store} > <App /> </Provider>,
  document.querySelector('#app')
);
  • Render() را با hydrate() جایگزین می‌کنیم. این فانکشن جدید هم مانند رندر عمل می‌کند و تنها تفاوتش در رندر کردن المنت‌ها با استفاده از ReactDOMServer است که باعث اطمینان حاصل کردن از یکسان بودن محتوای سرور و کلاینت می‌شود.
  • خواندن state از آبجکت یا شی گلوبال window و متغیری که با نام __STATE__ در آن ساخته‌ایم می‌شود. مقدار آن را در یک متغیر ذخیره کنید و window.__STATE__ را حذف کنید.
  • استوری جدید با استفاده از state به عنوان state اولیه بسازید.

جمع بندی

Index.js

این فایل نقطه ورود ما به اپ است و به مدیریت ریکوئست یا درخواست‌ها و تمپلیت کردن می‌پردازد.

همچنین متغیر initialState را تعریف می‌کند. که مدل شده‌ی آن را می‌توانید در assets/data.json مشاهده کنید. ما این مقدار را به فانکشن ssr() پاس می‌دهیم.

روتینگ یا مسیریابی

  • « / » : صفحه‌ی خانه (homepage) که به طور پیش‌فرض رندر از سرور می‌شود.
  • « /clinet » : مثال رندر کاملا سمت کاربر و کلاینت.
  • « /exit » :‌ تنها در حالت توسعه development در دسترس است.

Build & Run

حال می‌توانید با قطعه دستور زیر، از پروژه بیلد بگیرید و اپ را به اجرا در آورید:

npm run build && npm run start

اپ شما باید در پورت ۳۰۰۰ لوکال هاست اجرا شده باشد.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
ابوالفضل باغشاهی @BAbolfazl
Front-End
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو