এই পর্বে যা শিখবেন
- CSR, SSR, SSG, ISR-কে “কোথায়” আর “কখন” দিয়ে আলাদা করা; streaming আর PPR-এর মূল ধারণা
- MPA আর SPA-র পার্থক্য, আর কেন এটি rendering-এর থেকে আলাদা প্রশ্ন
- TTFB, FCP, LCP, INP দিয়ে একটি কৌশলের ভালো-মন্দ হিসাব করা
- SEO, link preview, CDN আর সার্ভার খরচের ওপর প্রতিটি কৌশলের প্রভাব
- “বাজারঘর”-এর প্রতিটি পাতার জন্য কারণসহ কৌশল বেছে নেওয়া
১ · HTML কোথায়, আর কখন তৈরি হয়
পর্ব ০২-এ আপনি একটি অনুরোধের পুরো যাত্রা দেখেছেন। সেখানে একটি প্রশ্ন বাকি ছিল: যে HTML ব্রাউজারে পৌঁছায়, সেটি আসলে কে বানাল? এই বানানোর কাজের নামই rendering. ব্রাউজারের কাছে শেষ পর্যন্ত একটি পাতা বানানোর মতো HTML বা DOM থাকতেই হবে। আলাদা হয় শুধু এটুকু: বানানোর কাজটি কে করছে, আর কোন মুহূর্তে করছে।
একটি রেস্তোরাঁর কথা ভাবুন। “বাজারঘর”-এর পাতা হলো খাবার।
- SSR: অর্ডার পেলে রান্নাঘরে রান্না শুরু। খাবার গরম আর একদম আপনার মতো, কিন্তু ওয়েটারকে অপেক্ষা করতে হয়।
- SSG: সকালে বিরিয়ানি রান্না করে প্যাকেট করা আছে। কাউন্টারে চাইলেই পান, সবাই একই খাবার পান।
- CSR: টেবিলে চুলা আর কাঁচা বাজার দিয়ে যাওয়া হলো, রান্না আপনি নিজেই করবেন। রেস্তোরাঁর খাটুনি কম, কিন্তু আপনার চুলা দুর্বল হলে অনেকক্ষণ না খেয়ে বসে থাকবেন।
এখানে “চুলা” মানে ব্যবহারকারীর ফোনের CPU, আর “রান্নাঘর” মানে আপনার সার্ভার। পুরো পর্বের চাবি এই ছবিতে:
Rendering-এর মানচিত্র: HTML কখন, কোথায় তৈরি হয়
Where and when the HTML is built, and how navigation is a separate question
নাম মুখস্থ না করে দুটি প্রশ্ন করুন: HTML কোথায় তৈরি হচ্ছে? আর কখন তৈরি হচ্ছে? CSR হলো ব্রাউজারে, পৌঁছানোর পরে। SSR হলো সার্ভারে, প্রতিটি অনুরোধে। SSG হলো সার্ভারে বা build মেশিনে, build time-এ। বাকি নামগুলো এই তিনটির মিশ্রণ।
২ · MPA বনাম SPA, আর ওয়েব কীভাবে এখানে এল
এবার সম্পূর্ণ আলাদা একটি প্রশ্ন: ব্যবহারকারী একটি লিংকে চাপ দিলে কী হয়? MPA-তে ব্রাউজার সার্ভার থেকে নতুন একটি HTML document আনে; পর্দা সাদা হয়ে নতুন পাতা আসে। SPA-তে document একটিই থাকে। JavaScript ক্লিক ধরে, দরকারি ডেটা আনে, আর পাতার অংশ বদলে দেয়। ঠিকানা বদলাতে আর Back বোতাম ঠিক রাখতে সে History API ব্যবহার করে।
MPA হলো নিউমার্কেটের এক দোকান থেকে আরেক দোকানে হেঁটে যাওয়া: প্রতিবার নতুন দোকান, নতুন করে সব দেখা। SPA হলো একটি দোকানে বসে থাকা, আর দোকানি তাক থেকে জিনিস এনে টেবিলে রাখছেন। শুরুতে বসতে একটু সময় লাগে, তারপর প্রতিটি জিনিস দ্রুত আসে।
| বিষয় | MPA | SPA |
|---|---|---|
| লিংকে চাপ দিলে | নতুন HTML document আসে | JavaScript পাতার অংশ বদলায় |
| প্রথম লোড | সাধারণত হালকা | বড় bundle লাগতে পারে |
| পরের পাতা | আবার পুরো document | শুধু ডেটা, তাই দ্রুত অনুভূত হয় |
| পাতার state (যেমন খোলা মেনু) | হারিয়ে যায় | থেকে যায় |
| যা নিজে সামলাতে হয় | সার্ভারের template | স্ক্রল, focus, ভুলের পাতা, পাতার শিরোনাম |
সবচেয়ে সাধারণ ভুল: “SPA মানেই CSR”. না। একটি Next.js অ্যাপের প্রথম পাতা সার্ভারে তৈরি হতে পারে, আর পরের ক্লিকগুলো SPA-র মতো নতুন document ছাড়াই চলতে পারে। আবার SSR করা MPA-তেও কোনো অংশ CSR হতে পারে। Rendering আর navigation দুটি আলাদা অক্ষ।
Rendering কীভাবে বদলেছে
ইতিহাসটি একটি দোলকের মতো: HTML প্রথমে সার্ভারে, তারপর ব্রাউজারে, এখন আবার দুই দিকে ভাগ করে।
| সময় | কী ঘটল | HTML কোথায় তৈরি |
|---|---|---|
| ১৯৯১-এর দিকে | Tim Berners-Lee-র প্রথম ওয়েবসাইট, হাতে লেখা HTML ফাইল | আগে থেকেই, হাতে |
| ১৯৯৩-১৯৯৫ | CGI, তারপর PHP: অনুরোধে সার্ভারে পাতা তৈরি | সার্ভারে |
| ২০০৪-২০০৫ | Gmail-এর মতো অ্যাপ; ২০০৫ সালে Jesse James Garrett “Ajax” নাম দেন | মূলত সার্ভারে, কিছু অংশ ব্রাউজারে |
| ২০১০-২০১৩ | Backbone, AngularJS, তারপর React (২০১৩); SPA-র যুগ | ব্রাউজারে |
| ২০১৫-২০১৬ | Gatsby-র মতো static generator, Next.js (২০১৬): React-ও সার্ভারে | আবার সার্ভারে বা build-এ |
| ২০২০ | Next.js 9.5-এ ISR স্থিতিশীল; ডিসেম্বরে React Server Component-এর প্রথম ঘোষণা | build আর অনুরোধের মাঝামাঝি |
| ২০২২ | React 18: Suspense-সহ streaming SSR | সার্ভারে, টুকরো টুকরো করে |
| ২০২৪ | INP Core Web Vital হয়ে FID (First Input Delay, শুধু প্রথম ক্লিকের দেরি)-এর জায়গা নেয় | JavaScript কমানোর চাপ |
| ২০২৫ | Next.js 16-এ cacheComponents স্থিতিশীল; PPR এর ভেতরে আসে | এক পাতায় মিশ্র |
৩ · CSR: ফোনই রান্নাঘর
CSR-এ সার্ভার পাঠায় প্রায় ফাঁকা একটি HTML, যাকে অনেকে app shell বলেন, আর একটি বড় JavaScript ফাইল। JavaScript ফোনে চলে, API থেকে ডেটা আনে, তারপর পাতা আঁকে।
এই পর্বের সব সময়ের হিসাব একই ধরে নেওয়া মানে করা, যাতে তুলনা করা যায়। এগুলো মাপা সংখ্যা নয়, একটি ধীর মোবাইল সংযোগের কাল্পনিক ছবি:
ধরে নেওয়া মান: গতি 200 KB/s; origin সার্ভারের RTT 200 ms; CDN-এর RTT 40 ms; সার্ভারে ডেটা আনা ও HTML বানানো 150 ms; ফোনের CPU প্রতি সেকেন্ডে 500 KB JavaScript চালায়। আকার: shell 2 KB, পূর্ণ HTML 30 KB, JSON 10 KB, JavaScript 300 KB. নতুন সংযোগে প্রথম byte পেতে ধরা হয়েছে 3 RTT (TCP, TLS আর HTTP অনুরোধ-উত্তর)। সরল করার জন্য JavaScript HTML-এর পরে আসে, আর তার সংযোগের সময় বাদ।
- ব্রাউজারঅনুরোধ পাঠায়
- ফাঁকা shell
2 KB - JavaScript
300 KB - JS চলেCPU-তে
- API + পাতা
10 KB JSON
| সময় | |
| পর্দায় | |
| চাপ দিলে কাজ করে? |
- 0 ms. ব্যবহারকারী “বাজারঘর”-এর লিংকে চাপলেন। ব্রাউজার CDN-কে অনুরোধ পাঠাল।
- 130 ms. Shell পৌঁছাল:
3 × 40 = 120 msপ্রথম byte, আর 2 KB নামাতে 10 ms. ভেতরে শুধু একটি ফাঁকা<div id="root">, তাই পর্দা সাদা। TTFB চমৎকার, কিন্তু ব্যবহারকারী কিছুই পাননি। - 1630 ms.
300 ÷ 200 = 1.5 sধরে JavaScript নামল। পর্দা এখনো সাদা। - 2230 ms. ফোনের CPU-তে
300 ÷ 500 = 0.6 sধরে JavaScript চলল। “লোড হচ্ছে...” দেখা গেল, এটিই FCP. এখন API-তে অনুরোধ গেল। - 3030 ms. Origin থেকে উত্তর:
3 × 200 + 150 = 750 msপ্রথম byte, 10 KB নামাতে 50 ms. মোট 800 ms পরে তালিকা এল। এটিই LCP. পাতা দেখা আর ক্লিক করা, দুটো প্রায় একসাথে শুরু হলো।
CSR-এর দুর্বলতা প্রথম লোডে। শক্তি পরের ক্লিকে: সার্ভারে HTML বানানোর খরচ নেই, shell আর JavaScript যেকোনো static hosting থেকে দেওয়া যায়, আর পরের পাতায় শুধু JSON আনলেই চলে। তাই লগইনের পেছনের ড্যাশবোর্ডে CSR এখনো খুব যুক্তিসঙ্গত।
৪ · SSR আর hydration: দেখা যায়, কিন্তু চাপ দিলে কাজ করে না
SSR-এ প্রতিটি অনুরোধে সার্ভার ডেটাবেস থেকে ডেটা আনে, পূর্ণ HTML বানায় আর পাঠায়। ব্রাউজার JavaScript ছাড়াই পাতা আঁকতে পারে। কিন্তু React-এ বানানো পাতা শুধু ছবি হয়ে থাকলে বোতাম কাজ করবে না। তাই একই JavaScript পরে আসে, আর HTML-এর সাথে নিজেকে জুড়ে নেয়।
Hydration এক ধারণায়: নতুন ফ্ল্যাটে দেয়ালে সব সুইচ বসানো আছে, দেখতে সম্পূর্ণ। কিন্তু মিস্ত্রি ভেতরের তার না জোড়া পর্যন্ত সুইচ টিপলে বাতি জ্বলে না। সার্ভারের HTML হলো সুইচ-বসানো দেয়াল, আর hydration হলো তার জোড়ার কাজ। বিস্তারিত পর্ব ৫৬-এ।
- ব্রাউজারঅনুরোধ পাঠায়
- Origin সার্ভার
150 ms - পূর্ণ HTML
30 KB - JavaScript
300 KB - Hydrationফোনের CPU-তে
| সময় | |
| পর্দায় | |
| চাপ দিলে কাজ করে? |
- 0 ms. অনুরোধ যাচ্ছে সরাসরি origin সার্ভারে, যা CDN-এর চেয়ে দূরে।
- 750 ms.
3 × 200 = 600 msসংযোগ আর অনুরোধ-উত্তরের পথে,150 msসার্ভারের কাজে। প্রথম byte এখন এল। এই সময়ে ফোনে কিছু বদলায়নি, এটাই TTFB. - 900 ms. 30 KB HTML নামল
150 ms-এ। পুরো তালিকা দেখা যাচ্ছে: FCP আর LCP একই মুহূর্তে। CSR-এর তুলনায় অনেক আগে। - 2400 ms. JavaScript নামতে আরও 1.5 s. “কার্টে যোগ করুন” বোতাম দেখা যাচ্ছে, কিন্তু চাপ দিলে কিছু হবে না।
- 3000 ms. 0.6 s ধরে hydration শেষ। এবার বোতাম কাজ করে। 900 থেকে 3000 ms, মোট 2100 ms পাতা “দেখা যায়, কাজ করে না” অবস্থায় ছিল।
খেয়াল করুন: SSR পাতা দেখানোর সময় কমিয়েছে, ক্লিকযোগ্য হওয়ার সময় প্রায় কমায়নি (CSR-এ 3030 ms, SSR-এ 3000 ms). JavaScript-এর আকার না কমালে এই ফাঁক থেকেই যায়। তা ছাড়া প্রতিটি অনুরোধে সার্ভারের CPU আর ডেটাবেস খরচ হয়।
৫ · SSG আর ISR: রান্না আগে, পরিবেশন কাছে
SSG-তে build-এর সময় একবার HTML তৈরি হয়, তারপর সেটি সাধারণ ফাইল। ফাইল হলে তা CDN-এ রাখা যায়। CDN-এর সবচেয়ে কাছের সার্ভারকে বলে edge, আর মূল সার্ভারকে origin. পাড়ার মুদি দোকানে মাল মজুত থাকলে যেমন প্রতিবার মূল গুদামে যেতে হয় না, তেমনি অনুরোধও origin পর্যন্ত যায় না। CDN আর cache-এর তত্ত্ব আছে System Design-এর CDN পর্বে।
- Buildআগেই HTML
- ব্রাউজারঅনুরোধ
- CDN edgecache hit
- JavaScript
300 KB - Hydrationফোনের CPU-তে
| সময় | |
| পর্দায় | |
| চাপ দিলে কাজ করে? | |
| Origin-এ অনুরোধ |
- Build-এর সময়। ব্যবহারকারী আসার আগেই HTML তৈরি হয়ে CDN-এ পৌঁছে গেছে। ডেটাবেসের খরচ এখানেই একবার।
- 0 ms. অনুরোধ যাচ্ছে সবচেয়ে কাছের edge-এ।
- 120 ms.
3 × 40. সার্ভারে কিছু বানাতে হয়নি, ফাইলটি তৈরিই ছিল। SSR-এর 750 ms-এর জায়গায় 120 ms. - 270 ms. 30 KB HTML নামল। FCP আর LCP দুটোই এখানে। SSR-এর চেয়ে 630 ms আগে।
- 1770 ms. SSG হলেই JavaScript কমে না। 300 KB-র দাম একই।
- 2370 ms. Hydration শেষ। ফাঁক এখনো 2100 ms. কৌশল শুধু শুরুটা বদলেছে; JavaScript-এর দাম আলাদা হিসাব।
SSG-র সমস্যা হলো পুরোনো হয়ে যাওয়া। দাম বদলালে আবার build? ৫০ হাজার পণ্যের দোকানে সেটি অসম্ভব রকম ধীর হতে পারে। এখানেই ISR: পাতা static থাকে, কিন্তু নির্দিষ্ট সময় পরে বা দরকারমতো একটি পাতাই পেছনে নতুন করে তৈরি হয়। নীতিটির নাম stale-while-revalidate: পুরোনো কপি এখনই দিন, নতুনটি পেছনে বানান।
দুপুরের প্যাকেট বিরিয়ানি একঘণ্টা পর ঠান্ডা হতে শুরু করেছে। পরের ক্রেতাকে না ফিরিয়ে সেই প্যাকেটই দেওয়া হলো, আর একই সাথে রান্নাঘরে নতুন হাঁড়ি চড়ল। তার পরের ক্রেতা গরমটি পাবেন। প্রথম ক্রেতা অপেক্ষা করেননি, কিন্তু একটু পুরোনো জিনিস পেয়েছেন।
| সময় | |
| x-nextjs-cache | |
| ব্যবহারকারী পেলেন | |
| পেছনের কাজ |
HIT. কপিটি এখনো 60 s-এর মধ্যে। সরাসরি cache থেকে উত্তর।STALE. 60 s পেরিয়ে গেছে। ব্যবহারকারী তবু দেরি ছাড়াই পুরোনো কপি পেলেন, আর এই অনুরোধটিই পেছনের নতুন তৈরি চালু করল। অনুরোধ না এলে কিছুই নতুন হতো না।HIT. নতুন কপি তৈরি, পরের অনুরোধ সেটি পেল। আমাদের ছোট পাতায় এতে 1 s-ও লাগেনি; ভারী পাতায় বেশি লাগবে।
দরকার হলে সময়ের অপেক্ষা না করে কোড থেকেও একটি পাতা বাতিল করা যায় (on-demand revalidation): Next.js-এ revalidatePath বা revalidateTag. এটি পর্ব ৫৩ আর পর্ব ৫৫-এর বিষয়।
৬ · Streaming, PPR, আর Next.js-এ সব একসাথে
SSR-এর একটি জ্বালা: পাতার সবচেয়ে ধীর অংশের জন্য পুরো পাতা আটকে থাকে। Streaming এটি ভাঙে। সার্ভার পাতার তৈরি অংশ আগে পাঠায়, ধীর অংশের জায়গায় একটি অপেক্ষার UI দেয়, আর সেটি তৈরি হলে একই সংযোগে বাকি HTML পাঠায়। থালিতে আগে ভাত দিয়ে দেওয়া, মাছ এলে পরে দেওয়ার মতো। React-এ এই সীমা টানে <Suspense>. এতে সাধারণত TTFB আর FCP কমে। একটি ফাঁদ: প্রথম টুকরো যাওয়ার পর HTTP status আর বদলানো যায় না, তাই পরে ভুল হলে 200 উত্তরের ভেতরেই তা দেখাতে হয়।
PPR আরেক ধাপ এগিয়ে: একই পাতার static অংশ আগেই তৈরি থাকে আর সঙ্গে সঙ্গে যায়, “গর্ত”গুলো অনুরোধের সময় stream হয়ে পূরণ হয়। পণ্যের নাম আর ছবি তৈরি থাকল, “আপনার জন্য সুপারিশ” পরে এল। Next.js 16-এ cacheComponents চালু করলে এটিই App Router-এর ডিফল্ট আচরণ; আগের experimental.ppr flag সরিয়ে দেওয়া হয়েছে। Next.js-এর বাইরে কিছু framework-এ একই ধারণা ভিন্ন নামে আছে।
একই অ্যাপে চারটি কৌশল: সত্যিকারের build
কথাগুলো কাল্পনিক নয়। “বাজারঘর”-এর ছোট একটি Next.js অ্যাপ আমরা সত্যিই build করেছি (সত্যিকারের run; লেখার সময়ে Next.js 16.4.3, React 19.3.1, Node 24). এই অ্যাপে cacheComponents বন্ধ ছিল, অর্থাৎ আগের মডেল। নতুন create-next-app (16.4.4) এটি নিজেই চালু করে দেয়, আর তখন নথি অনুযায়ী export const revalidate চলে না; তার বদলে 'use cache' আর cacheLife, যা পর্ব ৫৫-এ আছে। চারটি পাতার মূল অংশ:
export const revalidate = 60; // ISR: 60 s পর পেছনে নতুন করে
export function generateStaticParams() { // build-এ এই id-গুলোর পাতা তৈরি
return Object.keys(PRODUCTS).map((id) => ({ id }));
}import { cookies } from 'next/headers';
export default async function Cart() {
const store = await cookies(); // cookie পড়া মানে প্রতি অনুরোধে render
const items = store.get('cart_items')?.value ?? '0';
return <h1>Cart items: {items}</h1>;
}/admin পাতাটি 'use client' দিয়ে useEffect-এ ডেটা আনে, আর / ও /blog সাধারণ পাতা। next build নিজেই বলে দেয় কোন পাতা কী হলো:
$ npx next build
▲ Next.js 16.4.3 (Turbopack)
…
Route (app)
┌ ○ /
├ ○ /_not-found
├ ○ /admin
├ ○ /blog
├ ƒ /cart
└ /products/[id]
├ ● /products/1
└ ● /products/2
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses generateStaticParams)
ƒ (Dynamic) server-rendered on demand
এরপর next start চালিয়ে প্রতিটি পাতার header আর HTML-এর <h1> দেখেছি (এক local run, ছোট করে সাজানো):
| পাতা | x-nextjs-cache | cache-control | HTML-এর h1 |
|---|---|---|---|
/ | HIT | s-maxage=31536000 | Bazarghar home |
/products/1 | HIT | s-maxage=60, stale-while-revalidate=31535940 | Rice 5kg |
/products/3 | MISS, পরের বার HIT | s-maxage=60, stale-while-revalidate=31535940 | Unknown |
/admin | HIT | s-maxage=31536000 | Loading... |
/cart | নেই | private, no-cache, no-store, max-age=0, must-revalidate | Cart items: <!-- -->0 |
/cart, cookie cart_items=3 | নেই | একই | Cart items: <!-- -->3 |
প্রতিটি সারি একটি প্রমাণ। /admin “Static”, অথচ HTML-এ শুধু Loading...: এটি Next.js-এর ভেতরেই CSR, shell-টি static. /products/3 build-এ ছিল না, তাই প্রথমবার MISS, তারপর তৈরি হয়ে HIT. (আমাদের ছোট demo-তে এই পণ্য নেই বলে Unknown আসে; বাস্তবে notFound() দিয়ে 404 দিতে হয়।) /cart-এ private, no-store, কারণ এই উত্তর একজন মানুষের। cookie বদলাতেই HTML বদলেছে। <!-- --> হলো পাশাপাশি দুটি লেখাকে আলাদা রাখতে React-এর চিহ্ন। s-maxage শুধু CDN-এর মতো ভাগ করা cache-এর জন্য।
৭ · গতি মাপার চারটি কাঠি, আর ধীর নেটওয়ার্ক
“দ্রুত” শব্দটি অস্পষ্ট। তাই আলাদা আলাদা মুহূর্তের নাম আছে। LCP, INP আর CLS মিলে Core Web Vitals; TTFB আর FCP তাদের বোঝার সহায়ক। মান বিচার হয় আসল ব্যবহারকারীদের ৭৫তম percentile ধরে (অর্থাৎ ১০০টি ভিজিটের মধ্যে ৭৫টি যত ভালো বা তার চেয়ে ভালো), মোবাইল আর ডেস্কটপ আলাদা করে। বিস্তারিত আছে পর্ব ৮৪-তে আর HTML & CSS-এর performance পর্বে।
| মেট্রিক | কী মাপে | ভালো | খারাপ |
|---|---|---|---|
| TTFB | প্রথম byte কখন এল | ≤ 0.8 s | > 1.8 s |
| FCP | প্রথম কিছু কখন আঁকা হলো | ≤ 1.8 s | > 3.0 s |
| LCP | মূল বিষয়বস্তু কখন এল | ≤ 2.5 s | > 4.0 s |
| INP | ক্লিকের পর পরের ছবি আঁকতে কত সময়, পুরো ভিজিট ধরে | ≤ 200 ms | > 500 ms |
| কৌশল | TTFB | FCP, LCP | INP |
|---|---|---|---|
| CSR | ভালো (static shell) | দেরি; JS আর API-র ওপর নির্ভর | বড় JS হলে ঝুঁকি, তবে hydration নেই |
| SSR | সার্ভার আর ডেটাবেসের ওপর নির্ভর | ভালো | Hydration চলার সময় ঝুঁকি |
| SSG / ISR | সবচেয়ে ভালো (CDN) | সবচেয়ে ভালো | SSR-এর মতোই; JS-এর আকারই ঠিক করে |
| Streaming / PPR | ভালো | ভালো; মূল অংশ ধীর হলে LCP দেরি | কম JS হলে ভালো; পাতা ধরে আলাদা |
এবার তিনটি কৌশল পাশাপাশি চালান। নিচের সিমুলেটর ৩ নম্বর অংশের ধরে নেওয়া মানগুলোই ব্যবহার করে। js: 300 কমিয়ে 100 করে, বা kBps কমিয়ে আবার চালিয়ে দেখুন।
// সব সংখ্যা ধরে নেওয়া, মাপা নয়। বদলে দেখুন।
const net = { kBps: 200, originRTT: 200, cdnRTT: 40, serverMs: 150, cpuKBps: 500 };
const size = { shell: 2, html: 30, json: 10, js: 300 }; // KB
const get = (rtt, serverMs, kb) => ({
ttfb: 3 * rtt + serverMs,
done: 3 * rtt + serverMs + kb / net.kBps * 1000,
});
const runJs = t => t + size.js / net.kBps * 1000 + size.js / net.cpuKBps * 1000;
function load(kind) {
if (kind === 'CSR') {
const doc = get(net.cdnRTT, 0, size.shell);
const fcp = runJs(doc.done); // এই মুহূর্তে "লোড হচ্ছে..." আঁকে
const lcp = fcp + get(net.originRTT, net.serverMs, size.json).done;
return { ttfb: doc.ttfb, fcp, lcp, ready: lcp };
}
const doc = kind === 'SSR' ? get(net.originRTT, net.serverMs, size.html)
: get(net.cdnRTT, 0, size.html);
return { ttfb: doc.ttfb, fcp: doc.done, lcp: doc.done, ready: runJs(doc.done) };
}
for (const kind of ['CSR', 'SSR', 'SSG']) {
const t = load(kind);
console.log(kind, Object.entries(t).map(([k, v]) => `${k}=${Math.round(v)}ms`).join(' '));
const cell = ms => Math.round(ms / 100);
console.log(' ' + '.'.repeat(cell(t.lcp)) + '#'.repeat(cell(t.ready) - cell(t.lcp)) + '|');
}
console.log('. = মূল বিষয়বস্তুর অপেক্ষা # = দেখা যায়, চাপ দিলে কাজ করে না');আউটপুট:
CSR ttfb=120ms fcp=2230ms lcp=3030ms ready=3030ms
..............................|
SSR ttfb=750ms fcp=900ms lcp=900ms ready=3000ms
.........#####################|
SSG ttfb=120ms fcp=270ms lcp=270ms ready=2370ms
...#####################|
. = মূল বিষয়বস্তুর অপেক্ষা # = দেখা যায়, চাপ দিলে কাজ করে না
তিনটি শিক্ষা। প্রথমত, CSR আর SSG-র TTFB একই 120 ms, অথচ LCP-তে পার্থক্য 2760 ms: একা TTFB কিছু প্রমাণ করে না। দ্বিতীয়ত, SSR-এর TTFB CSR-এর চেয়ে খারাপ, তবু LCP অনেক ভালো। তৃতীয়ত, ready তিনটিতেই কাছাকাছি, কারণ তিনটিই একই 300 KB JavaScript বয়ে আনছে। ready এখানে আমাদের নিজের সরল মাপ, কোনো মানক মেট্রিক নয়; আসল INP মাপা হয় আসল ক্লিক থেকে, লোডের হিসাব থেকে নয়। সিমুলেটরে ছবির সময়ও ধরা নেই, যা বাস্তবে প্রায়ই LCP ঠিক করে।
ধীর মোবাইল নেটওয়ার্কে কী বদলায়
বাংলাদেশে অনেক ব্যবহারকারী ফোনে, সীমিত ডেটা প্যাকে, আর ভিড়ের সময়ে বা শহরের বাইরে দুর্বল সংকেতে ওয়েব দেখেন। অনেকের ফোনের CPU-ও দামি ফোনের মতো নয়। তাই গতি মাপুন নিজের অফিসের Wi-Fi আর ল্যাপটপে নয়। সিমুলেটরে গতি 50 KB/s ধরে দেখি (এটিও ধরে নেওয়া মান):
| গতি 50 KB/s | LCP | ready, JS 300 KB | ready, JS 100 KB |
|---|---|---|---|
| CSR | 7710 ms (JS 100 KB-তে 3310 ms) | 7710 ms | 3310 ms |
| SSR | 1350 ms | 7950 ms | 3550 ms |
| SSG | 720 ms | 7320 ms | 2920 ms |
এখানে SSR-এর ফাঁক সবচেয়ে বেশি: ৬ সেকেন্ডের বেশি সময় পাতা দেখা যায় কিন্তু কাজ করে না, আর ব্যবহারকারী তখন বারবার বোতাম চাপেন। JavaScript 300 থেকে 100 KB করলে তিন কৌশলেই 4.4 s বাঁচে। কৌশল বদলানোর আগে অনেক সময় এটিই বড় লাভ। Chrome DevTools-এর network আর CPU throttling (ইচ্ছা করে গতি কমানো) দিয়ে আপনি নিজের পাতা এভাবে পরীক্ষা করতে পারেন। JavaScript কমানোর কৌশল আসবে পর্ব ২৩ আর পর্ব ৭৯-এ।
৮ · SEO আর শেয়ারের preview: কে কী দেখে
বাজারঘর-এর একটি পণ্যের লিংক কেউ Facebook বা WhatsApp-এ দিলে শিরোনাম আর ছবি কোথা থেকে আসে? একটি crawler পাতা আনে, আর সাধারণত HTML-এর Open Graph tag পড়ে। এই preview bot-গুলো সাধারণত JavaScript চালায় না বলে ব্যাপকভাবে দেখা যায়, যদিও প্রতিটি প্ল্যাটফর্ম আলাদা আর নিয়ম বদলাতে পারে। তাই নিরাপদ অভ্যাস: শিরোনাম, বিবরণ আর ছবির tag প্রথম HTML-এই রাখুন।
নিচের ব্লকে একটি আসল node:http সার্ভার একই পণ্যতালিকা দুভাবে দেয়: /ssr-এ পূর্ণ HTML, /csr-এ ফাঁকা shell আর আলাদা JSON. তারপর সেই ব্লকই JavaScript না চালানো crawler-এর মতো পাতা পড়ে।
const http = require('node:http');
const products = [
{ name: 'Rice 5kg', price: 450 }, { name: 'Lentil 1kg', price: 140 },
{ name: 'Soybean oil 2L', price: 390 }, { name: 'Tea 400g', price: 210 },
];
const esc = s => String(s).replace(/[&<>"]/g, c => '&#' + c.charCodeAt(0) + ';');
const page = (title, body) => `<!doctype html><html><head><title>${title}</title>` +
`</head><body>${body}<script src="/app.js"></script></body></html>`;
const html = { 'content-type': 'text/html; charset=utf-8' };
const server = http.createServer((req, res) => {
if (req.url === '/ssr') {
const items = products.map(p => `<li>${esc(p.name)}: ${p.price} BDT</li>`);
res.writeHead(200, html).end(page('4 products', `<ul>${items.join('')}</ul>`));
} else if (req.url === '/csr') {
res.writeHead(200, html).end(page('Loading...', '<div id="root"></div>'));
} else if (req.url === '/api/products') {
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(products));
} else res.writeHead(404).end();
});
// A simple crawler: it reads the HTML and runs no JavaScript at all.
const seen = doc => doc.replace(/<script[\s\S]*?<\/script>/g, '')
.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
server.listen(0, '127.0.0.1', async () => {
const base = `http://127.0.0.1:${server.address().port}`;
for (const path of ['/ssr', '/csr', '/api/products']) {
const res = await fetch(base + path);
const text = await res.text();
console.log(path.padEnd(13), Buffer.byteLength(text) + ' bytes');
if (path !== '/api/products') console.log(' crawler sees:', JSON.stringify(seen(text)));
}
server.closeAllConnections();
server.close();
});আউটপুট:
/ssr 231 bytes
crawler sees: "4 products Rice 5kg: 450 BDT Lentil 1kg: 140 BDT Soybean oil 2L: 390 BDT Tea 400g: 210 BDT"
/csr 131 bytes
crawler sees: "Loading..."
/api/products 137 bytes
একই ডেটা, দুই রকম ফল। JavaScript না চালানো পাঠকের কাছে /csr মানে শুধু “Loading...”, পণ্যের নাম বা দাম কিছুই নেই। শিরোনামও বদলেছে, আর link preview ঠিক এই শিরোনামই দেখাত। লক্ষ করুন, /ssr-এ পণ্যের নাম esc দিয়ে HTML-escape করা। হাতে HTML জুড়লে এটি না করলে XSS-এর ঝুঁকি আসে; React নিজেই লেখা escape করে। আর byte-এর হিসাবে ভুলবেন না: CSR-এর 131 byte-এর সাথে JSON-এর 137 byte আর পুরো JavaScript যোগ হয়।
Google-এর কথা একটু আলাদা। তাদের নথি অনুযায়ী Googlebot পাতা crawl করে, পরে সম্পদ পেলে headless Chromium-এ (পর্দা ছাড়া চলা ব্রাউজার) JavaScript চালিয়ে render করে, তারপর index করে। অর্থাৎ CSR পাতাও Google পড়তে পারে, তবে render-এর একটি সারিতে অপেক্ষা থাকে। নথিটি নিজেই বলে, SSR বা pre-rendering এখনো ভালো ধারণা, কারণ এতে ব্যবহারকারী আর crawler দুজনেরই সুবিধা, আর সব bot JavaScript চালাতে পারে না। Metadata, sitemap আর structured data আসবে পর্ব ৫৮-এ; <head>-এর ভিত্তি আছে HTML & CSS কোর্সে।
| পাঠক | CSR | SSR / SSG / ISR |
|---|---|---|
| Google SEO | পড়তে পারে, তবে render-এর ওপর নির্ভর | প্রথম HTML-এই সব আছে |
| JS না চালানো bot | প্রায় ফাঁকা পাতা | পূর্ণ লেখা |
| শেয়ারের preview | সাধারণ, একই শিরোনাম-ছবি | পণ্য ধরে আলাদা শিরোনাম-ছবি |
| লগইনের পেছনের পাতা | SEO-র প্রশ্নই ওঠে না | SEO-র প্রশ্নই ওঠে না |
৯ · সার্ভারের খরচ, CDN, আর বাজারঘর-এর সিদ্ধান্ত
গতি ছাড়াও প্রশ্ন আছে: প্রতি অনুরোধে কে কাজ করছে? SSR-এ আপনার সার্ভার, SSG-তে প্রায় কেউ না, CSR-এ ব্যবহারকারীর ফোন আর আপনার API. হিসাব করে দেখি।
গণনার উদাহরণ: ধরি দিনে ১০ লাখ পাতা দেখা হয়; ব্যস্ত সময়ে চাপ গড়ের ৫ গুণ; একটি পাতা বানাতে 50 ms CPU; আর CPU core-গুলো 60% পর্যন্ত ব্যস্ত রাখতে চাই। সবই ধরে নেওয়া সংখ্যা।
| CDN hit rate | Origin-এ দিনে | ব্যস্ত সময়ে req/s | ব্যস্ত core | 60% লক্ষ্যে core |
|---|---|---|---|---|
| 0% (খাঁটি SSR) | 10,00,000 | 57.87 | 2.89 | 4.82 |
| 95% | 50,000 | 2.89 | 0.145 | 0.24 |
| 99% | 10,000 | 0.58 | 0.029 | 0.05 |
পথটি সরল: 1000000 ÷ 86400 = 11.57 req/s গড়, × 5 = 57.87, × 0.05 s = 2.89 core, ÷ 0.6 = 4.82. খেয়াল করুন, cache hit rate 95% থেকে 99% হলে শুধু ৪ শতাংশ বিন্দু বাড়ে, অথচ origin-এর চাপ ৫ ভাগের ১ হয়ে যায়। কারণ হিসাবটি হয় miss-এর ওপর: 5% থেকে 1%. এর অঙ্কের দিকটি আছে Computer Networks-এর caching পর্বে, আর নানা ধরনের অনুশীলন পর্ব ৮৯-এ।
ISR-ও বিনামূল্যে নয়। revalidate = 60 মানে একটি পাতা দিনে সর্বোচ্চ 86400 ÷ 60 = 1440 বার নতুন হতে পারে, আর ট্রাফিক থাকলে হয়ও। হাজার পণ্যে সীমাটি দিনে ১৪ লাখ ৪০ হাজার, যা উদাহরণের মোট ১০ লাখ ভিজিটের চেয়েও বেশি। অর্থাৎ ভিজিট সমানভাবে ছড়ানো থাকলে প্রায় প্রতিটি ভিজিটই পেছনে একটি render চালু করবে, আর সার্ভারের খরচে cache-এর লাভ প্রায় থাকবে না। তাই Next.js-এর নথিও ছোট সময়ের বদলে বড় সময় (যেমন ১ ঘণ্টা) আর দরকারে on-demand revalidation ব্যবহারের পরামর্শ দেয়।
সবচেয়ে দামি ভুল: ব্যক্তিগত পাতা ভাগ করা cache-এ রাখা। CDN যদি একজনের কার্ট বা অর্ডারের HTML জমা রাখে, পরের জন সেটি দেখবেন। ব্যক্তিগত উত্তরে Cache-Control হবে private বা no-store, যেমনটি Next.js /cart-এ নিজেই দিয়েছে। আর CDN-এর সামনে থাকা সব header যাচাই করুন।
পাতা ধরে সিদ্ধান্ত
চারটি প্রশ্ন করুন: (১) পাতাটি কি সবার জন্য এক? (২) কত ঘন ঘন বদলায়, পুরোনো হলে ক্ষতি কত? (৩) crawler বা preview-কে কি এটি পড়তে হবে? (৪) পাতায় কতটা ক্লিক-নির্ভর কাজ? নিচের সিদ্ধান্ত-সহায়ক এই যুক্তিকেই কোড করে। বৈশিষ্ট্য বদলে দেখুন।
function choose(p) {
if (p.private && !p.seo && p.manyClicks) return ['CSR', 'লগইনের পেছনে, crawler লাগে না, ক্লিক অনেক'];
if (p.private) return ['SSR', 'মানুষ ধরে আলাদা, ভাগ করা cache-এ রাখা যাবে না'];
if (p.freshSeconds === 0) return ['SSR', 'প্রতি অনুরোধে নতুন তথ্য দরকার'];
if (p.changesPerDay > 1) return ['ISR', 'সবার জন্য এক, তবে দিনে কয়েকবার বদলায়'];
return ['SSG', 'সবার জন্য এক, কালেভদ্রে বদলায়'];
}
const pages = [
{ name: 'হোম', seo: true, private: false, changesPerDay: 6, freshSeconds: 300 },
{ name: 'পণ্যের পাতা', seo: true, private: false, changesPerDay: 12, freshSeconds: 60 },
{ name: 'ব্লগ', seo: true, private: false, changesPerDay: 0, freshSeconds: 86400 },
{ name: 'কার্ট', seo: false, private: true, manyClicks: false, freshSeconds: 0 },
{ name: 'admin', seo: false, private: true, manyClicks: true, freshSeconds: 0 },
];
for (const p of pages) {
const [how, why] = choose(p);
console.log(`${p.name} -> ${how} (${why})`);
}আউটপুট:
হোম -> ISR (সবার জন্য এক, তবে দিনে কয়েকবার বদলায়)
পণ্যের পাতা -> ISR (সবার জন্য এক, তবে দিনে কয়েকবার বদলায়)
ব্লগ -> SSG (সবার জন্য এক, কালেভদ্রে বদলায়)
কার্ট -> SSR (মানুষ ধরে আলাদা, ভাগ করা cache-এ রাখা যাবে না)
admin -> CSR (লগইনের পেছনে, crawler লাগে না, ক্লিক অনেক)
পাঁচটি পাতা, তিনটি কৌশল, একটি অ্যাপ। এটি শেখার ছক, চূড়ান্ত নিয়ম নয়। কার্টে SSR বা CSR দুটোই প্রচলিত। আসল সিদ্ধান্তটি হলো কার্ট কখনো ভাগ করা cache-এ যাবে না।
| পাতা | কৌশল | খেয়াল রাখার বিষয় |
|---|---|---|
| হোম | ISR, বা PPR | “সালাম, রাফি” বা সুপারিশ হলে সেটুকু আলাদা dynamic অংশে |
| পণ্যের পাতা | ISR | নাম, ছবি cache-এ; স্টক আলাদা করে আনা যায়। অর্ডারের সময় দাম আর স্টক সার্ভারে নতুন করে যাচাই বাধ্যতামূলক (পর্ব ৪৩) |
| ব্লগ | SSG | লেখা বদলালে on-demand revalidation বা নতুন build |
| কার্ট | SSR বা CSR | ভাগ করা cache নয়; SEO লাগে না |
| admin ড্যাশবোর্ড | CSR | Shell public হতে পারে; ডেটা আর অনুমতি API-তে যাচাই (পর্ব ৪৮) |
আরও গভীরে: Hydration কমানোর চেষ্টায় কয়েকটি ধারা এসেছে। Islands architecture-এ পাতার বেশিরভাগ static, শুধু ছোট ছোট অংশে JavaScript (নামটি ২০১৯-২০ সালের দিকে Katie Sylor-Miller আর Jason Miller-এর লেখায় ছড়ায়)। React Server Component শুধু সার্ভারে চলে, তাই তার কোড bundle-এ যায় না; Qwik-এর resumability hydration-কেই এড়াতে চায়। Lighthouse-এর মতো পরীক্ষাগারের টুল ক্লিক করে না, তাই INP-এর বদলে TBT দেখে; আসল INP আসে মাঠের ডেটা থেকে।
পরীক্ষা ও ইন্টারভিউতে:
- SPA আর CSR কি এক? না; SPA পাতা বদলানোর ধরন, CSR HTML তৈরির জায়গা।
- Hydration কী? সার্ভারের HTML-এ client JavaScript-এর event handler জোড়া।
- SSG আর ISR-এর পার্থক্য? SSG build-এ একবার; ISR পরে পেছনে নির্দিষ্ট পাতা নতুন করে।
- TTFB ভালো, LCP খারাপ, কী বুঝবেন? সার্ভার দ্রুত, কিন্তু মূল বিষয়বস্তু আসে দেরিতে; প্রায়ই CSR বা বড় ছবি।
- ব্যক্তিগত পাতা CDN-এ রাখা যায়? ভাগ করা cache-এ না;
privateবাno-storeলাগে।
১০ · AI-এর যুগে rendering যাচাই
Rendering-এর ভুল বিশেষ করে ধূর্ত, কারণ AI-এর দেওয়া পাতা নিজের ল্যাপটপে ঠিকই চলে। সমস্যা ধরা পড়ে ধীর নেটওয়ার্কে, CDN-এর পেছনে, বা Facebook-এ লিংক দিলে। Framework-এর API-ও দ্রুত বদলায়, তাই AI প্রায়ই পুরোনো সংস্করণের কথা বলে।
| AI-এর ভুল | কেন সমস্যা | যাচাইয়ের উপায় |
|---|---|---|
App Router-এ getServerSideProps | এটি পুরোনো Pages Router-এর API | নিজের version-এর নথি পড়ুন, build চালান |
experimental.ppr flag দেওয়া | Next.js 16-এ সরানো হয়েছে | cacheComponents-এর নথি দেখুন |
কার্টে Cache-Control: public | একজনের তথ্য আরেকজন দেখতে পারেন | curl -I দিয়ে header দেখুন |
পণ্যের পাতায় শুধু useEffect-এ ডেটা | প্রথম HTML-এ পণ্য নেই, preview ফাঁকা | Page Source দেখুন |
Render-এর সময় Date.now() বা window | সার্ভার আর ব্রাউজারের HTML মেলে না | Console-এর hydration সতর্কতা পড়ুন |
| নতুন বড় package যোগ | JavaScript বাড়ে, ধীর ফোনে দাম দিতে হয় | Build-এর আকার আগে-পরে তুলনা |
AI-কে “SSR করে দাও” না বলে পাতার বৈশিষ্ট্য আর যাচাইয়ের প্রমাণ চাইলে উত্তর অনেক ভালো হয়:
AI-এর লেখা পাতা হাতে পেলে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১ওয়েব সার্ভারে শুরু হয়েছিল, ব্রাউজারে গেল, আবার সার্ভারে ফিরছে। এটি কি শুধু ফ্যাশনের ঘূর্ণি, না প্রতিবার কোনো আসল কারণ ছিল?
প্রতিবারের বদলের পেছনে একটি আসল সমস্যা ছিল, আর প্রতিটি সমাধান নতুন সমস্যা এনেছে।
PHP-র যুগে সার্ভারই সব করত, কিন্তু সামান্য বদলেও পুরো পাতা ফিরে আসত। ২০০৪-২০০৫ সালের দিকে Gmail-এর মতো অ্যাপ দেখাল, পেছনে ডেটা এনে পাতার অংশ বদলালে ওয়েব ডেস্কটপ সফটওয়্যারের মতো লাগে। Jesse James Garrett-এর “Ajax” নাম এই ধারাকে জনপ্রিয় করে। ফোন আর শক্তিশালী ল্যাপটপের যুগে ব্রাউজারে কাজ পাঠানো সস্তা মনে হলো, আর একই API মোবাইল অ্যাপও ব্যবহার করতে পারত। Frontend আর backend দল আলাদা হওয়াও সহজ হলো। সার্ভারের খরচও কমল, কারণ HTML বানানোর কাজ ব্যবহারকারীর যন্ত্রে সরে গেল। অর্থাৎ খরচ মিলিয়ে গেল না, শুধু জায়গা বদলাল।
কিন্তু দাম উঠল পরে। Bundle বড় হলো, প্রথম লোড ধীর হলো, আর ডেটা ও লজিক ছড়িয়ে গেল দুই জায়গায়। সস্তা ফোন আর ধীর নেটওয়ার্কের ব্যবহারকারীরা সবচেয়ে বেশি ভুগলেন। তাই Next.js-এর মতো framework React-কেই সার্ভারে আনল, যাতে component-এ লেখার সুবিধা থাকে, আবার প্রথম HTML-ও সার্ভার থেকে আসে।
আজকের ফেরা আগের জায়গায় ফেরা নয়। একই component মডেল সার্ভার আর ব্রাউজার দুই জায়গায় চলে, আর ভাগটি পাতা ধরে নয়, component ধরেও করা যায়। PHP-তে এমনটি সহজ ছিল না। অন্যদিকে সমালোচকরা বলেন, আধুনিক ব্যবস্থা অনেক জটিল, আর সাধারণ দোকানের জন্য একটি MPA-ই হয়তো যথেষ্ট। দুই পক্ষের কথাতেই সত্য আছে।
মূল উপলব্ধি: দোলকটি ঘোরে কারণ আসল সম্পদ বদলায়: নেটওয়ার্ক, ফোনের CPU, সার্ভারের দাম। নতুন কৌশল দেখলে জিজ্ঞেস করুন, এটি কোন খরচকে কোথায় সরাচ্ছে।
প্র ০২SSR-এর পরে আবার একই পাতার জন্য JavaScript পাঠাতে হয় কেন? Hydration-কে অনেকে “এক অ্যাপের দামে দুই অ্যাপ” বলেন কেন?
React পাতাকে দেখে একটি component-এর গাছ হিসেবে, যার state আর event handler আছে। সার্ভারের HTML এই গাছের একটি ছবি মাত্র; তার ভেতরে onClick নেই। Hydration-এ React ব্রাউজারে আবার গাছটি বানায়, HTML-এর সাথে মেলায় আর handler জোড়ে।
এর জন্য তিনটি জিনিস ব্রাউজারে আসে: HTML নিজে, component-এর কোড, আর সেই ডেটা যা দিয়ে HTML বানানো হয়েছিল। অর্থাৎ পণ্যের তালিকা HTML-এও আছে, আবার JavaScript-এর জন্য JSON-এর মতো আলাদা ডেটা হিসেবেও আছে। web.dev-এর “Rendering on the Web” প্রবন্ধে Jason Miller আর Addy Osmani এই সমস্যাকে “one app for the price of two” বলে বর্ণনা করেন। প্রবন্ধটি আরও বলে, পূর্ণ rehydration পাতা দেখতে তৈরি করে কিন্তু সাড়া দিতে দেরি করায়, যা TBT আর INP-এর ক্ষতি করতে পারে।
আমাদের সিমুলেটরে এই ফাঁকই ছিল 2100 ms. ধীর সংযোগে আরও বেশি। ব্যবহারকারী তখন বোতাম চাপেন, কিছু হয় না, আবার চাপেন। পাতা ফাঁকা থাকলে অন্তত তিনি অপেক্ষা করেন; এখানে পাতা ঠকায়। সার্ভার আর ব্রাউজারের HTML না মিললে আরেক বিপদ: React সতর্ক করে, আর অংশবিশেষ নতুন করে আঁকতে হতে পারে, যাতে কাজ আরও বাড়ে।
সমাধানের কয়েকটি ধারা আছে। React 18-এর selective hydration গুরুত্বপূর্ণ অংশ আগে জোড়ে। Server Component-এর কোড ব্রাউজারে আসেই না, তাই জোড়ার কাজ কমে। Islands-ভিত্তিক framework শুধু দরকারি অংশ hydrate করে। Qwik-এর মতো নকশা hydration-এর বদলে সার্ভারের অবস্থা থেকে “resume” করতে চায়। প্রতিটিরই নিজের জটিলতা আছে, আর কোনোটিই সব পাতার জন্য সেরা নয়, তবে সবার লক্ষ্য একই: ব্রাউজারে কম JavaScript আর কম কাজ। পর্ব ৫২ আর পর্ব ৫৬-এ এর বাস্তব দিক দেখবেন।
মূল উপলব্ধি: SSR JavaScript-এর খরচ দূর করে না, শুধু দেখানোটিকে আগে আনে। ক্লিকযোগ্যতা কিনতে হয় JavaScript কমিয়ে।
প্র ০৩Googlebot তো JavaScript চালায়। তাহলে SEO-র জন্য SSR বা SSG নিয়ে এত কথা কেন?
কথাটি সত্য, কিন্তু অসম্পূর্ণ। Google-এর নথি অনুযায়ী কাজটি তিন ধাপে হয়: crawl, render, index. Render-এর জন্য পাতা একটি সারিতে যায়, আর Google-এর সম্পদ পেলে headless Chromium JavaScript চালায়। তাই CSR পাতা index হতে পারে, এবং বহু ক্ষেত্রে হয়ও।
তবু তিনটি ঝুঁকি থাকে। প্রথমত, লিংক আর লেখা পেতে render-এর ওপর নির্ভর করতে হয়; API ধীর বা ব্যর্থ হলে, বা robots.txt JavaScript ফাইল আটকে রাখলে, পাতা ফাঁকা দেখাতে পারে। দ্বিতীয়ত, দুনিয়ায় Google একা নয়। নথিই বলে, সব bot JavaScript চালাতে পারে না। অনেক ছোট সার্চ ইঞ্জিন, social preview bot, আর নানা AI crawler সাধারণত প্রথম HTML-এর ওপরই নির্ভর করে বলে ধরে নেওয়া নিরাপদ; এদের আচরণ অবশ্য বদলাতে থাকে। তৃতীয়ত, গতি নিজেই ব্যবহারকারীর অভিজ্ঞতার অংশ, আর CSR-এর LCP সাধারণত পিছিয়ে থাকে। নতুন পণ্যের দোকানে আরেকটি বাস্তব প্রশ্নও আছে: পাতা যত দ্রুত সঠিকভাবে পড়া হয়, তত ভালো; render-এর অপেক্ষা আর API-র ব্যর্থতা সেখানে বাড়তি অনিশ্চয়তা যোগ করে।
এক সময় অনেক সাইট “dynamic rendering” করত: bot-কে আলাদা pre-render করা HTML, মানুষকে CSR. Google-এর নথি এখন একে একটি workaround বলে, সুপারিশ করে না, আর এর বদলে SSR, static rendering বা hydration-এর কথা বলে। কারণ নথির ভাষায় এতে বাড়তি জটিলতা আর সম্পদের দরকার হয়।
আবার সব পাতায় SEO লাগে না। “বাজারঘর”-এর admin ড্যাশবোর্ড কোনো সার্চ ফলাফলে আসার কথাই নয়, সেখানে এই পুরো আলোচনা অপ্রাসঙ্গিক।
মূল উপলব্ধি: “Google পারে” আর “নির্ভরযোগ্যভাবে সবার কাছে পৌঁছায়” এক কথা নয়। যে পাতা বাইরের মানুষের খুঁজে পাওয়া দরকার, তার মূল বিষয়বস্তু প্রথম HTML-এই রাখুন।
প্র ০৪CDN দিয়ে অনেক খরচ বাঁচে। তাহলে SSR করা ব্যক্তিগত পাতাও CDN-এ cache করলে সমস্যা কোথায়? বাস্তবে কী ঘটেছে?
CDN একটি উত্তর জমা রাখে সাধারণত URL-কে চাবি ধরে। /cart-এর URL সবার একই। রাফির কার্ট জমা হলে পরের ক্রেতা নাদিয়া একই URL চাইলে CDN তাঁকে রাফির কার্টই দেবে। শুধু কার্ট নয়; ঠিকানা, ফোন নম্বর, অর্ডারের ইতিহাসও ফাঁস হতে পারে।
এটি কাল্পনিক ভয় নয়। ২০১৫ সালের বড়দিনের সময় Steam-এ কিছু ব্যবহারকারী অন্যদের অ্যাকাউন্টের পাতা দেখতে পান। Valve-এর পরে দেওয়া ব্যাখ্যা অনুযায়ী, DoS আক্রমণের (প্রচুর ভুয়া অনুরোধে সার্ভার অচল করার চেষ্টা) চাপ সামলাতে একটি web caching সহযোগীর কনফিগারেশনে ভুল হয়, আর লগইন করা ব্যবহারকারীর পাতা cache হয়ে অন্যদের কাছে যায়। ২০১৭ সালে নিরাপত্তা গবেষক Omer Gil “web cache deception” দেখান: আক্রমণকারী শিকারকে এমন একটি URL-এ পাঠায়, যা দেখতে static ফাইলের মতো, কিন্তু সার্ভার ব্যক্তিগত পাতা দেয়, আর CDN তা জমা করে ফেলে।
নিরাপদ অভ্যাস কয়েকটি। ব্যক্তিগত উত্তরে Cache-Control: private বা no-store দিন। CDN-এর নিয়ম URL-এর শেষাংশ দেখে নয়, উত্তরের header দেখে ঠিক করুন। Vary: Cookie তাত্ত্বিকভাবে সাহায্য করলেও প্রায় প্রতিটি ব্যবহারকারীর আলাদা কপি হয়, ফলে hit rate নেমে যায়, তাই খুব লাভ হয় না। ভালো নকশা হলো পাতার public অংশ cache করা, আর ব্যক্তিগত অংশ আলাদা করে আনা: CSR দিয়ে, বা PPR-এর dynamic অংশে।
আমাদের Next.js run-এ ফ্রেমওয়ার্ক নিজেই /cart-এ private, no-store দিয়েছে। কিন্তু আপনার proxy বা CDN-এর নিয়ম এটি উপেক্ষা করার মতো করে সাজানো থাকলে সে সুরক্ষা থাকবে না।
মূল উপলব্ধি: Cache গতির সমস্যা সমাধান করে, কিন্তু “কে দেখতে পারবেন” প্রশ্নটি তার সাথে যুক্ত। প্রতিটি cache-এর সামনে জিজ্ঞেস করুন: এই উত্তর কি সবার জন্য এক?
প্র ০৫কেউ বললেন, “ধীর নেটওয়ার্কের ব্যবহারকারীদের জন্য আমরা SSR-এ যাচ্ছি, সমস্যা শেষ।” এই সিদ্ধান্ত কতটা ঠিক?
আংশিক ঠিক। SSR তাঁদের সবচেয়ে বড় উপকার করে পাতা দ্রুত দেখিয়ে। আমাদের 50 KB/s সিমুলেশনে CSR-এর LCP 7710 ms থেকে SSR-এ 1350 ms হয়েছে। লেখা পড়া, দাম দেখা, সিদ্ধান্ত নেওয়া এই সময়েই সম্ভব। কিন্তু ক্লিকযোগ্য হওয়ার সময় 7950 ms, CSR-এর চেয়েও একটু বেশি। অর্থাৎ “কার্টে যোগ করুন” চাপার সমস্যা থেকেই গেল।
তাই প্রথম প্রশ্ন হওয়া উচিত: JavaScript কতটা? 100 KB-তে নামালে SSR-এর সময় 3550 ms. এটি কৌশল বদলানোর চেয়েও বড় লাভ। আর ফোনের CPU ধীর হলে সার্ভারের কোনো কৌশলই hydration-এর খরচ কমায় না।
দ্বিতীয় প্রশ্ন: বোতামটি JavaScript ছাড়াও কাজ করে কি? একটি সাধারণ HTML <form> hydration-এর আগেই জমা দেওয়া যায়। React 19-এর form action আর Next.js-এর Server Action এই “progressive enhancement” সমর্থন করে, যা পর্ব ৫৪-এ দেখবেন। ধীর নেটওয়ার্কে এটি সরাসরি উপকারী।
তৃতীয় প্রশ্ন: সার্ভার কোথায়? দূরের origin হলে প্রতিটি SSR অনুরোধে সেই দূরত্বের দাম দিতে হবে। সিমুলেশনে origin 200 ms আর CDN 40 ms ধরা ছিল; তাই SSR-এর TTFB 750 ms, SSG-র 120 ms. বাংলাদেশের ব্যবহারকারীদের জন্য সার্ভার বা CDN-এর অবস্থান তাই বাস্তব প্রশ্ন, যদিও কোন প্রদানকারী কোথায় আছে তা বদলাতে থাকে, আর আপনাকে নিজে মেপে দেখতে হবে।
শেষ প্রশ্ন: ছবি আর font কত ভারী? অনেক পাতায় LCP আসলে একটি ছবি। Rendering-এর কৌশল বদলে তা ঠিক হবে না; সেখানে ছবির আকার কমানো, সঠিক ফরম্যাট আর ফোনের মাপে ছবি দেওয়াই আসল কাজ। আর এই সব সিদ্ধান্ত অনুমানে নয়, আসল ব্যবহারকারীর মাঠের ডেটা দেখে নিন।
মূল উপলব্ধি: ধীর নেটওয়ার্কের সমাধান একটি শব্দ নয়। আগে মাপুন, তারপর একে একে HTML কোথায়, JavaScript কতটা, সার্ভার কত দূরে, আর ছবি কত ভারী, এই চারটি প্রশ্ন করুন।
অনুশীলন
হিসাব, পর্যবেক্ষণ, ফল অনুমান আর নকশার সমালোচনা, চার ধরনের কাজ।
-
হিসাব: একটি সাইটে দিনে ২০ লাখ পাতা দেখা হয়। ব্যস্ত সময়ে চাপ গড়ের ৫ গুণ, একটি পাতা বানাতে 40 ms CPU লাগে। (ক) CDN না থাকলে ব্যস্ত সময়ে কত core সম্পূর্ণ ব্যস্ত থাকবে? (খ) CDN hit rate 98% হলে origin-এ দিনে কত অনুরোধ যাবে, আর ব্যস্ত সময়ে কত core লাগবে?
(ক) গড়
2000000 ÷ 86400 = 23.15req/s. ব্যস্ত সময়ে× 5 = 115.74req/s. CPU:115.74 × 0.04 = 4.63core.(খ) Miss 2%, তাই origin-এ
2000000 × 0.02 = 40000অনুরোধ। গড়0.463, ব্যস্ত সময়ে2.31req/s. CPU:2.31 × 0.04 = 0.093core. চাপ ঠিক ৫০ ভাগের ১। -
ফল অনুমান করুন: একটি Next.js পাতায় (
cacheComponentsবন্ধ)export const revalidate = 60. Build-এর ঠিক পরেt = 0-তে একটি অনুরোধ এল। তারপরt = 30 s,t = 61 s, আর পেছনের কাজ শেষ হওয়ার পরt = 62 s-এ অনুরোধ এল। প্রতিটিরx-nextjs-cacheকী হবে? আর কেউ যদিt = 61 s-এর আগে একবারও না আসে, পাতা কি নিজে থেকে নতুন হবে?HIT,HIT,STALE,HIT.t = 30 s-এ কপিটি সময়ের ভেতরে।t = 61 s-এ পুরোনো কপি যায়, আর পেছনে নতুন তৈরি শুরু হয়। আমাদের সত্যিকারের run-এ 0, 61, 62 s-এ ঠিকHIT,STALE,HITপাওয়া গেছে।না। সময়ভিত্তিক ISR শুরু হয় অনুরোধ থেকে। কেউ না এলে পাতা যেমন আছে তেমনই থাকে; 60 s মানে “প্রতি 60 s-এ নতুন হবে” নয়, “60 s পরের প্রথম অনুরোধে নতুন করার কাজ শুরু হবে”।
-
নিজে পরীক্ষা করুন: Terminal-এ
curl -sI https://abcltech.com/চালিয়ে উত্তরেরcache-controlআর cache-সংক্রান্ত header দেখুন। তারপর যেকোনো অনলাইন দোকানের একটি পণ্যের পাতা খুলে Ctrl+U চাপুন। পণ্যের নাম কি Page Source-এ আছে?og:titleআছে? DevTools-এর Elements-এর সাথে পার্থক্য কেন হতে পারে?Page Source মানে সার্ভার থেকে আসা আসল HTML, যা JavaScript না চালানো crawler দেখে। Elements মানে এখনকার DOM, যা JavaScript বদলে থাকতে পারে। নাম শুধু Elements-এ থাকলে পাতাটি CSR. Page Source-এই থাকলে SSR, SSG বা ISR. দুটোর পার্থক্য Header দেখে আরও বোঝা যায়:
age,x-cacheবা এই ধরনের header অনেক CDN দেয়, যদিও নাম প্রদানকারী ভেদে আলাদা। আউটপুট প্রতিটি সাইটে ভিন্ন হবে, তাই এখানে নির্দিষ্ট উত্তর নেই। -
নকশার সমালোচনা: একজন ডেভেলপার “বাজারঘর”-এর জন্য লিখলেন: “পুরো সাইট SSR হবে। গতি বাড়াতে CDN-এ সব পাতার HTML 10 মিনিট cache করব, কার্টও। পণ্যের পাতার দাম cache থেকে নিয়েই অর্ডার নিশ্চিত করব।” এই নকশায় কী কী সমস্যা, আর আপনি কী বদলাবেন?
- কার্ট cache করা গুরুতর ভুল: একজনের কার্ট আরেকজন দেখতে পারেন।
privateবাno-storeদিন। - Cache-এর দাম দিয়ে অর্ডার নিশ্চিত করা: দাম পুরোনো হতে পারে, আর ব্রাউজার থেকে আসা দামে আস্থা রাখা যায় না। অর্ডারের সময় সার্ভারে ডেটাবেস থেকে দাম আর স্টক নতুন করে নিন (পর্ব ৪৩)।
- সব SSR অদরকারি: ব্লগ SSG, পণ্যের পাতা ISR হতে পারে; সার্ভারের খরচ কমবে।
- admin: সব পাতার cache-এ এটিও পড়ছে। ড্যাশবোর্ড ভাগ করা cache-এর বাইরে রাখুন।
- যেটি ভালো: public পাতা CDN-এ রাখার চিন্তাটি ঠিক। শুধু সীমা টানতে হবে পাতার ধরন দেখে।
- কার্ট cache করা গুরুতর ভুল: একজনের কার্ট আরেকজন দেখতে পারেন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমSSR করা পাতা লোডের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ব্রাউজার সার্ভারে অনুরোধ পাঠায়
- সার্ভার ডেটা এনে পূর্ণ HTML বানায়
- HTML পৌঁছায়, পাতা দেখা যায় (FCP)
- ব্রাউজার JavaScript নামায়
- Hydration শেষে বোতাম কাজ করে
কোন বক্তব্যটি সঠিক?
- SPA মানেই CSR
- MPA মানেই SSG
- SPA, MPA বলে navigation-এর কথা; CSR, SSR, SSG বলে HTML তৈরির কথা
- SSR করলে JavaScript পাঠানোর দরকার হয় না
সংযোগ শুরু থেকে প্রথম byte পেতে 3 RTT লাগে। RTT 100 ms আর সার্ভারের কাজ 200 ms হলে TTFB কত?
- 300 ms
- 500 ms
- 600 ms
- 700 ms
একটি পাতায় TTFB 100 ms কিন্তু LCP 5 s. সবচেয়ে সম্ভাব্য ব্যাখ্যা কোনটি?
- সার্ভারের ডেটাবেস খুব ধীর, তাই HTML দেরিতে আসছে
- DNS সার্ভার কাজ করছে না
- পাতাটি SSG, CDN থেকে আসছে, আর লেখাই মূল বিষয়বস্তু
- Shell দ্রুত এসেছে, কিন্তু মূল বিষয়বস্তু JavaScript, API বা বড় ছবির ওপর নির্ভর করছে
ব্যবহারকারী পাতায় পণ্য দেখছেন, কিন্তু বোতাম চাপলে ২ সেকেন্ড কিছুই হচ্ছে না। পাতাটি SSR করা। সবচেয়ে সম্ভাব্য কারণ কী?
- Hydration এখনো শেষ হয়নি
- সার্ভারের TTFB খারাপ
- CDN-এ cache miss হয়েছে
- Open Graph tag নেই
দিনে ১০ লাখ অনুরোধ আসে। CDN hit rate 90% থেকে 99% হলে origin-এ দিনে অনুরোধ কত থেকে কত হয়?
- ৯ লাখ থেকে ৯.৯ লাখ
- ১ লাখ থেকে ৫০ হাজার
- ১ লাখ থেকে ১০ হাজার
- ১০ হাজার থেকে ১ লাখ
একটি অনলাইন দোকানের কোন পাতা ভাগ করা CDN cache-এ রাখা সবচেয়ে বিপজ্জনক?
- ব্লগের লেখা
- ব্যবহারকারীর কার্ট
- পণ্যের পাতা
- “আমাদের সম্পর্কে” পাতা
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Rendering-এর দুই প্রশ্ন: HTML কোথায় আর কখন তৈরি হয়। SSG আগে, SSR অনুরোধে, CSR ব্রাউজারে; ISR, streaming আর PPR এদের মিশ্রণ।
- SPA, MPA আলাদা অক্ষ: এগুলো পাতা বদলানোর ধরন, HTML তৈরির কৌশল নয়।
- Hydration: SSR পাতা আগে দেখায়, কিন্তু ক্লিকযোগ্যতার দাম JavaScript দিয়েই দিতে হয়।
- TTFB একা কিছু বলে না। FCP, LCP আর INP একসাথে দেখুন, ধীর নেটওয়ার্ক আর সাধারণ ফোনে।
- সিদ্ধান্ত হয় পাতা ধরে, আর ব্যক্তিগত পাতা কখনো ভাগ করা cache-এ নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৪ · Architecture pattern — MVC, MVVM, layered আর monolith বনাম serviceপরবর্তী পর্বকোড আর দায়িত্ব কীভাবে ভাগ হয়।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · কোর্স হোম৯৩টি পর্বপুরো কোর্সের পথ। Rendering-এর গভীর অংশ পর্ব ৫৫ আর পর্ব ৫৬-এ।
- System Design · CDN & Edge Cachingপাশের ট্র্যাকEdge, origin shield আর cache-এর নকশা।
- Computer Networks · Web Caching & CDNপাশের ট্র্যাকCache-Control, ETag আর delay-এর অঙ্ক।
- HTML & CSS · Core Web Vitalsপাশের ট্র্যাকLCP, INP, CLS আর critical CSS.