সার্ভার-সাইড রেন্ডারিং (SSR)
এই পাঠে যা শিখবেন
- SSR-এ রেন্ডারিং কোন ধাপে ঘটে ও কেন সম্পূর্ণ HTML ক্লায়েন্টে পাঠানো হয়
- Python দিয়ে SSR সিমুলেট করে প্রতিটি ইনকামিং রিকোয়েস্টে
render()-এর প্রকৃত ইনভোকেশন গোনা - CSR (L38) ও SSR-এর মধ্যে
render()-এর অবস্থান ও টাইমিং-এর সরাসরি তুলনা - SSR-এর trade-off: দ্রুত প্রথম কন্টেন্ট বনাম প্রতি-রিকোয়েস্ট সার্ভার খরচ
১ · SSR-এ রেন্ডারিং কোথায় ঘটে
সার্ভার-সাইড রেন্ডারিংServer-Side Rendering (SSR)একটি রেন্ডারিং স্ট্র্যাটেজি যেখানে সম্পূর্ণ HTML প্রতিটি রিকোয়েস্টের জন্য সার্ভারে তৈরি হয়, তারপর ক্লায়েন্টে পাঠানো হয়। -তে ব্রাউজার একটি রিকোয়েস্ট পাঠানোর পর সার্ভার নিজেই ডেটাবেস/API থেকে ডেটা আনে, টেমপ্লেটের সাথে সেই ডেটা মিলিয়ে সম্পূর্ণ HTML তৈরি করে, এবং সেই প্রস্তুত HTML স্ট্রিং-টাই HTTP রেসপন্স বডি হিসেবে ক্লায়েন্টে পাঠিয়ে দেয়। ব্রাউজারকে কোনো অতিরিক্ত API কল করে কন্টেন্ট বসাতে হয় না -- যা এসেছে তা ইতিমধ্যেই সম্পূর্ণ।
render() ক্লায়েন্টে কল হয়, প্রতি পেজ-লোডে একবার -- সার্ভার শুধু শেল পাঠায়।render() সার্ভারে কল হয়, প্রতি রিকোয়েস্টে একবার -- ক্লায়েন্ট প্রস্তুত HTML পায়।
CSR ও SSR দুটোতেই render() "প্রতি ঘটনায় ঠিক একবার" কল হয় -- শুধু "ঘটনা" ও "কোথায়" বদলায়।
CSR-এ ঘটনা হলো একটি পেজ-লোড, স্থান ক্লায়েন্ট। SSR-এ ঘটনা হলো একটি ইনকামিং রিকোয়েস্ট, স্থান সার্ভার। যদি L38-এর
মতোই ৩টি পেজ-লোডের সমান ৩টি রিকোয়েস্ট SSR-এ আসত, তাহলেও render() ৩ বারই কল হতো -- শুধু
ক্লায়েন্টে নয়, সার্ভারে।
২ · SSR সিমুলেশন -- প্রতি রিকোয়েস্টে একবার render()
নিচের কোড সেলে একই render(template, data) হেল্পার পুনর্ব্যবহার করা হয়েছে। এবার একটি
handle_incoming_request ফাংশন সার্ভারের ভূমিকা পালন করে -- এটি ডেটাবেস থেকে ডেটা আনে, সাথে সাথে
render() কল করে সম্পূর্ণ HTML তৈরি করে, এবং সেটিকে "রেসপন্স বডি" হিসেবে রিটার্ন করে। ৫টি সিমুলেটেড
ইনকামিং রিকোয়েস্ট চালিয়ে দেখা হয়েছে render() ঠিক কতবার কল হয়।
render_call_log = [] # প্রতিটি render() কল ঠিক কোন রিকোয়েস্টে হলো তার লগ
def render(template, data):
"""L38-এর একই ধারণা -- str.format() ভিত্তিক টেমপ্লেট-রেন্ডার।"""
return template.format(**data)
def db_fetch_product(product_id):
fake_db = {
101: {"name": "ওয়্যারলেস মাউস", "price": 650},
102: {"name": "মেকানিক্যাল কীবোর্ড", "price": 3200},
103: {"name": "USB-C হাব", "price": 1100},
104: {"name": "ল্যাপটপ স্ট্যান্ড", "price": 900},
105: {"name": "ওয়েবক্যাম", "price": 2500},
}
return fake_db[product_id]
template = "<div class='product'><h1>{name}</h1><p>মূল্য: ৳{price}</p></div>"
def handle_incoming_request(request_number, product_id):
print(f"--- রিকোয়েস্ট #{request_number} সার্ভারে এসেছে (GET /product/{product_id}) ---")
print("[server] ডেটাবেস থেকে ডেটা আনা হচ্ছে...")
data = db_fetch_product(product_id)
html = render(template, data)
render_call_log.append(f"রিকোয়েস্ট #{request_number} -- server-এ")
print(f"[server] render() কল হলো -- সম্পূর্ণ HTML তৈরি: {html}")
print(f"[server] এই HTML-ই রেসপন্স বডি হিসেবে ক্লায়েন্টে পাঠানো হলো")
print("[client] ব্রাউজার শুধু এই সম্পূর্ণ HTML প্রদর্শন করে -- এখানে render() আর কল হয় না")
return html
incoming_requests = [101, 102, 103, 104, 105]
for i, pid in enumerate(incoming_requests, start=1):
handle_incoming_request(i, pid)
print()
print("render() কল লগ:")
for entry in render_call_log:
print(f" - {entry}")
print(f"\nমোট render() ইনভোকেশন: {len(render_call_log)}")
print(f"মোট ইনকামিং রিকোয়েস্ট: {len(incoming_requests)}")
print(f"সংখ্যা দুটো সমান কি না: {len(render_call_log) == len(incoming_requests)}")
render_call_log-এ ঠিক ৫টি এন্ট্রি জমা হয়েছে, প্রতিটির লেবেলে "server-এ" লেখা --
L38-এ প্রতিটি এন্ট্রির লেবেল ছিল "client-এ"। ডেটা কাঠামো ও গণনার লজিক দুই পাঠেই হুবহু একই
(একটি লিস্টে এন্ট্রি যোগ করে দৈর্ঘ্য গোনা) -- শুধু render() কোন ফাংশনের ভেতর থেকে, কোন
ট্যাগ-লেবেলের নিচে কল হচ্ছে তা পাল্টেছে। এটাই দেখায় CSR ও SSR-এর মূল পার্থক্য কোড-লজিকে নয়,
স্থানে।
৩ · SSR-এর trade-off
SSR-এ ব্যবহারকারী দ্রুত সম্পূর্ণ কন্টেন্ট দেখতে পায় -- কোনো JS bundle এক্সিকিউট হওয়া বা আলাদা API কলের জন্য
অপেক্ষা করতে হয় না, কারণ সার্ভার নিজেই সেই কাজ আগে থেকে সেরে ফেলে। কিন্তু এর মূল্য হলো: প্রতিটি রিকোয়েস্টে
সার্ভারকে নতুন করে ডেটা আনতে হয় ও render() কল করতে হয় -- অর্থাৎ যদি একই পেজ ১০০০ বার রিকোয়েস্ট
হয়, সার্ভারকে ১০০০ বার এই পুরো কাজ করতে হবে, এমনকি ডেটা কিছুই না পাল্টালেও। L40-এ আমরা দেখব SSG কীভাবে সেই
পুনরাবৃত্তি এড়ায়।
SSR-এ render() কল হয় সার্ভারে, প্রতিটি ইনকামিং রিকোয়েস্টে ঠিক একবার -- ক্লায়েন্ট সবসময় একটি
সম্পূর্ণ, প্রস্তুত HTML স্ট্রিং পায়, নিজে কখনো রেন্ডার করে না। CSR-এর সাথে তুলনায় শুধু অবস্থান
(ক্লায়েন্ট → সার্ভার) ও ঘটনা (পেজ-লোড → রিকোয়েস্ট) পাল্টেছে, "প্রতি ঘটনায় ঠিক একবার" নিয়মটি
একই থেকে গেছে। L40-এ দেখব কীভাবে এই নিয়মটিও ভাঙা যায় -- render() কে প্রতি-রিকোয়েস্ট থেকে সরিয়ে একবারই
কল করে ক্যাশ করে রাখা যায়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন -- তারপর "→ উত্তর" চাপুন।
প্র ০১
উপরের কোডে ক্লায়েন্ট কেন কখনো db_fetch_product ফাংশনটি নিজে কল করে না?
কারণ SSR-এ ডেটাবেস অ্যাক্সেস সম্পূর্ণভাবে সার্ভারের দায়িত্ব -- ক্লায়েন্ট শুধু চূড়ান্ত, প্রস্তুত HTML স্ট্রিং পায় (রিটার্ন ভ্যালু), যেখানে ডেটা আগে থেকেই বসানো আছে। ক্লায়েন্টের কাছে ডেটা আনার বা রেন্ডার করার কোনো দায়িত্ব বা কোডই নেই।
প্র ০২
L38 (CSR)-এ ৩টি পেজ-লোডে render() ৩ বার কল হয়েছিল। এই পাঠে ৫টি রিকোয়েস্টে কতবার কল হলো,
এবং সংখ্যা দুটো তুলনা করার সময় কী বিবেচনা করা উচিত?
এখানে ৫ বার কল হয়েছে -- সংখ্যাটি ভিন্ন কারণ এই পাঠে ভিন্ন সংখ্যক ঘটনা (৫টি রিকোয়েস্ট, ৩টি পেজ-লোড নয়)
সিমুলেট করা হয়েছে। তুলনা করার সময় গুরুত্বপূর্ণ বিষয়টি সংখ্যার সমতা নয় -- বরং এই সত্য যে দুটো ক্ষেত্রেই
render() "প্রতি ঘটনায় ঠিক একবার" কল হয়, শুধু ঘটনা ও অবস্থান ভিন্ন।
প্র ০৩
যদি একই product_id (যেমন ১০১) নিয়ে ১০০ বার রিকোয়েস্ট আসে, SSR-এ render()
কতবার কল হবে -- এবং এটি কি অপ্রয়োজনীয় মনে হয়?
১০০ বার -- কারণ SSR-এ প্রতিটি রিকোয়েস্ট, ডেটা একই থাকলেও, নতুন করে সম্পূর্ণ কাজ (ডেটা আনা +
render()) করায়। ডেটা না পাল্টালে এই পুনরাবৃত্তি অপ্রয়োজনীয় মনে হতে পারে -- ঠিক এই সমস্যাটিই
L40-এর SSG স্ট্র্যাটেজি সমাধান করে, একবার রেন্ডার করে ফলাফল ক্যাশ করে রেখে।
অনুশীলন
-
চিন্তা করুন: কোন ধরনের পেজের জন্য SSR সবচেয়ে বেশি মানানসই মনে হয় -- যার কন্টেন্ট
ব্যবহারকারী-ভেদে সবসময় আলাদা (যেমন একটি ড্যাশবোর্ড), নাকি যার কন্টেন্ট সবার জন্য একই ও কমই পাল্টায় (যেমন
একটি ব্লগ পোস্ট)? কেন?
SSR সবচেয়ে বেশি মূল্য দেয় যেখানে কন্টেন্ট ব্যবহারকারী-ভেদে বা রিকোয়েস্ট-ভেদে সত্যিই পাল্টায় (যেমন একটি পার্সোনালাইজড ড্যাশবোর্ড) -- কারণ তখন প্রতিবার নতুন করে রেন্ডার করাটাই যুক্তিসঙ্গত। যদি কন্টেন্ট সবার জন্য একই ও কমই পাল্টায় (যেমন একটি ব্লগ পোস্ট), তাহলে প্রতি রিকোয়েস্টে একই কাজ বারবার করা অপচয় -- সেখানে SSG (L40) বেশি উপযুক্ত।
-
পরীক্ষা করুন: উপরের কোড সেলে
incoming_requests-এ101-কে দুইবার রাখুন (যেমন[101, 101, 102, 103, 104, 105]), Run চাপুন, এবং দেখুনrender_call_log-এর দৈর্ঘ্য এখন কত এবং কেন সেই সংখ্যাটিই যুক্তিসঙ্গত।দৈর্ঘ্য ৬ হবে -- কারণ SSR-এ
product_idএকই হলেও প্রতিটি রিকোয়েস্ট আলাদাভাবেhandle_incoming_requestকল করে, যা প্রতিবারইdb_fetch_productওrender()নতুন করে চালায়। SSR ডেটার ভিত্তিতে ফলাফল "মনে রাখে" না -- এটাই দেখায় কেন পুনরাবৃত্ত রিকোয়েস্টের জন্য SSR ব্যয়বহুল হতে পারে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ L40 স্ট্যাটিক সাইট জেনারেশন (SSG) -- render() মাত্র একবার বিল্ড টাইমে কল হয়, ১০০০ রিকোয়েস্ট ক্যাশ থেকে সার্ভ হয়।
- আগের পাঠ আবার দেখুন L38 CSR-এ render() ক্লায়েন্টে কীভাবে কল হয় তা আবার দেখে এই পাঠের সাথে তুলনা করুন।
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks -- সব এক জায়গায়।