এই পর্বে যা শিখবেন
- Vite + React Router-এর তুলনায় Next.js-এর মতো meta-framework কী যোগ করে
create-next-appচালিয়ে project বানানো আর তৈরি হওয়া প্রতিটি file চেনা- “বাজারঘর”-এর route: dynamic segment, catch-all, route group, private folder
- Layout, template, loading, error, not-found কোথায় বসে আর navigation-এ কী টিকে থাকে
<Link>, prefetch,useRouter,redirect,notFound, product modal আরnext build-এর route table পড়া
১ · Meta-framework কী যোগ করে: Vite + React Router থেকে Next.js
পর্ব ২০-এ “বাজারঘর” একটি SPA ছিল। Vite একটি ফাঁকা HTML আর JavaScript-এর bundle বানাত। তারপর browser সেই JavaScript চালিয়ে পর্দা আঁকত। Route-এর তালিকা আমরা নিজের হাতে লিখেছিলাম। ডেটার জন্য loader লিখেছিলাম। Backend ছিল আলাদা Express server (মডিউল ৪)। আর cPanel-এ তোলার সময় .htaccess-এ SPA fallback বসাতে হয়েছিল।
এই প্রতিটি সিদ্ধান্ত আপনি নিজে নিয়েছিলেন, আর প্রতিটির জোড়া নিজে লাগিয়েছিলেন। Meta-framework মানে React-এর ওপরে আরেকটি স্তর, যা এই জোড়াগুলো আগে থেকেই লাগিয়ে দেয়: routing, server-এ render, ডেটা আনা, build আর server চালানো। Next.js এমন সবচেয়ে প্রচলিত meta-framework. React-এর নিজের docs-ও নতুন অ্যাপ শুরু করতে এমন framework-এর পরামর্শ দেয়।
নিজে বাড়ি বানানোর কথা ভাবুন। Vite + React Router মানে আপনি ইট, রড আর সিমেন্ট আলাদা কিনে নিজে মিস্ত্রি ডাকছেন: স্বাধীনতা পুরো, কিন্তু প্রতিটি জোড়ার দায়ও আপনার। Next.js যেন একটি ডেভেলপার কোম্পানির ফ্ল্যাট: সিঁড়ি, পানির লাইন আর বিদ্যুতের তার আগেই নকশা মেনে বসানো। আপনি ঘর সাজাবেন। দেয়াল ভাঙতে চাইলে কোম্পানির নিয়ম মানতে হবে।
| বিষয় | Vite + React Router (পর্ব ২০) | Next.js App Router |
|---|---|---|
| Route কোথা থেকে আসে | কোডে লেখা route-এর তালিকা | app/ folder-এর গঠন |
| প্রথম HTML | প্রায় ফাঁকা, browser আঁকে (CSR) | Server বা build আগেই HTML বানায় (SSR, SSG, PPR) |
| Component কোথায় চলে | সবই browser-এ | শুরুতে server-এ; "use client" দিলে browser-এও (পর্ব ৫২) |
| ডেটা আনা | loader বা TanStack Query | Server Component-এ সরাসরি await (পর্ব ৫৩) |
| Backend | আলাদা Express server | একই project-এ route handler আর Server Action (পর্ব ৫৪, ৫৭) |
| Code splitting | lazy() হাতে লিখে | প্রতিটি route segment নিজে থেকেই আলাদা |
| Deploy | Static file + SPA fallback, যেকোনো hosting | Node.js server (next start), অথবা সীমাবদ্ধ static export |
লাভের সাথে দামও আছে। Next.js-এর নিয়ম শিখতে হয়। Server চালানোর জায়গা লাগে, তাই সাধারণ shared hosting-এ সব সুবিধা পাওয়া যায় না। আর framework যত কাজ নিজে করে, ভুল হলে খুঁজে বের করাও তত কঠিন। React Router-এর framework mode, Remix-এর উত্তরসূরি হিসেবে, প্রায় একই জায়গায় দাঁড়িয়ে। Vue-র জন্য Nuxt আছে (পর্ব ৬৬)। কোনটি “সেরা”, তা নিয়ে community-তে মতভেদ আছে। Rendering-এর মূল মানচিত্র পর্ব ০৩-এ দেখেছেন, এখানে আবার শেখানো হবে না।
ইতিহাস: Next.js প্রথম প্রকাশ পায় ২০১৬ সালের অক্টোবরে। এটি বানিয়েছিল ZEIT, যে কোম্পানি পরে ২০২০ সালে নাম বদলে Vercel হয়। শুরু থেকেই এর পরিচয় ছিল file দিয়ে routing: pages/about.js মানেই /about. ২০২২ সালে Next.js 13-এ নতুন app/ folder, অর্থাৎ App Router, পরীক্ষামূলকভাবে আসে, আর ২০২৩ সালে 13.4-এ স্থিতিশীল হয়। Next.js 15 (২০২৪) params-কে async করে, আর 16 (২০২৫) পুরোনো sync পথ পুরোপুরি সরিয়ে দেয়, Turbopack-কে default বানায়। Next.js open-source, আর Vercel ছাড়াও যেকোনো Node.js server-এ চলে।
২ · create-next-app: সত্যিকারের run আর তৈরি হওয়া folder
লেখার সময়ে (নভেম্বর ২০২৬) npm-এ next-এর latest হলো 16.4.4, সাথে React 19.3.0. Project তৈরির জন্য আছে scaffold tool create-next-app. প্রশ্ন না করে default নিতে --yes দিয়েছি। Node.js 24-এ সত্যিকারের run (path বদলে ~/bazarghar লেখা হয়েছে, লম্বা অংশ “…” দিয়ে ছাঁটা):
npx create-next-app@16.4.4 bazarghar --yes --disable-git --use-npm
cd bazarghar
npm run buildCreating a new Next.js app in ~/bazarghar.
Using npm.
Initializing project with template: app-tw
Installing dependencies:
- next
- react
- react-dom
Installing devDependencies:
- @tailwindcss/turbopack
- @types/node
…
- typescript
added 359 packages, and audited 360 packages in 4m
…
Generating route types...
✓ Types generated successfully
Skipping git initialization.
Success! Created bazarghar at ~/bazarghar
--disable-git না দিলে tool নিজে থেকে একটি git repository-ও খোলে। তৈরি হওয়া folder-টি এমন:
bazarghar/
├── AGENTS.md ← AI coding agent-এর জন্য নির্দেশনা (অংশ ১০)
├── README.md
├── app/ ← সব route এখানে
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx ← root layout: <html> আর <body>
│ └── page.tsx ← "/" ঠিকানার পাতা
├── eslint.config.mjs
├── next-env.d.ts ← Next.js-এর type, হাতে বদলাবেন না
├── next.config.ts
├── package.json
├── package-lock.json
├── public/ ← ছবি ইত্যাদি, যেমন আছে তেমন পরিবেশিত
└── tsconfig.json
দুটি file-এ চোখ রাখুন। app/layout.tsx হলো root layout: পুরো সাইটের বাইরের খোলস, আর <html> ও <body> ট্যাগ লেখার একমাত্র জায়গা। আর next.config.ts-এ এই version-এর template দুটি জিনিস চালু করে রেখেছে:
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
// turbopack: { …Tailwind-এর loader… }
};Cache Components ঠিক করে কোন অংশ build-এর সময় তৈরি থাকবে আর কোনটি request-এর সময় stream হবে। এর পুরো আলোচনা পর্ব ৫৩ আর ৫৫-এর। Partial Prefetching ঠিক করে link-এর পেছনে আগেভাগে কতটুকু আনা হবে, যা অংশ ৭-এ দেখব। এই পর্বে শুধু একটি প্রভাব মনে রাখুন: এই default-এ যে route request-এর তথ্য (params, searchParams, usePathname) পড়ে, তার চারপাশে loading.tsx বা <Suspense> লাগে। না দিলে কী হয়, তা আমাদের প্রথম build-ই দেখিয়েছে:
Error: Route "/category/[...slug]": Next.js encountered URL data `usePathname()` in a Client Component outside of `<Suspense>`.
This blocks prerendering because the value is only available at runtime.
…
Export encountered an error on /(shop)/category/[...slug]/page: /category/[...slug], exiting the build.
Error-বার্তাটি নিজেই দুটি পথ বলে দিচ্ছে: অংশটিকে <Suspense>-এ মুড়ে stream করা, অথবা route-টিকে ইচ্ছা করে blocking ঘোষণা করা। এমন পরিষ্কার বার্তা পেলে AI-কে জিজ্ঞেস করার আগে বার্তাটি নিজে পড়ে দেখা ভালো অভ্যাস। আমরা প্রথম পথ নিয়েছি।
Header-এর menu-কে <Suspense>-এ মুড়ে আর দুটি route-এ loading.tsx যোগ করে build সফল হলো। npm run dev চালালে development server চালু হয়। Next.js 16 থেকে next dev আর next build দুটোই default হিসেবে Turbopack bundler ব্যবহার করে।
৩ · app folder: folder মানে segment, page মানে প্রকাশ্য
URL-কে / দিয়ে ভাঙলে প্রতিটি টুকরোকে বলে route segment. /account/orders-এ দুটি segment: account আর orders. App Router-এর মূল নিয়ম এক লাইনের: app/-এর ভেতরের প্রতিটি folder একটি segment, আর সেই folder-এ page.tsx থাকলে তবেই ঠিকানাটি প্রকাশ্য। একে বলে file-based routing.
ডাকঘরের বাছাই ঘরের কথা ভাবুন। ঠিকানা “ঢাকা / মিরপুর / রোড ১০”. বাছাইকর্মী প্রথমে “ঢাকা” তাক, তার ভেতরে “মিরপুর” খোপ, তার ভেতরে “রোড ১০”-এর বাক্স খোঁজেন। তাক আর খোপ থাকলেই চিঠি বিলি হয় না, শেষ খোপে বিলির বাক্স (page) থাকতে হয়। আর প্রতিটি তাকের গায়ে একই নোটিশ সাঁটা থাকতে পারে (layout), যা ওই তাকের সব চিঠির ওপর প্রযোজ্য।
প্রতিটি folder-এ কয়েকটি নির্দিষ্ট নামের file রাখা যায়। Next.js এগুলোকে React-এর বিশেষ জিনিসে বদলে নেয়:
| File | কাজ | ভেতরে আসলে যা হয় |
|---|---|---|
page.tsx | ওই ঠিকানার নিজের পাতা | Segment-টিকে প্রকাশ্য করে |
layout.tsx | নিচের সব পাতার ভাগ করা খোলস (layout) | children পায়; navigation-এ টিকে থাকে |
template.tsx | Layout-এর মতো, কিন্তু প্রতি navigation-এ নতুন (template) | Segment অনুযায়ী আলাদা key পায় |
loading.tsx | অপেক্ষার সময়ের UI | Suspense-এর fallback |
error.tsx | অপ্রত্যাশিত error-এর UI; "use client" বাধ্যতামূলক | React error boundary |
not-found.tsx | notFound() ডাকলে যা দেখাবে | “পাওয়া যায়নি”-র জন্য আলাদা boundary |
এছাড়া আছে default.tsx (অংশ ৮), global-error.tsx (root layout-এর error), আর route.ts, যা পাতার বদলে API দেয় (পর্ব ৫৭)। এই file-গুলো একটি নির্দিষ্ট ক্রমে একটির ভেতরে আরেকটি বসে। ক্রমটি Next.js-এর docs-এ লেখা, আর ছবিতে তা এমন:
ছবি ৫১.১ · একটি segment-এর বিশেষ file কীভাবে একটির ভেতরে আরেকটি বসে
Component hierarchy of App Router special files
এর একটি জরুরি ফল আছে। একই folder-এর error.tsx নিজের পাশের layout.tsx-এর error ধরতে পারে না, কারণ layout তার বাইরে বসে। Layout-এর error ধরে বাবা segment-এর error.tsx. আর root layout-এর error ধরতে আলাদা global-error.tsx লাগে। Error boundary আর Suspense-এর মূল ধারণা পর্ব ২৩-এ দেখেছেন।
Nested route-এ এই ছয় ভাঁজ প্রতিটি segment-এ বারবার বসে, বাবার ভাঁজের ভেতরে সন্তানের ভাঁজ। “বাজারঘর”-এ একটি URL কীভাবে file-এ বদলায়, তা ধাপে ধাপে দেখুন:
- URL
/products/2ঠিকানা - Segment
products · 2ভাঙা - Folder
[id]মেলানো - React tree
layout › pageভাঁজ - পাতা
h1ফল
GET /products/2
["products", "2"]
app/ layout.tsx, not-found.tsx
└ (shop)/ layout.tsx, @modal/default.tsx
└ products/ loading.tsx
└ [id]/ page.tsx, error.tsx, not-found.tsx
"2" কোনো স্থির folder-এর নাম নয়, তাই [id]-এ মেলে
<RootLayout>
<ShopLayout modal={<Default />}>
<Suspense fallback={<Loading />}> products/
<ErrorBoundary fallback={ProductError}> [id]/
<NotFoundBoundary>
<ProductPage params={Promise} />
await params → { id: "2" }
h1 = মসুর ডাল ১ কেজি
- অনুরোধ। কেউ WhatsApp-এ পাওয়া link থেকে সরাসরি
/products/2খুললেন। - টুকরো করা। Router URL-কে দুটি segment-এ ভাঙে। Query string (
?…) এই কাজে অংশ নেয় না। - Folder মেলানো।
(shop)একটি route group, URL-এ তার কোনো টুকরো নেই, তাই Next.js তার ভেতরে ঢুকেproductsখোঁজে।2নামের folder নেই, তাই dynamic folder[id]মেলে। পথের প্রতিটি স্তরের বিশেষ file জড়ো হয়। - ভাঁজ বানানো। বাইরের segment-এর file বাইরে, ভেতরেরটি ভেতরে।
products/loading.tsxহয় Suspense,[id]/error.tsxহয় error boundary, আর সবার ভেতরে page. - Page চলল।
paramsএকটি Promise,awaitকরলে পাওয়া যায়{ id: "2" }. সংখ্যা নয়, string. তাই ডেটা খোঁজার সময় তুলনাটি string হিসেবে করতে হয়।
৪ · Dynamic segment, catch-all আর async params
সব পণ্যের জন্য আলাদা folder বানানো অসম্ভব। Folder-এর নাম বন্ধনীতে লিখলে সেটি dynamic segment হয়, অর্থাৎ ওই জায়গায় যা আসবে তা-ই মান। তিন রকম আছে:
| Folder | URL | params |
|---|---|---|
products/[id] | /products/2 | { id: "2" } |
category/[...slug] (catch-all) | /category/grocery/rice | { slug: ["grocery", "rice"] } |
category/[...slug] | /category | মেলে না, ৪০৪ |
help/[[...topic]] (optional catch-all) | /help | { topic: undefined } |
help/[[...topic]] | /help/payment/bkash | { topic: ["payment", "bkash"] } |
এবার “বাজারঘর”-এর আসল পণ্যের পাতা। এটি সত্যিকারের project-এর file, হুবহু:
import { notFound } from "next/navigation";
import { findProduct, products } from "../../../_lib/products";
export async function generateStaticParams() {
return products.map((p) => ({ id: String(p.id) }));
}
export default async function ProductPage({ params }: PageProps<"/products/[id]">) {
const { id } = await params;
const product = findProduct(id);
if (!product) notFound();
console.log("[render] ProductPage id=" + id);
return (
<article>
<h1>{product.name}</h1>
<p>দাম: ৳{product.price}</p>
</article>
);
}console.log লাইনটি আমাদের পরীক্ষার জন্য রাখা, যাতে কোন পাতা কখন render হলো তা log-এ দেখা যায়। আসল project-এ এমন log structured logger দিয়ে লেখা ভালো (পর্ব ৩১)। এবার চারটি জিনিস খেয়াল করুন।
asyncfunction আরawait params: Next.js 15 থেকেparamsআরsearchParamsহলো Promise. 15-এ পুরোনো sync পথ সাময়িকভাবে চলত। 16-এ তা পুরোপুরি বাদ। পুরোনো ঢঙেparams.idলিখে আমাদেরtscচালালে এই error আসে:error TS2339: Property 'id' does not exist on type 'Promise<{ id: string; }>'.PageProps<"/products/[id]">: এই type কোথাও import করতে হয় না।next dev,next buildবাnext typegenfolder দেখে এটি নিজে বানায়। ফলে route-এর নাম ভুল লিখলে TypeScript ধরে ফেলে।notFound(): পণ্য না পেলে এটি ডাকা হয়। এটি একটি বিশেষ error ছুড়ে দেয়, তাই এর পরের লাইন আর চলে না। কাছেরnot-found.tsxদেখায়।- generateStaticParams: build-কে জানায় কোন কোন
id-এর পাতা আগেই বানিয়ে রাখা যায়। বাকি id request-এর সময় তৈরি হয়। এর বিস্তারিত পর্ব ৫৫-এ।
Client Component-এ (যেখানে async চলে না) একই Promise খুলতে React-এর use(params), অথবা যেকোনো গভীরতা থেকে useParams() hook ব্যবহার করা যায়। searchParams-ও একইভাবে await করে পড়তে হয়, যা অংশ ৭-এ দেখবেন। Promise আর await-এর ভিত্তি JavaScript কোর্সে আছে।
এবার মূল ধারণাটি নিজের হাতে চালান। এটি একটি শেখার মডেল, আসল Next.js নয়: file-এর একটি তালিকা থেকে route বানায়, route group আর private folder বাদ দেয়, তারপর URL মিলিয়ে params বের করে।
// শেখার মডেল: app/ folder-এর file তালিকা থেকে route, তারপর URL মেলানো
const files = ['app/page.tsx', 'app/layout.tsx', 'app/(shop)/layout.tsx',
'app/(shop)/products/page.tsx', 'app/(shop)/products/new/page.tsx',
'app/(shop)/products/[id]/page.tsx', 'app/(shop)/category/[...slug]/page.tsx',
'app/help/[[...topic]]/page.tsx', 'app/_lib/page.tsx', 'app/_lib/products.ts'];
function toRoute(file) {
const parts = file.split('/').slice(1);
const name = parts.pop();
if (!/^page\.(tsx|jsx|ts|js)$/.test(name)) return null; // শুধু page
if (parts.some((p) => p.startsWith('_'))) return null; // private folder
return '/' + parts.filter((p) => !/^\(.+\)$/.test(p)).join('/'); // group বাদ
}
const kind = (s) => s.startsWith('[[...') ? 3 : s.startsWith('[...') ? 2 : s.startsWith('[') ? 1 : 0;
const segs = (r) => r.split('/').filter(Boolean);
const key = (r) => segs(r).map(kind).join(''); // 0 স্থির, 1 [id], 2 [...], 3 [[...]]
const routes = files.map(toRoute).filter(Boolean).sort((x, y) => (key(x) < key(y) ? -1 : 1));
console.log(routes.join(' '));
function match(route, url) {
const a = segs(route), b = segs(url), params = {};
for (let i = 0; i < a.length; i++) {
const s = a[i], k = kind(s);
if (k >= 2) { const rest = b.slice(i); if (k === 2 && !rest.length) return null;
params[s.replace(/[[\].]/g, '')] = rest.length ? rest : null; return params; }
if (i >= b.length) return null;
if (k === 1) params[s.slice(1, -1)] = b[i]; else if (s !== b[i]) return null;
}
return a.length === b.length ? params : null;
}
for (const url of ['/products/new', '/products/2', '/category/grocery/rice', '/help', '/help/payment/bkash', '/shop/products', '/_lib']) {
const hit = routes.find((r) => match(r, url));
console.log(url.padEnd(22), '->', hit ?? '404', hit ? JSON.stringify(match(hit, url)) : '');
}চালালে দেখবেন:
/ /products /products/new /products/[id] /category/[...slug] /help/[[...topic]]
/products/new -> /products/new {}
/products/2 -> /products/[id] {"id":"2"}
/category/grocery/rice -> /category/[...slug] {"slug":["grocery","rice"]}
/help -> /help/[[...topic]] {"topic":null}
/help/payment/bkash -> /help/[[...topic]] {"topic":["payment","bkash"]}
/shop/products -> 404
/_lib -> 404
প্রথম লাইনে route-গুলো অগ্রাধিকার ধরে সাজানো: স্থির নাম (০), তারপর [id] (১), তারপর [...slug] (২), শেষে [[...topic]] (৩)। তাই /products/new স্থির folder-টিতে যায়, [id]-এ নয়। app/_lib/page.tsx-এ page থাকলেও সেটি route হয়নি, কারণ _ দিয়ে শুরু folder-এর পুরো অংশ routing থেকে বাদ। (shop) নামটিও URL-এ নেই, তাই /shop/products ৪০৪. আমাদের আসল Next.js server-ও /shop/products আর /_lib/products-এ 404 দিয়েছে। এই মডেলে optional catch-all-এর ফাঁকা মান null লেখা হয়েছে, কারণ JSON.stringify undefined বাদ দিয়ে দেয়। আসল Next.js-এ মানটি undefined, আর আসল ক্রম ঠিক করার নিয়মে আরও কিছু খুঁটিনাটি আছে।
৫ · Route group, private folder আর colocation
“বাজারঘর”-এর দোকানের পাতাগুলোর (পণ্য, বিভাগ) বাঁ পাশে একটি ঝুড়ির বোতাম চাই, কিন্তু অ্যাকাউন্টের পাতায় চাই না। আবার URL-এ /shop/products লিখতেও চাই না। সমাধান route group: folder-এর নাম গোল বন্ধনীতে, (shop). এটি folder গোছায় আর নিজের layout.tsx রাখতে দেয়, কিন্তু URL-এ কোনো টুকরো যোগ করে না।
আর যে file route নয়, যেমন ডেটার module বা ছোট component, সেগুলো private folder-এ রাখা যায়: নামের শুরুতে _, যেমন _lib, _components. আসলে page না থাকলে কোনো folder এমনিতেই প্রকাশ্য নয়, তাই route-এর পাশে file রাখা, অর্থাৎ colocation, নিরাপদ। _ উপসর্গটি শুধু পরিষ্কারভাবে বলে দেয়, “এখানে কখনো route হবে না”, আর ভবিষ্যতের কোনো বিশেষ file-নামের সাথে সংঘাত এড়ায়।
app/
├── layout.tsx সবার খোলস: header, menu
├── page.tsx /
├── not-found.tsx
├── _lib/products.ts ডেটা, route নয়
├── _components/ nav-links, cart-counter, modal …
├── (shop)/
│ ├── layout.tsx ঝুড়ির বোতাম + @modal slot
│ ├── @modal/ অংশ ৮
│ ├── products/ /products, /products/[id]
│ └── category/[...slug]/ /category/…
├── account/ layout.tsx, template.tsx, orders/, profile/
├── help/[[...topic]]/
└── offers/page.tsx redirect
দুটি ফাঁদ। এক, দুটি ভিন্ন group-এ একই URL বানালে (যেমন (shop)/about আর (info)/about) Next.js error দেয়, কারণ একটি ঠিকানার দুটি পাতা হতে পারে না। দুই, প্রতিটি group-এ আলাদা root layout রাখা যায়, কিন্তু এক root layout থেকে অন্যটিতে গেলে browser পুরো পাতা নতুন করে load করে।
Segment থেকে breadcrumb
Catch-all-এর একটি রোজকার ব্যবহার হলো breadcrumb: “হোম › বিভাগ › মুদি › চাল”. প্রতিটি segment একটি ধাপ, আর প্রতিটির link হলো শুরু থেকে ওই segment পর্যন্ত জোড়া path. নিচের শেখার মডেলটি সেই হিসাব করে। বাংলা নামের segment URL-এ শতাংশ চিহ্ন দিয়ে encode হয়ে আসে, তাই লেখাটি দেখানোর আগে decode করতে হয়।
// শেখার মডেল: URL-এর segment থেকে breadcrumb
const labels = { products: 'পণ্য', category: 'বিভাগ', grocery: 'মুদি', rice: 'চাল' };
function crumbs(url) {
const pathname = url.split(/[?#]/)[0]; // query আর hash বাদ
const parts = pathname.split('/').filter(Boolean);
const out = [{ label: 'হোম', href: '/' }];
parts.forEach((s, i) => {
const raw = decodeURIComponent(s); // %E0%A6... থেকে বাংলা
out.push({ label: labels[raw] ?? raw, href: '/' + parts.slice(0, i + 1).join('/') });
});
return out;
}
for (const url of ['/category/grocery/rice', '/products/2?ref=fb',
'/category/%E0%A6%AE%E0%A6%BE%E0%A6%9B']) {
const c = crumbs(url);
console.log(c.map((x) => x.label).join(' › '));
console.log(' hrefs:', c.map((x) => x.href).join(' '), '| aria-current:', c.at(-1).label);
}চালালে দেখবেন:
হোম › বিভাগ › মুদি › চাল
hrefs: / /category /category/grocery /category/grocery/rice | aria-current: চাল
হোম › পণ্য › 2
hrefs: / /products /products/2 | aria-current: 2
হোম › বিভাগ › মাছ
hrefs: / /category /category/%E0%A6%AE%E0%A6%BE%E0%A6%9B | aria-current: মাছ
দ্বিতীয় URL-এর ?ref=fb breadcrumb-এ ঢোকেনি, কারণ query string segment নয়। তৃতীয় URL-এর %E0%A6%AE… আসলে “মাছ” শব্দের UTF-8 byte. লেখায় decode হয়েছে, কিন্তু href-এ encode করা রূপই রাখা হয়েছে, যা নিরাপদ। শেষ ধাপটি বর্তমান পাতা, তাই সেখানে aria-current="page" বসানো উচিত। “বাজারঘর”-এর আসল category/[...slug]/page.tsx একই যুক্তিতে চলে। সত্যিকারের run-এ /category/grocery/rice-এর breadcrumb-এর link হলো /, /category/grocery আর /category/grocery/rice. Accessibility-র নিয়ম HTML-CSS কোর্সে আছে।
৬ · Nested layout টিকে থাকে, template নতুন করে বসে
React Router-এর <Outlet> মনে আছে? Next.js-এ তার জায়গায় আছে children prop. প্রতিটি layout.tsx নিচের segment-কে children হিসেবে পায়। “বাজারঘর”-এর অ্যাকাউন্ট অংশ:
import Link from "next/link";
import VisitCounter from "../_components/visit-counter";
export default function AccountLayout({ children }: LayoutProps<"/account">) {
console.log("[render] AccountLayout");
return (
<section>
<h2>আমার অ্যাকাউন্ট</h2>
<nav>
<Link href="/account/orders">অর্ডার</Link> <Link href="/account/profile">প্রোফাইল</Link>
</nav>
<VisitCounter label="layout" />
{children}
</section>
);
}VisitCounter একটি ছোট Client Component, যার ভেতরে useState দিয়ে একটি সংখ্যা আছে আর mount হলে console-এ লেখে। একই component app/account/template.tsx-এও বসানো। তারপর Edge browser-এ Playwright দিয়ে production build চালিয়ে দুটি বোতাম চেপে পাশের পাতায় গিয়েছি:
1// app/account/layout.tsx2function AccountLayout({ children }) {3 <VisitCounter label="layout" />{children}4}5// app/account/template.tsx6function AccountTemplate({ children }) {7 <VisitCounter label="template" />{children}8}9// page: orders → profile
| URL | |
| layout-এর counter | |
| template-এর counter | |
| h1 |
- পাতা খুলল।
/account/orders-এ দুটি counter-ই একবার mount হলো, দুটির মান 0. - Layout-এর বোতাম ৩ বার। Layout-এর counter 3 হলো। Template-এর counter 0-ই।
- Template-এর বোতাম ২ বার। Template-এর counter 2.
- “প্রোফাইল” link-এ ক্লিক। URL আর h1 বদলাল। Layout-এর counter 3-ই রইল, কারণ layout একই জায়গায় একই component হিসেবে থাকে। Template-এর counter 0 হলো, আর console-এ নতুন একটি
[mount] template counterএলো। - কারণ। Next.js template-কে segment অনুযায়ী
keyদেয়।ordersথেকেprofile-এ key বদলায়, তাই React পুরোনোটি সরিয়ে নতুন বসায়। Layout-এর console-এ দ্বিতীয় mount আসেনি।
এটি পর্ব ১৫-এর reconciliation-এর নিয়ম: একই জায়গায় একই type থাকলে state থাকে, key বদলালে state মুছে যায়। নিচের live block-টি সেই একই কাঠামোর শেখার মডেল, আসল Next.js নয়। এখানে template-কে আমরা নিজেরাই key={seg} দিয়েছি।
<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
// শেখার মডেল: layout একই থাকে, template পায় নতুন key
function Counter({ label }) {
const [n, setN] = useState(0);
useEffect(() => { console.log('mount', label); }, [label]);
return <button onClick={() => setN(n + 1)}>{label}: {n}</button>;
}
function AccountLayout({ children }) {
return <section><b>আমার অ্যাকাউন্ট </b><Counter label="layout" />{children}</section>;
}
function Template({ children }) {
return <div><Counter label="template" />{children}</div>;
}
const pages = { orders: () => <p>অর্ডারের তালিকা</p>, profile: () => <p>প্রোফাইল</p> };
function App() {
const [seg, setSeg] = useState('orders');
const Page = pages[seg];
console.log('render /account/' + seg);
return (<>
{Object.keys(pages).map((p) => <button key={p} onClick={() => setSeg(p)}>/account/{p}</button>)}
<AccountLayout>
<Template key={seg}><Page /></Template>
</AccountLayout>
</>);
}
createRoot(document.getElementById('root')).render(<App />);
</script>পাতা খুলতেই নিচে আসে:
render /account/orders
mount layout
mount template
এবার “layout” আর “template” বোতাম কয়েকবার চেপে /account/profile চাপুন। Layout-এর সংখ্যা থাকবে, template-এর সংখ্যা 0 হবে, আর console-এ render /account/profile-এর পরে শুধু mount template আসবে, mount layout আর আসবে না। App আবার render হয়, তাই AccountLayout-ও আবার চলে, কিন্তু mount হয় না। এই “render হওয়া” আর “নতুন করে mount হওয়া”-র পার্থক্যই মূল কথা।
| প্রয়োজন | যা ব্যবহার করবেন |
|---|---|
| Menu, ঝুড়ি, খোলা sidebar, চলমান video টিকিয়ে রাখা | layout.tsx |
প্রতি পাতায় নতুন form, প্রতি পাতায় useEffect আবার চালানো (যেমন page-view গোনা) | template.tsx |
| প্রতি navigation-এ loading-এর fallback আবার দেখানো | template.tsx |
Search params template বদলায় না: docs অনুযায়ী ?sort=price-এর মতো বদলে template আবার mount হয় না, শুধু segment বা তার dynamic মান বদলালে হয়। আর cacheComponents চালু থাকলে Next.js 16 আগের পাতাগুলোও পুরো মুছে না দিয়ে React-এর <Activity> দিয়ে লুকিয়ে রাখে (docs অনুযায়ী সর্বোচ্চ ৩টি route পর্যন্ত)। ফলে Back চাপলে পুরোনো পাতার state ফিরে আসে। অংশ ৮-এ এর একটি সত্যিকারের প্রভাব দেখবেন।
৭ · Navigation: Link, prefetch, hook আর redirect
next/link-এর <Link> দেখতে সাধারণ <a>, কিন্তু দুটি কাজ বাড়তি করে। এক, ক্লিক থামিয়ে client-side navigation করে, যাকে Next.js বলে soft navigation. দুই, prefetch: link পর্দায় দেখা দিলেই পরের পাতার দরকারি অংশ পটভূমিতে এনে রাখে। Docs স্পষ্ট বলে, prefetch শুধু production-এ চলে, next dev-এ নয়। তাই আমরা next build আর next start চালিয়ে Edge-এ Playwright দিয়ে network log ধরেছি:
- Load
GET /document - Tree
/_treeprefetch - Segment
__PAGE__prefetch - ক্লিক
RSC: 1বাকি অংশ - পাতা
/productsএকই JS জগৎ
document /
header-এ ৪টি Link: / · /products · /category/grocery · /account/orders
fetch /?_rsc=5CB6… Segment-Prefetch: /_tree
fetch /products?_rsc=5CB6… Segment-Prefetch: /_tree
fetch /category/grocery?_rsc=5CB6… Segment-Prefetch: /_tree
fetch /account/orders?_rsc=5CB6… Segment-Prefetch: /_tree
প্রতিটিতে RSC: 1 আর Next-Router-Prefetch: 1
fetch /category/grocery …/category/$c$slug/__PAGE__ fetch /category/grocery …/@modal/__DEFAULT__ fetch /products …/products/__PAGE__ fetch /account/orders /account/orders/__PAGE__
fetch /products?_rsc=OPcF… RSC: 1 Prefetch header নেই
location = /products window.__mark = same page t = 1.98 s /products/1 … /products/4 Segment-Prefetch: /_tree
- প্রথমবার খোলা। পুরো সময়ে মাত্র একটি document request. Header-এর চারটি Link আর পাতার একটি Link পর্দায় এলো।
- গাছ আগেভাগে। ০.১৩ সেকেন্ডে প্রতিটি আলাদা ঠিকানার জন্য একটি করে
/_treerequest গেল। এতে থাকে route-এর কাঠামো: কোন layout, কোন segment._rsc=মানটি cache আলাদা রাখার একটি চিহ্ন। - Segment আগেভাগে। তারপর পাতার অংশ আলাদা আলাদা এলো, segment ধরে।
(shop)-এর ভেতরের পাতার জন্য@modal-এর default-ও এলো। এটিই Partial Prefetching: পুরো পাতা নয়, যতটুকু static আর cache করা যায় ততটুকু। - ক্লিক। Link ক্লিক থামাল, document request হলো না। গেল শুধু একটি request, prefetch-এর চিহ্ন ছাড়া।
/productsপাতাsearchParamsপড়ে, তাই তার ওই অংশ request-এর সময়ই তৈরি হয়। বাকি সব আগেই হাতে ছিল। - একই পাতা, নতুন পর্দা। শুরুতে
window-এ রাখা__markটিকে আছে, মানে পাতা আবার load হয়নি। নতুন পর্দার চারটি পণ্যের link-এর জন্য prefetch আবার শুরু হলো।
এই request-গুলো যে ডেটা আনে তার নাম RSC payload: server-এ render হওয়া component গাছের একটি সংক্ষিপ্ত বিবরণ। এর ভেতরের গঠন পর্ব ৫২-এর বিষয়। prefetch prop-এর মান তিন রকম: "auto" বা না দেওয়া (default; static route পুরোটা, dynamic route-এর কাছের loading পর্যন্ত), true (পুরো route), আর false (কখনো নয়, hover-এও নয়)। Partial Prefetching চালু থাকলে default-এর আচরণ একটু বদলায়, যা ওপরের log-এ দেখলেন।
কোড থেকে navigation: চারটি hook আর দুটি function
সবগুলো আসে next/navigation থেকে। Hook-গুলো শুধু Client Component-এ চলে।
| API | কোথায় | কাজ |
|---|---|---|
useRouter() | Client | push, replace, back, refresh, prefetch |
usePathname() | Client | চলতি path, যেমন /category/grocery; query ছাড়া |
useSearchParams() | Client | শুধু পড়ার জন্য URLSearchParams |
useParams() | Client | Dynamic segment-এর মান |
redirect(url) | Server (মূলত) | অন্য ঠিকানায় পাঠায়; সাধারণত 307 |
notFound() | Server | কাছের not-found.tsx দেখায় |
router.push ইতিহাসে নতুন ঘর যোগ করে, router.replace চলতি ঘরটি বদলে দেয়, আর router.refresh client-এর state না মুছে server থেকে চলতি route আবার আনে, যা ডেটা বদলের পরে কাজে লাগে (পর্ব ৫৪)। ব্যবহারকারী যেখানে ক্লিক করবেন সেখানে সবসময় <Link> দিন। router.push শুধু কোনো কাজ শেষ হওয়ার পরে, যেমন dropdown বদলালে। কারণ Link-এ Ctrl-ক্লিক, নতুন tab আর screen reader সবই ঠিকভাবে কাজ করে।
Header-এর সক্রিয় link চিহ্নিত করতে usePathname, আর পণ্য সাজানোর dropdown-এ বাকি তিনটি hook একসাথে:
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
export default function SortPicker() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const sort = searchParams.get("sort") ?? "default";
function choose(value: string) {
const next = new URLSearchParams(searchParams.toString());
if (value === "default") next.delete("sort");
else next.set("sort", value);
const qs = next.toString();
router.push(qs ? `${pathname}?${qs}` : pathname);
}
return (
<label>
সাজান:{" "}
<select id="sort" value={sort} onChange={(e) => choose(e.target.value)}>
<option value="default">স্বাভাবিক</option>
<option value="price">দাম কম থেকে বেশি</option>
</select>
</label>
);
}পাতাটি নিজে server-এ const { sort } = await searchParams পড়ে তালিকা সাজায়, আর SortPicker-কে <Suspense>-এ মুড়ে রাখে। সত্যিকারের run-এ “দাম কম থেকে বেশি” বাছতেই URL হলো /products?sort=price, আর তালিকা: ৳135, ৳210, ৳280, ৳420. Filter URL-এ রাখার কারণ পর্ব ২০-এ দেখেছেন: link পাঠালে অন্যজনও একই তালিকা দেখেন।
redirect আর notFound-এর সত্যিকারের HTTP উত্তর (next start-এর ওপর সরাসরি request):
GET /offers → 307 location: /products?sort=price
GET /nope → 404 <meta name="robots" content="noindex">
GET /products/9 → 200 <meta name="robots" content="noindex"> + "এই পণ্যটি বাজারঘর-এ নেই"
শেষ লাইনটি অবাক করার মতো। ৯ নম্বর পণ্য নেই, তবু status 200? কারণ /products/[id]-এর অজানা id-র পাতা stream হয়: Next.js আগে পাতার static খোলস পাঠাতে শুরু করে, আর status code একবার পাঠানো হলে বদলানো যায় না। Docs ঠিক এই আচরণই লেখে, আর search engine যাতে পাতাটি index না করে সেজন্য noindex meta যোগ করে। Status code-এর গুরুত্ব পর্ব ৩৩-এ দেখেছেন। দরকার হলে, যেমন SEO-র জন্য, id যাচাইটি stream শুরুর আগে করার উপায় পর্ব ৫৫ আর ৫৮-এ।
নিরাপত্তা: redirect-এ URL থেকে আসা মান (যেমন ?next=…) সরাসরি দিলে open redirect তৈরি হয়। শুধু নিজের সাইটের ভেতরের path মানুন। আর menu থেকে link লুকানো নিরাপত্তা নয়: কেউ সরাসরি URL লিখে ঢুকতে পারেন, তাই যাচাই server-এ করতে হয়। বিস্তারিত Broken Access Control-এ।
৮ · Parallel আর intercepting route: পণ্যের “দ্রুত দেখা” modal
অনেক দোকানে তালিকার পণ্যে ক্লিক করলে তালিকার ওপরেই একটি ছোট জানালা (modal) খোলে। কিন্তু সেই modal-এর URL থাকলে ভালো: link share করা যায়, Back চাপলে modal বন্ধ হয়। আর কেউ সেই link সরাসরি খুললে পুরো পণ্যের পাতাই দেখা উচিত। Next.js দুটি কৌশল মিলিয়ে এটি করে।
- Parallel route:
@modalনামের folder একটি slot. এটি segment নয়, URL-এ আসে না। বাবা layoutchildren-এর পাশাপাশিmodalনামের একটি prop পায়। - Intercepting route:
(.)products/[id]মানে “একই স্তরেরproducts/[id]-কে ভেতর থেকে ক্লিক করে যাওয়ার সময় আটকাও”.(..)এক স্তর ওপরে,(...)একেবারেappথেকে।
app/(shop)/
├── layout.tsx {children}{modal}
├── @modal/
│ ├── default.tsx return null — modal বন্ধ
│ └── (.)products/[id]/page.tsx <Modal>…</Modal>
└── products/[id]/page.tsx পূর্ণ পাতা
সত্যিকারের run-এর ফল (Edge, Playwright, production build):
/products-এ ঝুড়ির বোতাম ২ বার → "ঝুড়িতে: 2"
"মসুর ডাল ১ কেজি" ক্লিক → URL /products/2, dialog খোলা, h1 এখনো "পণ্য", ঝুড়িতে: 2
"বন্ধ করুন" (router.back()) → URL /products, dialog DOM-এ আছে কিন্তু display:none
সরাসরি /products/2 খোলা → dialog নেই, h1 "মসুর ডাল ১ কেজি"
তিনটি জিনিস লক্ষ করুন। ক্লিকে তালিকা পেছনে থেকে গেল আর URL বদলাল: এটিই interception. ঝুড়ির সংখ্যা 2-ই রইল, কারণ (shop)/layout.tsx টিকে ছিল। আর সরাসরি খুললে interception হয় না, এটি তখন hard navigation, তাই পূর্ণ পাতা আসে। মাঝের লাইনটিও শিক্ষণীয়। বন্ধ করার পরে dialog মুছে যায়নি, লুকানো আছে। এটি অংশ ৬-এর <Activity>-এর কাজ। তাই modal-এর ভেতরে চলমান কিছু (video, timer) থাকলে লুকানোর সময় তা থামাতে হবে।
default.tsx বাধ্যতামূলক: Next.js 16-এ প্রতিটি slot-এর জন্য default.tsx না থাকলে build ব্যর্থ হয়। আমরা ফাইলটি সরিয়ে দেখেছি (অংশ):
Error: Parallel route slots cannot render the same URLs
The following layouts have parallel route slots that cannot render the same URLs:
app/(shop)/layout.tsx
- /products is missing a matching page or default.tsx in @modal
- /products/[id] is missing a matching page or default.tsx in @modal
এই কৌশল শক্তিশালী, কিন্তু জটিলও। ছোট অ্যাপে সাধারণ useState-এর modal-ই যথেষ্ট হতে পারে। URL দরকার কি না, অর্থাৎ share বা Back-এর সুবিধা লাগবে কি না, সেটি আগে ঠিক করুন। Modal-এর focus আর keyboard-এর নিয়মও মানতে হয়।
৯ · next build-এর route table আর “বাজারঘর”-এর route মানচিত্র
পুরো project-এ npm run build চালানোর সত্যিকারের ফল (Next.js 16.4.4; আমাদের console.log-এর [render] লাইনগুলো বাদ দেওয়া):
▲ Next.js 16.4.4 (Turbopack)
- Cache Components enabled
- Partial Prefetching enabled
✓ Compiled successfully in 667ms
Finished TypeScript in 1049ms ...
✓ Generating static pages using 13 workers (20/20) in 1130ms
Route (app)
┌ ○ /
├ ○ /_not-found
├ /(.)products/[id]
│ ├ ◐ /(.)products/[id]
│ ├ ○ /(.)products/1
│ ├ ○ /(.)products/2
│ └ ◐ [+2 more paths]
├ ○ /account/orders
├ ○ /account/profile
├ /category/[...slug]
│ └ ◐ /category/[...slug]
├ /help/[[...topic]]
│ └ ◐ /help/[[...topic]]
├ ○ /offers
├ ◐ /products
└ /products/[id]
├ ◐ /products/[id]
├ ○ /products/1
├ ○ /products/2
└ ◐ [+2 more paths]
○ (Static) prerendered as static content
◐ (Partial Prerender) prerendered as static HTML with dynamic server-streamed content
Table-টি পড়তে শিখলে project-এর স্বাস্থ্য এক নজরে বোঝা যায়:
- ○ Static: build-এর সময়ই পুরো HTML তৈরি, যেমন
/,/account/orders./offers-ও static, কারণ তার redirect build-এই জানা। - ◐ Partial Prerender: static খোলস তৈরি, ভেতরের অংশ request-এর সময় stream হয়।
/productssearchParamsপড়ে, আর catch-all route-গুলো আগে থেকে কোনো মান জানে না। সব রকমের বিস্তারিত পর্ব ৫৫-এ। - Route group নেই:
(shop)কোথাও নেই।_lib,_components-ও নেই। generateStaticParams-এর ফল:/products/1,/products/2আর “+2 more paths” মিলে চারটি পণ্যের পাতা। ওপরের◐ /products/[id]হলো অজানা id-র জন্য খোলস, যেমন/products/9./(.)products/[id]: modal-এর intercepting route. URL-এ এটি দেখা যায় না।- ২০টি পাতা: “Generating static pages (20/20)”-এ ভেতরের বিশেষ পাতাগুলোও গোনা হয়।
এবার পুরো ছবি একসাথে, “বাজারঘর”-এর route মানচিত্র। এটি পরের পর্বগুলোতে বাড়বে:
| URL | File (app/-এর ভেতরে) | বিশেষ যা আছে | Build |
|---|---|---|---|
/ | page.tsx | Root layout, header menu | ○ |
/products | (shop)/products/page.tsx | ?sort, loading.tsx, ঝুড়ির layout | ◐ |
/products/2 | (shop)/products/[id]/page.tsx | error, not-found, modal-এ intercept | ○ / ◐ |
/category/… | (shop)/category/[...slug]/page.tsx | Breadcrumb | ◐ |
/help, /help/… | help/[[...topic]]/page.tsx | Optional catch-all | ◐ |
/account/orders, /account/profile | account/…/page.tsx | Layout + template | ○ |
/offers | offers/page.tsx | redirect → 307 | ○ |
Pages Router, শুধু চেনার জন্য: পুরোনো টিউটোরিয়াল আর অনেক চালু project-এ app/-এর বদলে pages/ folder দেখবেন। এটিই Pages Router, যা Next.js এখনো সমর্থন করে। চেনার উপায়: pages/products/[id].tsx (folder নয়, file), getServerSideProps বা getStaticProps function, _app.tsx আর _document.tsx, আর next/router থেকে useRouter. নতুন project-এ App Router-ই ব্যবহার করুন, আর দুটির কোড মিশিয়ে ফেলবেন না। App Router-এর একটি Server Component-এ next/router import করে আমাদের build-এ সত্যিই এই error এসেছে:
Error: You have a Server Component that imports next/router. Use next/navigation instead.
আরও গভীরে: Client-এর router ভেতরে একটি গাছ রাখে, যার প্রতিটি ঘর একটি segment আর তার RSC payload. ওপরের log-এ __PAGE__, __DEFAULT__, $d$id (dynamic), $c$slug (catch-all) নামগুলো সেই গাছের চাবির encode করা রূপ। Navigation-এ router দুটি গাছ মিলিয়ে দেখে কোন segment বদলেছে, আর শুধু সেটুকু আনে। তাই layout-এর কাজ আবার হয় না। এই চাবির রূপ ভেতরের ব্যাপার, version-এ বদলাতে পারে, তাই এর ওপর নির্ভর করে কোড লিখবেন না।
পরীক্ষা ও ইন্টারভিউতে:
- Layout আর template-এর পার্থক্য? Layout navigation-এ টিকে থাকে আর state রাখে; template segment ধরে নতুন key পায়, তাই state মুছে নতুন mount হয়।
[...slug]আর[[...slug]]-এর পার্থক্য? দ্বিতীয়টি segment ছাড়া ঠিকানাও (/shop) মেলায়, তখন মানundefined.- Route group কী কাজে লাগে? URL না বদলে folder গোছানো আর অংশভিত্তিক আলাদা layout দেওয়া।
- Next.js 16-এ
paramsকীভাবে পড়বেন? Server-এawait params, Client Component-এuse(params)বাuseParams(). - Prefetch কখন হয়? Production-এ,
<Link>viewport-এ এলে;prefetch={false}দিলে হয় না।
১০ · AI-এর যুগে Next.js routing
Next.js-এ AI-এর ভুলের প্রধান কারণ ইতিহাস। প্রায় সাত বছর ধরে ইন্টারনেটের বেশিরভাগ Next.js কোড ছিল Pages Router-এর। তারপর App Router এলো, আর 15 ও 16-এ আবার বড় বদল হলো। AI তাই প্রায়ই তিন যুগের কোড মিশিয়ে লেখে। মজার ব্যাপার, create-next-app নিজেই এই সমস্যা মেনে নিয়েছে। আমাদের project-এ সে একটি AGENTS.md file বানিয়েছে, যার প্রথম লাইন:
## This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` …
অর্থাৎ install করা package-এর সাথেই সেই version-এর docs আসে, node_modules/next/dist/docs/-এ। AI agent, আর আপনিও, সেখান থেকে মিলিয়ে নিতে পারেন। আমাদের সত্যিকারের run-এ পাওয়া ভুলগুলো:
| AI যা লেখে | কী হয় | এখনকার পথ |
|---|---|---|
params.id | TS2339: Promise-এ id নেই | const { id } = await params |
import { useRouter } from "next/router" | Build error: Server Component-এ next/router | next/navigation, আর hook শুধু Client Component-এ |
getServerSideProps app/-এর ভেতরে | App Router এটি চেনে না | Server Component-এ সরাসরি await (পর্ব ৫৩) |
middleware.ts | 16-এ নামটি deprecated | proxy.ts (পর্ব ৫৭) |
error.tsx-এ শুধু reset | চলে, কিন্তু ডেটা আবার আনে না | 16.3 থেকে স্থিতিশীল retry |
@modal আছে, default.tsx নেই | Build ব্যর্থ | প্রতিটি slot-এ default.tsx |
<Link><a>…</a></Link> | 13-এর আগের রীতি | Link নিজেই <a> বানায় |
| Menu থেকে admin link লুকানো = নিরাপত্তা | URL লিখে সরাসরি ঢোকা যায় | Server-এ authorization (পর্ব ৪৮) |
AI-কে version, router আর config বলে দিন, আর অনিশ্চিত জায়গা স্বীকার করতে বলুন:
AI-এর লেখা routing কোড নেওয়ার আগে
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১File দিয়ে routing কি নতুন ধারণা? PHP-র যুগ থেকে Next.js পর্যন্ত এই ধারণা কেন বারবার ফিরে আসে?
একদমই নতুন নয়। ওয়েবের শুরুতে Apache-এর মতো server-এ URL মানেই ছিল disk-এর একটি file-এর পথ। /about.html চাইলে server ওই নামের file পাঠাত। PHP এসে এই মডেল বজায় রাখল: /cart.php মানে cart.php file চালানো। cPanel-এ যে কেউ একটি file তুলে দিলেই নতুন পাতা পেতেন। এই সরলতাই PHP-কে শেয়ার্ড hosting-এর জগতে জনপ্রিয় করেছিল।
তারপর এলো framework-এর যুগ। Rails, Django, Express-এ route আলাদা একটি তালিকায় লিখতে হয় (মডিউল ৪-এ Express-এর app.get দেখেছেন)। এতে স্বাধীনতা বাড়ে: যেকোনো URL যেকোনো function-এ পাঠানো যায়, একই handler একাধিক URL-এ বসানো যায়। কিন্তু বড় project-এ তালিকা আর আসল file আলাদা হয়ে যায়, আর “এই URL-এর কোড কোথায়” খুঁজতে সময় লাগে।
২০১৬ সালে Next.js আবার পুরোনো সরলতায় ফিরল, কিন্তু নতুন রূপে: pages/-এর file-এর নামই route. App Router এটিকে আরও এক ধাপ এগিয়ে নিল। এখন folder segment, আর folder-এর ভেতরের নির্দিষ্ট নামের file (layout, loading, error) জানায় সেই segment-এর আচরণ কী। SvelteKit, Nuxt, Astro, আর React Router-এর framework mode-ও কোনো না কোনো রূপে file-ভিত্তিক routing দেয়।
দাম কী? নিয়ম কঠোর। page.tsx-এর নাম ভুল লিখলে কোনো error ছাড়াই route গায়েব হয়ে যায়। আবার “এই একটি URL অন্যরকম আচরণ করুক” ধরনের ব্যতিক্রম করতে route group, rewrite বা proxy-র মতো বাড়তি কৌশল লাগে। তবু বেশিরভাগ দলের কাছে লাভই বেশি: নতুন সদস্য folder দেখেই সাইটের মানচিত্র বোঝেন, আর build-এর route table একটি জীবন্ত দলিল হয়ে থাকে।
মূল উপলব্ধি: প্রযুক্তি প্রায়ই চক্রাকারে ঘোরে। পুরোনো একটি সরল ধারণা নতুন প্রয়োজন (nested layout, streaming, code splitting) মেটাতে নতুন রূপে ফেরে। কোনটি আপনার দলের জন্য ভালো, তা ঠিক হয় convention আর স্বাধীনতার মধ্যে আপনি কোথায় দাঁড়াতে চান তা দিয়ে।
প্র ০২Pages Router তো ভালোই চলছিল। Next.js কেন এত বড় দাম দিয়ে App Router-এ গেল, আর community-র প্রতিক্রিয়া কেমন ছিল?
Pages Router-এ একটি পাতা ছিল একটি একক। ডেটা আসত পাতার স্তরে, getServerSideProps বা getStaticProps দিয়ে, তারপর props হয়ে নিচে নামত। ভাগ করা layout বানাতে _app.tsx-এ কৌশল খাটাতে হতো। আর পাতার পুরো JavaScript browser-এ যেত, এমনকি যে অংশ শুধু একবার লেখা দেখায় তারও।
React দল তখন React Server Components নিয়ে কাজ করছিল: এমন component যা শুধু server-এ চলে, তার কোড browser-এ যায়ই না, আর নিজের ডেটা নিজে আনে। এই ধারণা পুরোনো কাঠামোয় বসানো যাচ্ছিল না। তাই ২০২২ সালে Next.js 13 নতুন app/ folder আনে, যেখানে প্রতিটি segment-এর নিজের layout, loading আর error থাকে, আর Suspense দিয়ে অংশে অংশে stream করা যায়।
প্রতিক্রিয়া মিশ্র ছিল। অনেকে nested layout আর কম JavaScript-এর সুবিধা পছন্দ করলেন। আবার অনেকের অভিযোগ ছিল, caching-এর আচরণ বোঝা কঠিন, আর শেখার উপকরণ পুরোনো হয়ে গেল। Next.js 15-এ caching-এর default বদলানো, আর 16-এ Cache Components আনা, আংশিকভাবে সেই অভিযোগেরই উত্তর। Pages Router সরানো হয়নি, তাই দুটি একই project-এ পাশাপাশি চলতে পারে, আর ধীরে ধীরে সরানো যায়।
একটি বড় framework যখন এমন মৌলিক বদল আনে, তখন খরচ শুধু কোড বদলানো নয়। পুরোনো টিউটোরিয়াল, Stack Overflow-এর উত্তর, আর AI-এর শেখা কোড সবই এক রাতে অর্ধেক পুরোনো হয়ে যায়। এজন্যই অংশ ১০-এ দেখা AGENTS.md-এর মতো সমাধান এসেছে।
মূল উপলব্ধি: বড় architecture বদলের সিদ্ধান্ত মাপা হয় দুই পাল্লায়: নতুন কী সম্ভব হলো, আর পুরোনো ব্যবহারকারী আর জ্ঞানভান্ডারের কত ক্ষতি হলো। ভালো migration পথ (দুটি router পাশাপাশি, codemod) সেই ক্ষতি কমায়।
প্র ০৩Prefetch পাতা দ্রুত করে, কিন্তু কার খরচে? বাংলাদেশের মোবাইল ডেটার ব্যবহারকারীর দিক থেকে ভাবুন।
Prefetch একটি বাজি: “ব্যবহারকারী সম্ভবত এই link-এ ক্লিক করবেন, তাই আগেই এনে রাখি।” বাজি জিতলে ক্লিকের পরে অপেক্ষা প্রায় শূন্য। হারলে ডেটা নষ্ট। অংশ ৭-এর log-এ হোম পাতা খুলতেই ক্লিক ছাড়াই আটটি request গেছে। একটি তালিকার পাতায় পর্দায় যদি ২৪টি পণ্য থাকে, তবে অনেকগুলো request যেতে পারে, যার বেশিরভাগে কেউ ক্লিক করবেন না।
যে ব্যবহারকারী সীমিত MB-এর প্যাকেজে ব্রাউজ করছেন, তাঁর কাছে এটি সত্যিকারের টাকা। Server-এর দিকেও খরচ: প্রতিটি prefetch একটি request, যা CPU আর bandwidth খায়। তাই Next.js নানা সুরক্ষা রেখেছে: শুধু viewport-এর link, শুধু production-এ, আর Partial Prefetching-এ পুরো পাতার বদলে static খোলস ও cache করা অংশ।
ধারণাটি পুরোনো। HTML-এ <link rel="prefetch"> বহু বছর ধরে আছে। পরে quicklink আর instant.page-এর মতো ছোট library hover বা viewport দেখে prefetch চালু করে। এখন Chromium-ভিত্তিক browser-এ Speculation Rules API আছে, যা দিয়ে সাইট browser-কে বলে দেয় কোন পাতা আগেভাগে আনা বা এমনকি render করে রাখা যায়।
ভালো সিদ্ধান্তের জন্য মাপতে হয়: কোন link-এ আসলে কত ক্লিক হয়? ফুটারের ৩০টি link-এ prefetch={false} দেওয়া যুক্তিসঙ্গত, কিন্তু “ঝুড়িতে যান” বোতামে নয়। অনুশীলন ১-এ এর একটি হিসাব আছে।
মূল উপলব্ধি: গতি বিনামূল্যে আসে না। Prefetch latency কমায় bandwidth আর server-এর কাজ বাড়িয়ে। কোন ব্যবহারকারীর জন্য কোন খরচ গ্রহণযোগ্য, তা মেপে ঠিক করুন।
প্র ০৪একটি modal-এর কি নিজের URL থাকা উচিত? Intercepting route-এর পক্ষে আর বিপক্ষে যুক্তি দিন।
পক্ষের যুক্তি ওয়েবের মূল চুক্তি থেকে আসে: যা দেখা যায় তার একটি ঠিকানা থাকা উচিত। রিনা পণ্যের দ্রুত-দেখা জানালাটি বোনকে পাঠাতে চাইলে URL লাগবে। Mobile-এ মানুষ Back বোতাম চেপে modal বন্ধ করতে চান। URL না থাকলে Back চাপলে পুরো পাতা থেকেই বেরিয়ে যান, যা বিরক্তিকর। Photo-sharing ধরনের সাইটে এই আচরণ বহুদিন ধরে চালু, আর Next.js-এর docs-এর উদাহরণও সেখান থেকে নেওয়া।
বিপক্ষের যুক্তি জটিলতা। অংশ ৮-এ দেখেছেন, একটি modal-এর জন্য একটি slot, একটি default.tsx, একটি intercepting folder, আর বন্ধ করার আলাদা ব্যবস্থা লাগে। default.tsx না থাকলে build ভাঙে। বন্ধ করার পরেও Activity-র কারণে DOM লুকিয়ে থাকে। নতুন সদস্যের পক্ষে (.) আর @-এর অর্থ বোঝা সহজ নয়।
আরও একটি দিক accessibility. URL থাকুক বা না থাকুক, modal খুললে focus তার ভেতরে যেতে হবে, Escape চাপলে বন্ধ হতে হবে, আর বন্ধ হলে focus আগের জায়গায় ফিরতে হবে। Router এর কিছুই নিজে করে না। HTML-এর <dialog> element এখন এর অনেকটা সহজ করে।
সিদ্ধান্তের একটি সহজ পরীক্ষা: “এই modal-এর বিষয়বস্তু কি নিজে একটি পাতা হওয়ার যোগ্য?” পণ্যের বিবরণ, ছবি বা login-এর ক্ষেত্রে হ্যাঁ, কারণ তাদের পূর্ণ পাতাও আছে। “নিশ্চিত তো?” ধরনের confirmation বা ছোট tooltip-এর ক্ষেত্রে না, সেখানে সাধারণ state-ই যথেষ্ট।
মূল উপলব্ধি: URL হলো share করার, ফিরে আসার আর search engine-এর চোখে পড়ার দরজা। যে UI-এর এগুলো দরকার, তার জন্য বাড়তি জটিলতা দেওয়া যায়; বাকিদের জন্য তা অপচয়।
প্র ০৫Meta-framework বেছে নেওয়া মানে কি একটি কোম্পানির ওপর নির্ভরশীল হয়ে পড়া? Lock-in-এর ঝুঁকি কীভাবে মাপবেন?
Next.js open-source, কিন্তু এর রক্ষণাবেক্ষণের প্রধান দায়িত্ব একটি hosting কোম্পানি, Vercel-এর। এ নিয়ে community-তে বহুদিনের আলোচনা আছে। কেউ কেউ অভিযোগ করেছেন, কিছু সুবিধা সেই কোম্পানির নিজের platform-এ সবচেয়ে সহজে চলে, আর অন্য জায়গায় চালাতে বাড়তি পরিশ্রম লাগে। এর উত্তরে OpenNext-এর মতো community প্রকল্প হয়েছে, আর Next.js-এর নিজের docs-এও এখন অন্য platform-এর জন্য adapter-এর নির্দেশনা আছে।
বাস্তবে তিন স্তরে lock-in মাপা যায়। প্রথম, runtime: next start একটি সাধারণ Node.js server, যা যেকোনো VPS বা container-এ চলে (পর্ব ৮০, ৮২)। দ্বিতীয়, API: params-এর মতো API version-এ version-এ বদলায়, তাই framework-এর সাথে আপনার কোড বাঁধা। তৃতীয়, ধারণা: Server Component, nested layout, streaming এখন React-এরই অংশ, তাই এই জ্ঞান অন্য framework-এও কাজে লাগে।
ঝুঁকি কমানোর অভ্যাস আছে। ব্যবসার নিয়ম (দাম হিসাব, stock যাচাই) framework-এর বাইরে সাধারণ TypeScript module-এ রাখুন, যেমন আমাদের _lib/products.ts. Database-এর কাজ একটি data access layer-এ রাখুন (পর্ব ২৯)। Hosting-নির্দিষ্ট সুবিধা ব্যবহার করলে তার তালিকা রাখুন।
উল্টো দিকও ভাবুন: Vite + React Router + Express-এ lock-in কম, কিন্তু প্রতিটি জোড়ার দায় আপনার। ছোট দলের কাছে সেই দায়ই অনেক সময় বড় খরচ। তাই সিদ্ধান্তটি প্রযুক্তির চেয়ে বেশি দল, বাজেট আর hosting-এর বাস্তবতার ওপর নির্ভর করে, যা পর্ব ৮৫-এ বিস্তারিত।
মূল উপলব্ধি: প্রতিটি framework-ই কিছুটা lock-in. প্রশ্ন হলো সেটি কোন স্তরে, আর বের হওয়ার দরজা খোলা আছে কি না। নিজের মূল যুক্তি framework থেকে আলাদা রাখলে সেই দরজা খোলা থাকে।
অনুশীলন
নিজে করে তারপর উত্তর মেলান। সংখ্যার ক্ষেত্রে হিসাবের ধাপও লিখুন।
-
Prefetch-এর খরচ (হিসাব): একটি বিভাগের পাতার পর্দায় একসাথে ২৪টি পণ্যের Link দেখা যায়। ধরে নিন প্রতিটি link-এর prefetch-এ গড়ে ২টি request যায়, প্রতিটি গড়ে 3 kB. দিনে 10,000 বার পাতাটি খোলা হয়। (ক) এক বার পাতা খুললে কতটি request আর কত kB? (খ) দিনে মোট কত? (গ) প্রথম ৬টি বাদে বাকি সব link-এ
prefetch={false}দিলে দিনে কত সাশ্রয়?(ক) 24 × 2 = 48টি request; 48 × 3 = 144 kB.
(খ) 48 × 10,000 = 4,80,000 request; 144 × 10,000 = 14,40,000 kB = 1,440 MB ≈ 1.44 GB (1 MB = 1000 kB ধরে)।
(গ) এখন 6 × 2 = 12টি request, 36 kB প্রতি বার। দিনে 1,20,000 request আর 360 MB. সাশ্রয় 1,440 − 360 = 1,080 MB, অর্থাৎ ৭৫%. দাম হলো, বাকি ১৮টি পণ্যে ক্লিক করলে অপেক্ষা একটু বাড়বে। সংখ্যাগুলো ধরে নেওয়া; আসল মাপ আপনার নিজের network log থেকে নিন।
-
ফল আন্দাজ করুন: এই file-গুলো আছে:
app/page.tsx,app/(shop)/products/page.tsx,app/(shop)/products/[id]/page.tsx,app/(shop)/products/new/page.tsx,app/docs/[[...slug]]/page.tsx,app/_admin/page.tsx. নিচের প্রতিটি URL-এ কোন file চলবে আরparamsকী হবে? (১)/products/new(২)/products/42(৩)/docs(৪)/docs/a/b(৫)/shop/products(৬)/_admin(১)
products/new/page.tsx, params{}: স্থির নাম dynamic-এর আগে। (২)[id],{ id: "42" }, string. (৩)docs/[[...slug]],{ slug: undefined }. (৪) একই file,{ slug: ["a", "b"] }. (৫) ৪০৪:(shop)URL-এর অংশ নয়। (৬) ৪০৪:_adminprivate folder, ভেতরেpageথাকলেও route নয়। -
নকশা সমালোচনা: একজন সহকর্মী এই গঠন জমা দিয়েছেন:
app/(shop)/about/page.tsxআরapp/(info)/about/page.tsxদুটোই আছে; সব componentapp/components/-এ; পণ্যের modal বানানো হয়েছেuseStateদিয়ে, কিন্তু চাওয়া হয়েছে link share করার সুবিধা;app/admin/page.tsx-এর নিরাপত্তা শুধু header-এ link না দেখানো। কী কী বদলাবেন?এক: দুটি group-এ একই
/about, তাই Next.js error দেবে। একটি রাখুন, অথবা নাম আলাদা করুন।দুই:
app/components/-এpageনেই বলে এটি route নয়, তাই কাজ করবে। তবু_componentsনাম দিলে উদ্দেশ্য স্পষ্ট হয়, অথবা component-গুলোapp/-এর বাইরে রাখুন। দলের মধ্যে একটি নিয়ম ঠিক করুন।তিন: Link share করতে হলে modal-এর URL লাগবে:
@modalslot,(.)products/[id]আরdefault.tsx, সাথে পূর্ণ পাতাproducts/[id]. সাথে focus আর Escape-এর ব্যবস্থা।চার: Link লুকানো নিরাপত্তা নয়।
/adminসরাসরি খোলা যায়। পাতার server কোডে, আর প্রতিটি API ও Server Action-এ session আর role যাচাই করুন (পর্ব ৪৮)। -
কমান্ড আর route table: একটি নতুন project বানিয়ে
app/blog/[slug]/page.tsxযোগ করুন, যেখানেgenerateStaticParamsতিনটি slug ফেরায়, আরapp/(marketing)/pricing/page.tsx. কোন কোন command চালাবেন, আরnext build-এর route table-এ এই দুটি route কেমন দেখাবে বলে আশা করেন?Command:
npx create-next-app@latest myapp --yes, তারপরcd myapp, file দুটি লিখেnpm run build. চালানোর আগেnpm view next versionদিয়ে version দেখে নিন।প্রত্যাশা:
/pricingএকটি ○ লাইন,(marketing)নামটি table-এ থাকবে না।/blog/[slug]-এর নিচে তিনটি slug-এর জন্য ○ লাইন (কিছু হয়তো “+N more paths”-এ গোনা), আর অজানা slug-এর জন্য একটি ◐ লাইন, যদি default config-এ Cache Components চালু থাকে। আসল চিহ্ন আপনার config আর কোডের ওপর নির্ভর করবে, তাই table-টি নিজে পড়ে মিলিয়ে নিন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমNext.js App Router-এর production build-এ একটি <Link>-এর জীবনের ধাপগুলো সাজান (▲▼ চেপে সরান)
- পাতা load হয়, Link পর্দায় (viewport-এ) আসে
- পটভূমিতে লক্ষ্য route-এর গাছ আর static segment আনা হয় (prefetch)
- ব্যবহারকারী ক্লিক করেন, Link browser-এর page load থামায়
- শুধু যে অংশ আগে আনা যায়নি, তার জন্য একটি RSC request যায়
- বদলানো segment বসে, layout অক্ষত থাকে, URL নতুন হয়
Next.js App Router-এ app/(shop)/products/[id]/page.tsx file কোন ঠিকানায় খোলে?
- /shop/products/7
- /products/[id]
- /products/7, যেখানে params হয় { id: "7" }
- কোনো ঠিকানাতেই নয়, কারণ group-এর ভেতরের পাতা প্রকাশ্য নয়
Next.js 16-এর একটি Server Component page-এ dynamic segment id পড়ার সঠিক উপায় কোনটি?
- const id = params.id
- const { id } = await params
- const { id } = useRouter().query, next/router থেকে
- getServerSideProps-এ context.params থেকে
app/account/layout.tsx আর app/account/template.tsx দুটিতেই একটি করে useState-এর counter আছে। /account/orders-এ layout-এর counter 3 আর template-এর counter 2 করে ব্যবহারকারী Link দিয়ে /account/profile-এ গেলেন। এখন মান দুটি কী?
- Layout 3, template 0
- Layout 3, template 2
- Layout 0, template 0
- Layout 0, template 2
Next.js App Router-এ app/shop/[[...slug]]/page.tsx আছে। /shop খুললে কী হয়?
- ৪০৪, কারণ অন্তত একটি segment লাগে
- পাতা খোলে, params হয় { slug: [] }
- পাতা খোলে, params হয় { slug: "" }
- পাতা খোলে, params হয় { slug: undefined }
Next.js-এর <Link>-এর prefetch সম্পর্কে কোনটি সত্য?
- next dev-এ সবচেয়ে বেশি prefetch হয়, যাতে development দ্রুত হয়
- Production-এ link viewport-এ এলে হয়; prefetch={false} দিলে বন্ধ
- শুধু ক্লিকের পরে হয়
- Prefetch মানে browser পরের পাতার পুরো HTML document load করে রাখে
একটি দোকানে চাওয়া হয়েছে: তালিকা থেকে পণ্যে ক্লিক করলে তালিকার ওপরে modal খুলবে আর URL হবে /products/2; কিন্তু সেই URL সরাসরি খুললে পূর্ণ পণ্যের পাতা আসবে। Next.js 16-এ কোন গঠন ঠিক?
- তালিকার পাতায় useState দিয়ে modal, আর কোনো route নয়
- @modal/(.)products/[id]/page.tsx, কোনো default.tsx ছাড়া
- @modal slot-এ (.)products/[id]/page.tsx আর default.tsx, সাথে আলাদা products/[id]/page.tsx
- (modal)/products/[id]/page.tsx নামে একটি route group
Next.js App Router-এ একটি segment-এর error.tsx সম্পর্কে কোনটি ঠিক?
- এটি Client Component হতে হয়, আর একই folder-এর layout.tsx-এর error ধরতে পারে না
- এটি Server Component, তাই এতে database ডাকা যায়
- এটি পুরো সাইটের, root layout-সহ, সব error ধরে
- notFound() ডাকলে error.tsx দেখায়
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Folder = segment,
page= প্রকাশ্য;(group)আর_privateURL-এ আসে না। - ক্রম: layout › template › error › loading › not-found › page; layout টেকে, template নতুন হয়।
[id],[...slug],[[...slug]]; Next.js 16-এawait params, মান সবসময় string বা string-এর array.<Link>production-এ prefetch করে; hook আসেnext/navigationথেকে;redirect307, stream শুরুর পরেnotFound200 + noindex.- Modal-এর URL চাইলে
@slot+(.)+default.tsx; সব শেষেnext build-এর route table পড়ে যাচাই।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫২ · Server Component আর Client Component — কোনটি কোথায় চলেপরবর্তী পর্বকোন অংশ server-এ চলবে আর কোনটি browser-এ, সেই সীমানা টানবেন।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · কোর্স হোম৯৩টি পর্বপুরো কোর্সের রোডম্যাপ; পরের পর্ব ৫২ · Server Component আর Client Component.
- পর্ব ২০ · React RouterতুলনাNested route, Outlet আর loader, যার পাশে Next.js-কে মিলিয়ে দেখবেন।
- পর্ব ০৩ · Rendering-এর মানচিত্রভিত্তিCSR, SSR, SSG-এর মূল ধারণা।
- HTTPভিত্তি307, 404, header আর request-এর গঠন।
- Accessibilityপাশের ট্র্যাকLink, breadcrumb আর modal-এর সঠিক ব্যবহার।