React Server Components নীতিগতভাবে সহজ আর বাস্তবে বিভ্রান্তিকর, আর বিভ্রান্তিটা প্রায় সবসময় একই: সীমারেখাটা কোথায়, আর আমি কোন দিকে আছি?

এটা পরিষ্কার হয়ে গেলে বাকি সব আপনাআপনি আসে। তাই আর্কিটেকচার দিয়ে নয়, এখান থেকেই শুরু।

এক বাক্যের সংস্করণ

Server Component সার্ভারে চলে, HTML বানায়, আর তার জাভাস্ক্রিপ্ট কখনোই ব্রাউজারে পাঠায় না।

পুরো সুবিধাটা এটাই। "সার্ভার রেন্ডারিং" নয় — সেটা React বছরের পর বছর ধরেই করছে। নতুন অংশটা হলো কম্পোনেন্টের কোড সার্ভারেই থেকে যায়। ৬০ কিলোবাইটের লাইব্রেরি দিয়ে তারিখ ফরম্যাট করা একটি কম্পোনেন্ট Client Component হিসেবে আপনার বান্ডলে ৬০ কিলোবাইট যোগ করে, আর Server Component হিসেবে কিছুই না

বাকি সবকিছুই ওই একটি সত্যের পরিণতি।

useState ব্যবহার করতে পারেন না কেন

কারণ কম্পোনেন্টটি ইতিমধ্যে চলে গেছে, শেষ হয়েছে, আর HTML পাঠিয়ে দিয়েছে। স্টেট ধরে রাখার মতো কোনো কম্পোনেন্ট ব্রাউজারে বসে নেই।

একই যুক্তি Server Component-এ যা যা কাজ করে না তার পুরো তালিকাটাই ব্যাখ্যা করে:

  • useState, useReducer — স্টেট ধরে রাখার কিছু নেই
  • useEffect — যুক্ত হওয়ার মতো কোনো ব্রাউজার লাইফসাইকেল নেই
  • onClick আর অন্য প্রতিটি ইভেন্ট হ্যান্ডলার — শোনার কেউ নেই
  • window, document, localStorage — কোনো ব্রাউজার নেই
  • Context প্রোভাইডার — দেওয়ার মতো কেউ নেই

আর সেই অনুযায়ী, Server Component-এ যা পারেন আর Client Component-এ পারেন না:

  • কম্পোনেন্টের শরীরেই সরাসরি await
  • ডেটাবেসে কোয়েরি
  • ফাইল, এনভায়রনমেন্ট ভেরিয়েবল আর গোপন তথ্য পড়া
  • বড় লাইব্রেরি ব্যবহার, বান্ডলে শূন্য খরচে

এই প্রতিসাম্যটাই পুরো মানসিক মডেল। Server Components ইন্টার‌্যাক্টিভিটির বিনিময়ে ডেটা অ্যাক্সেস আর শূন্য বান্ডল ওজন দেয়।

App Router-এ Server-ই ডিফল্ট

পুরোনো React থেকে আসা মানুষ এখানেই হোঁচট খান: আপনি অন্য কিছু না বললে প্রতিটি কম্পোনেন্টই Server Component। 'use client' লিখে ব্রাউজারে যাওয়ার সিদ্ধান্ত নিতে হয়।

tsx
// একটি Server Component। কোনো ডিরেক্টিভ লাগে না।
export default async function ArticleList() {
  const articles = await db.article.findMany()   // সরাসরি ডেটাবেস অ্যাক্সেস
  return <ul>{articles.map(a => <li key={a.id}>{a.title}</li>)}</ul>
}
tsx
'use client'
import { useState } from 'react'

export default function Counter() {
  const [n, setN] = useState(0)
  return <button onClick={() => setN(n + 1)}>{n}</button>
}

প্রথমটিতে কী হারিয়ে গেল লক্ষ করুন: কোনো getServerSideProps নেই, কোনো এপিআই রুট নেই, ক্লায়েন্ট থেকে কোনো fetch নেই, কোনো লোডিং স্টেট নেই, কোনো useEffect নেই। এক দশক ধরে React অ্যাপ্লিকেশনে যে ডেটা আনার আনুষ্ঠানিকতা চলছিল, তা সংকুচিত হয়ে await-এ পরিণত হয়।

যে নিয়মটি প্রায় সবকিছুর সমাধান করে

'use client' একটি সীমারেখা চিহ্নিত করে, একটি কম্পোনেন্ট নয়।

Client Component যা কিছু ইমপোর্ট করে তার সবই ক্লায়েন্ট কোড হয়ে যায়, একেবারে নিচ পর্যন্ত। ট্রির ওপরের দিকে একটি কম্পোনেন্ট চিহ্নিত করলে গোটা ট্রি ব্রাউজার বান্ডলে ঢুকে যায় আর পুরো সুবিধাটাই চুপচাপ হারিয়ে যায়।

App Router প্রকল্পে সবচেয়ে ব্যয়বহুল ভুল এটাই, আর এটি অদৃশ্য — কিছুই ভাঙে না, অ্যাপটি কেবল দরকারের চেয়ে অনেক বেশি জাভাস্ক্রিপ্ট পাঠায়।

তাই: 'use client'-কে ট্রির যতটা নিচে নেওয়া যায় ততটা নিচে নামান। পেজ নয় — বোতাম। লেআউট নয় — ড্রপডাউন।

যে ধাঁচটি এটিকে বাস্তবসম্মত করে: Client Component এখনো Server Component রেন্ডার করতে পারে, যদি সেগুলো ইমপোর্ট না হয়ে children হিসেবে আসে।

tsx
// Server Component — সার্ভারেই থাকে
<InteractiveTabs>
  <ExpensiveServerRenderedContent />
</InteractiveTabs>

InteractiveTabs একটি Client Component, যা ট্যাবের স্টেট সামলায়। এর ভেতরের কনটেন্ট সার্ভারে রেন্ডার হয়েছে আর ইতিমধ্যে রেন্ডার হওয়া চাইল্ড হিসেবে পার হয়ে এসেছে। ট্যাবের লজিক যায়; কনটেন্ট যায় না।

এটা একবার মাথায় বসে গেলে "এই গোটা পেজটাকেই ক্লায়েন্ট কম্পোনেন্ট বানাতে হবে" ধরনের বেশিরভাগ সমস্যাই মিলিয়ে যায়।

সীমারেখা কী পার হয়

Server Component থেকে Client Component-এ পাঠানো প্রপগুলোকে সিরিয়ালাইজযোগ্য হতে হবে, কারণ সেগুলো আক্ষরিক অর্থেই সিরিয়ালাইজ হয়ে নেটওয়ার্ক দিয়ে যায়।

পার হয়

পার হয় না

স্ট্রিং, সংখ্যা, বুলিয়ান, null

ফাংশন

সাধারণ অবজেক্ট ও অ্যারে

ক্লাস ইনস্ট্যান্স

Date, Map, Set

Symbol

JSX (children হিসেবে)

ক্লোজার ধরে রাখে এমন যেকোনো কিছু

ফাংশনের নিষেধাজ্ঞাটাই কামড় বসায়। Client Component-এ কলব্যাক পাঠাতে পারবেন না — একটি গুরুত্বপূর্ণ ব্যতিক্রমসহ: Server Actions, যেগুলো 'use server' চিহ্নিত ফাংশন আর পাঠানো যায়, কারণ আসলে যা পার হয় তা ফাংশনটি নয়, বরং একটি রেফারেন্স — যা ক্লায়েন্ট নেটওয়ার্ক দিয়ে ফিরে ডাকে।

যে ভুলগুলোর দাম সবচেয়ে বেশি

১. ট্রির ওপরে 'use client' ওপরে বলা হয়েছে। বড় ভুলটা এটাই।

২. Server Components সবসময় দ্রুত ধরে নেওয়া। এগুলো কাজটা সার্ভারে সরায়। ধীর কিছু করা Server Component ব্রাউজারের বদলে আপনার সার্ভারকেই ধীর করে, আর এখন প্রতিটি ভিজিটর সেটির জন্য অপেক্ষা করেন। সার্ভার রেন্ডারিং বিনামূল্যে নয়, স্থানান্তরিত

৩. জলপ্রপাতের মতো ডেটা আনা। নেস্ট করা Server Component-এ পরপর await আপনার রিকোয়েস্টগুলোকে সারিবদ্ধ করে ফেলে। স্বাধীন যেকোনো কিছুর জন্য Promise.all ব্যবহার করুন — পুরোনো ক্লায়েন্ট-সাইড দুনিয়ার চেয়ে এখানে ভুলটা করা সহজ, কারণ পড়তে এত স্বাভাবিক লাগে।

৪. লোডিং স্টেট ভুলে যাওয়া। ধীর ডেটার জন্য অপেক্ষা করা Server Component সমাধান না হওয়া পর্যন্ত গোটা পেজ আটকে রাখে। একে ফলব্যাকসহ <Suspense>-এ মুড়ে দিন, বাকি পেজ সঙ্গে সঙ্গে রেন্ডার হবে।

৫. গোপন তথ্য ফাঁস। Server Component-এ এনভায়রনমেন্ট ভেরিয়েবল পাওয়া যায়। সেটিকে প্রপ হিসেবে Client Component-এ পাঠালে সেটি এখন ব্রাউজারে। React অনেক ক্ষেত্রে সতর্ক করবে; সব ক্ষেত্রে ধরবে না।

কখন কোনটি নেবেন

Server Component — ডিফল্ট, আর আপনার অ্যাপ্লিকেশনের বেশিরভাগ:

  • ডেটা আনে এমন যেকোনো কিছু
  • স্ট্যাটিক কনটেন্ট, লেআউট, শিরোনাম
  • বড় ফরম্যাটিং বা পার্সিং লাইব্রেরি ব্যবহার করে এমন যেকোনো কিছু
  • গোপন তথ্য বা ডেটাবেস ছোঁয় এমন যেকোনো কিছু

Client Component — কেবল যখন সত্যিই দরকার:

  • স্টেট, ইভেন্ট হ্যান্ডলার, ইফেক্ট
  • ব্রাউজার এপিআই
  • ভেতরে হুক ব্যবহার করে এমন তৃতীয় পক্ষের কম্পোনেন্ট
  • ব্যবহারকারীর সাড়ায় অ্যানিমেট হয় এমন যেকোনো কিছু

কাজের একটি সূত্র: ব্যবহারকারীর সাড়ায় সাড়া না দিলে সেটির Client Component হওয়ার দরকার নেই।

এর আসল দাম কত

জয়ের আকার নিয়ে সৎ থাকা যাক।

সত্যিকারের উন্নতি যখন: আপনার অ্যাপ কনটেন্ট-নির্ভর, বান্ডল বড় হয়ে গেছে, প্রচুর ডেটা আনছেন, বা এমন একগাদা useEffect জমেছে যাদের একমাত্র কাজ জিনিস লোড করা।

সামান্য যখন: আপনার অ্যাপ এমন একটি ইন্টার‌্যাক্টিভ ড্যাশবোর্ড, যেখানে এমনিতেই প্রায় সবই Client Component। সীমারেখার জটিলতাটা পাবেন, লাভের বেশিরভাগ পাবেন না।

ব্যয়বহুল যখন: আপনার টিমের কাছে এটি নতুন। মানসিক মডেলটি সত্যিই আলাদা, আর ব্যর্থতার চেহারা — কাজ করা একটি অ্যাপ যা বেশি জাভাস্ক্রিপ্ট পাঠায় — না মাপলে অদৃশ্য। আচরণ নয়, বান্ডলের আকার দেখুন।

কনটেন্ট-নির্ভর বেশিরভাগ সাইটে ক্লায়েন্ট জাভাস্ক্রিপ্টের হ্রাস উল্লেখযোগ্য, আর সেটি Core Web Vitals-এ ভালো INP হিসেবে দেখা যায় — কারণ চালানোর মতো স্ক্রিপ্টই কম। নিজে না করে হাতে তুলে দিতে চাইলে আমরা এই কাজটাই করি।

ঠিকঠাক করেছেন কি না যাচাই

bash
# আসলে কোন কম্পোনেন্টগুলো ক্লায়েন্ট কম্পোনেন্ট?
grep -rl "use client" ./app ./components | wc -l

# আর আপনি কত বড় বান্ডল পাঠাচ্ছেন?
next build   # First Load JS কলামটি পড়ুন

দুটি জিনিস দেখবেন। প্রায় সবকিছুই যদি Client Component হয়, সীমারেখাটা ভুল জায়গায়। আর First Load JS যদি রিলিজে রিলিজে বাড়তে থাকে, ট্রির ওপরের দিকে কোথাও একটি 'use client' জুটে গেছে — ওই সংখ্যাটাই সৎ স্কোরবোর্ড, আর্কিটেকচারটি আপনার জন্য কিছু করছে কি না তার।

Server Actions, সংক্ষেপে

মডেলের বাকি অর্ধেক, আর যে অংশটি এটিকে ব্যবহারযোগ্য করে তোলে।

Server Action হলো 'use server' চিহ্নিত একটি ফাংশন, যা Client Component স্থানীয় ফাংশনের মতো ডাকতে পারে — কিন্তু যা চলে সার্ভারে:

tsx
// actions.ts
'use server'
export async function subscribe(formData: FormData) {
  const email = String(formData.get('email'))
  await db.subscriber.create({ data: { email } })
}
tsx
<form action={subscribe}>
  <input name="email" type="email" required />
  <button>Subscribe</button>
</form>

কোনো এপিআই রুট নেই, fetch নেই, JSON নেই, ক্লায়েন্ট-সাইড লোডিং স্টেট নেই। জাভাস্ক্রিপ্ট লোড হওয়ার আগেই ফর্মটি কাজ করে, কারণ এটি সত্যিকারের একটি ফর্ম যা সত্যিকারের একটি এন্ডপয়েন্টে পোস্ট করে — পরিশ্রম করে নয়, ডিফল্টেই পাওয়া প্রোগ্রেসিভ এনহ্যান্সমেন্ট।

দুটি বিষয়ে সতর্ক থাকা দরকার:

Server Action একটি উন্মুক্ত HTTP এন্ডপয়েন্ট। Next.js এটির জন্য একটি URL বানায়। যে কেউ যেকোনো আর্গুমেন্ট দিয়ে এটি ডাকতে পারেন। প্রতিটি ইনপুট যাচাই করুন আর অনুমোদন পরীক্ষা করুন অ্যাকশনটির ভেতরেই — আপনার ইউআই কেবল বৈধ বিকল্প দেয় বলে ভরসা করবেন না, কারণ আক্রমণকারী ইউআই ব্যবহার করেন না।

লেখার পর রিভ্যালিডেট করুন। পাতাটি নিজে থেকে নতুন হবে না। অ্যাকশনের শেষে revalidatePath বা revalidateTag ডাকুন, নইলে আপনার ব্যবহারকারী ফর্ম জমা দিয়ে বসে থাকবেন আর কিছুই বদলাবে না।

পড়ার জন্য Server Components আর লেখার জন্য Server Actions — এই দুইয়ের মাঝে সাধারণ একটি অ্যাপ্লিকেশনে ডেটাবেস আর ইউআইয়ের মধ্যেকার যে এপিআই স্তরটি থাকত, সেটি বেশিরভাগটাই মিলিয়ে যায়। এই আর্কিটেকচারের আসল উৎপাদনশীলতার গল্প এটাই, আর বান্ডলের আকারের যুক্তির চেয়ে এটি বড় ব্যাপার।

মডেলটি কোথা থেকে এল

একটু ইতিহাস, কারণ এতে নকশার সিদ্ধান্তগুলো অর্থবহ হয়ে ওঠে।

React শুরু হয়েছিল ক্লায়েন্ট-সাইড লাইব্রেরি হিসেবে। সবকিছু চলত ব্রাউজারে, আর সার্ভারের কাজ ছিল একটি খালি পাতা আর একটি বান্ডল পাঠানো। তার আগে যা ছিল তার তুলনায় এটি সত্যিকারের উন্নতি ছিল, আর এতে কাঠামোগত একটি সমস্যা ছিল: আপনি যে সক্ষমতাই যোগ করুন, সেটি প্রতিটি ভিজিটরের কাছে পাঠাতে হতো।

শিল্পের উত্তরগুলো এসেছে ক্রমান্বয়ে। সার্ভার-সাইড রেন্ডারিং ফাঁকা প্রথম পেইন্টের সমস্যা মেটাল, কিন্তু তবু সবই পাঠাত — কারণ পাতাটিকে হাইড্রেট হতে হতো। কোড স্প্লিটিং একবারে কতটা আসবে তা কমাল, মোট পরিমাণ না কমিয়েই। স্ট্যাটিক জেনারেশন কখনো বদলায় না এমন কনটেন্টের জন্য সমাধান দিল, আর বাকি কিছুর জন্য নয়।

Server Components-ই প্রথম উত্তর, যা লক্ষণ নয়, কারণটির মোকাবিলা করে: কিছু কম্পোনেন্টের ব্রাউজারে থাকার দরকারই নেই, তাই সেগুলো সেখানে যাওয়াই উচিত নয়।

এই দৃষ্টিভঙ্গিটাই সেই অংশগুলো ব্যাখ্যা করে যেগুলো খেয়ালখুশির মতো লাগে। Server Component-এ useState ব্যবহার করা যায় না কোনো সীমাবদ্ধতার কারণে নয়, বরং কম্পোনেন্টটি ইতিমধ্যে শেষ হয়ে গেছে বলে। প্রপগুলোকে সিরিয়ালাইজ হতে হয় কারণ সেগুলো একটি নেটওয়ার্ক পার হয়। 'use client' কম্পোনেন্ট নয়, সীমারেখা চিহ্নিত করে — কারণ আসল ধারণাটিই সীমারেখা, আর তার পরের সবকিছুকেই পাঠাতে হবে।

মডেলটি একবার "ব্রাউজার কেবল ততটুকুই পাবে যতটুকু না হলেই নয়" হয়ে গেলে নিয়মগুলো আর মুখস্থ করার নিয়ম থাকে না, যুক্তি দিয়ে বের করার পরিণতি হয়ে যায়। ঠিক সেই বিন্দু থেকেই এই আর্কিটেকচার আগেরটার চেয়ে কঠিন নয়, সহজ হতে শুরু করে।

সচরাচর জিজ্ঞাসা

Server Components আর সার্ভার-সাইড রেন্ডারিংয়ের পার্থক্য কী? SSR আপনার কম্পোনেন্টগুলোকে সার্ভারে HTML-এ রেন্ডার করে, তারপর জাভাস্ক্রিপ্ট পাঠায় যাতে React সেগুলো হাইড্রেট করতে পারে। Server Components ওই জাভাস্ক্রিপ্ট কখনো পাঠায়ই না — কম্পোনেন্টের কোড স্থায়ীভাবে সার্ভারে থাকে। SSR প্রথম পেইন্ট নিয়ে; RSC বান্ডল নিয়ে।

"use client" কখন লাগে? যখন কম্পোনেন্টটি স্টেট, ইফেক্ট, ইভেন্ট হ্যান্ডলার বা ব্রাউজার এপিআই ব্যবহার করে। আর কিছুতে নয়। যেটির সত্যিই দরকার সেই সবচেয়ে ছোট কম্পোনেন্টে বসান, কখনো পেজ বা লেআউটে নয়।

Client Component কি Server Component রেন্ডার করতে পারে? ইমপোর্ট করে নয় — তবে children হিসেবে পেলে পারে। ওই কম্পোজিশন ধাঁচটাই সার্ভারে রেন্ডার হওয়া কনটেন্টকে বান্ডলে না টেনে তার চারপাশে একটি ইন্টার‌্যাক্টিভ মোড়ক রাখতে দেয়।

Client Component-এ প্রপ হিসেবে ফাংশন পাঠাতে পারি না কেন? প্রপগুলো সিরিয়ালাইজ হয়ে নেটওয়ার্ক দিয়ে যায়, আর ফাংশন সিরিয়ালাইজ হয় না। ব্যতিক্রম Server Actions ('use server'), যেখানে পার হয় ফাংশনটি নয়, ডাকা যায় এমন একটি রেফারেন্স।

Server Components কি দ্রুত? এগুলো ক্লায়েন্ট জাভাস্ক্রিপ্ট কমায়, যা সাধারণত লোড ও ইন্টার‌্যাকশনের মাপকাঠি ভালো করে। ধীর কাজকে দ্রুত করে না — কাজটি আপনার সার্ভারে সরিয়ে দেয়। ধীর ডেটাবেস কোয়েরি যেখানেই চলুক, ধীরই।

Next.js ছাড়া কি Server Components চলে? এটি React-এর ফিচার, তবে প্রোটোকলটি বাস্তবায়ন করে এমন ফ্রেমওয়ার্ক বা বান্ডলার লাগে। Next.js App Router সবচেয়ে পরিণত বাস্তবায়ন; অন্যগুলো আছে আর কম সম্পূর্ণ।