IlmHamroh
JavaScript Full-stack/23-qism. Next.js3/43-dars25 daqiqa
Mundarija (34)

Next.js routing: page.tsx, papkalar va dinamik segmentlar ([slug], [...slug])

Qisqacha: Next.js'da marshrut ro'yxati yozilmaydi — uni papkalar beradi. app/haqimda/page.tsx fayli /haqimda sahifasiga aylanadi: papka — URL segmenti, page.tsx — shu segmentning sahifasi. Kvadrat qavsdagi papka — dinamik segment: app/loyihalar/[slug]/page.tsx bitta fayl bilan /loyihalar/vazifalar, /loyihalar/bahor va boshqa hamma loyihaga xizmat qiladi. Qiymat sahifaga params orqali keladi, va u Next 15 dan beri Promise: const { slug } = await params. [...qism] — qolgan hamma segmentni massiv qilib ushlaydi, [[...qism]] — xuddi shunday, lekin bo'sh manzilni ham qabul qiladi.

Bu darsda

  • Papka tuzilmasidan URL'ni (va aksincha) aytib bera olasiz; page.tsx siz papka nega ochilmasligini tushuntirasiz.
  • [slug] dinamik segmentli sahifa yozasiz va params ni await bilan o'qiysiz.
  • PageProps<"/loyihalar/[slug]"> turi qayerdan kelishini va qanday xatoni ushlashini bilasiz.
  • [...qism] va [[...qism]] farqini haqiqiy build natijasi bilan ko'rasiz.
  • portfolio v2 ga v1 sahifalarini ko'chirasiz: bosh sahifa, «Men haqimda», «Loyihalar», loyiha sahifasi, «Aloqa».

Oldin bilishingiz kerak: Loyiha yaratish va tuzilishi, React Router II: dinamik va layout marshrutlar, React Router V: framework mode, async / await, Ko'p sahifali portfolio (faqat HTML).

1. Nega bu kerak?

Oldingi darsda create-next-app bo'sh skelet yaratdi: bitta app/page.tsx va bitta sahifa. Endi portfolio v1 dagi beshta sahifani ko'chirish kerak.

Eslang, 04-qismda v1 ni qanday qurgandingiz. Har sahifa — alohida papka, ichida index.html. haqimda/index.html — /haqimda/ manzili, loyihalar/bahor/index.html — /loyihalar/bahor/. Hech qanday "marshrutlar ro'yxati" yo'q edi: papkani ko'rib, manzilni bilardingiz. GitHub Pages ham shunday ishlaydi.

React Router da esa boshqacha edi. Har sahifa uchun marshrutlar.tsx ga bitta obyekt yozdingiz: { path: "/taom/:slug", element: <Taom /> }. Fayl qayerda turishi muhim emas — ro'yxat hal qiladi.

Next.js birinchi yo'lni tanladi, lekin unga React qo'shdi. Papka — manzil, xuddi v1 dagidek. Faqat index.html o'rnida page.tsx — React komponenti turadi. Bu fayl-asosli routing (file-based routing) deyiladi: marshrut fayl tizimidan o'qiladi.

Hayotiy o'xshatish: ko'p qavatli uyning pochta qutilari. Har qutida xonadon raqami yozilgan. Pochtachi ro'yxat ochmaydi — qutini topadi va xatni tashlaydi. Qutisi yo'q xonadonga xat bormaydi. Next.js'da quti — page.tsx.

v1 dagi loyihalar/bahor/ papkasida yana bir muammo bor edi. Har yangi loyiha uchun yangi papka va yangi HTML nusxasi kerak edi. Uchinchi loyihada sarlavha o'zgarsa, uchta faylni qo'lda tuzatardingiz. Bugun buni bitta fayl bilan hal qilamiz.

2. Papka — segment, page.tsx — sahifa

2.1 Birinchi qoida

Segment — URL'ning ikki / orasidagi bo'lagi (React Router II da ko'rgansiz): /loyihalar/bahor da ikkita segment — loyihalar va bahor. Next.js'da app/ ichidagi har papka — bitta segment. Papka ichida page.tsx bo'lsa, shu manzil ochiladi.

Rasmda — portfolio ning bugungi app/ papkasi va undan chiqqan manzillar.

flowchart LR
  A["app/page.tsx"] --> U1["/"]
  B["app/haqimda/page.tsx"] --> U2["/haqimda"]
  C["app/loyihalar/page.tsx"] --> U3["/loyihalar"]
  D["app/loyihalar/[slug]/page.tsx"] --> U4["/loyihalar/vazifalar<br/>/loyihalar/bahor …"]
  E["app/aloqa/page.tsx"] --> U5["/aloqa"]

Diqqat qiling: app/page.tsx — ildiz /. app papkasining o'zi segment emas, u — boshlanish nuqtasi. [slug] papkasi esa bitta emas, ko'p manzil beradi — uni keyingi bo'limda ochamiz.

portfolio kanonidagi «Aloqa» sahifasi to'liq shunday:

tsx
import type { Metadata } from "next";

export const metadata: Metadata = { title: "Aloqa — Aziz Karimov" };

export default function AloqaSahifasi() {
  return (
    <main id="asosiy">
      <h1>Aloqa</h1>
      <p>
        Ish taklifi yoki savol bo'lsa, yozing:{" "}
        <a href="mailto:aziz@example.com">aziz@example.com</a> yoki{" "}
        <a href="https://t.me/sizning_kanal">Telegram</a>.
      </p>
      {/* … ko'p so'raladigan savollar — details tegi bilan */}
    </main>
  );
}

Uchta narsaga qarang:

Qism Nima qiladi
export default Next.js sahifa sifatida aynan shuni chizadi
AloqaSahifasi Nomi ixtiyoriy — URL'ga ta'sir qilmaydi
metadata Brauzer tabidagi sarlavha (Metadata API da chuqur)

Funksiya nomi URL'ni belgilamaydi. Manzilni faqat papka yo'li belgilaydi. AloqaSahifasi ni Page deb atasangiz ham, sahifa baribir /aloqa da qoladi.

Bu sahifada useState ham, onClick ham yo'q. Next.js'da sahifa sukut bo'yicha serverda chiziladigan Server Component bo'ladi — brauzerga tayyor HTML boradi. Buni Server Components darsida to'liq o'rganamiz. Hozircha bilish yetarli: sahifa — oddiy React komponent, faqat u serverda ishlaydi.

Tekshirib ko'ring: app/blog/yangiliklar/page.tsx fayli qaysi manzilni ochadi? Funksiyani export default function Salom() deb atasak, manzil o'zgaradimi?

Javob

/blog/yangiliklar — ikkita papka, ikkita segment. Funksiya nomi manzilga ta'sir qilmaydi, faqat export default bo'lishi muhim.

2.2 page.tsx siz papka — yopiq

Papka yaratishning o'zi manzil ochmaydi. Biz tajriba qildik: app/sinov/ papkasiga faqat Tugma.tsx komponentini qo'ydik, page.tsx qo'ymadik. Keyin next build va next start, so'ng ikki manzilni so'radik:

text
/sinov -> 404
/sinov/Tugma -> 404

Ikkalasi ham 404 (topilmadi). Build jadvalida /sinov umuman yo'q. Ya'ni app/ ichida sahifaga tegishli komponentni (masalan faqat shu bo'limda ishlatiladigan kartani) yonma-yon saqlash xavfsiz — u o'z-o'zidan sahifa bo'lib qolmaydi. Bu odatning nomi colocation (yonma-yon joylash); uni Route groups va private papkalar darsida tartibga solamiz.

Ikkinchi tajriba: page.tsx bor, lekin ichida export default yo'q, faqat nomli eksport (export function Sinov()). next build to'xtadi:

text
.next/types/validator.ts(184,31): error TS2344: Type 'typeof import("C:/Users/…/p/src/app/sinov/page")' does not satisfy the constraint 'AppPageConfig<"/sinov">'.
Failed to type check.

Tarjimasi: "sinov/page modulining turi AppPageConfig<"/sinov"> shartiga mos kelmaydi". Xabar validator.ts degan begona faylni ko'rsatadi — chalg'imang. Bu faylni Next.js o'zi yaratadi va unda har page.tsx "to'g'ri sahifami?" deb tekshiriladi. Asl sabab xabardagi yo'lda: src/app/sinov/page. Qoida: page.tsx da doim export default.

Sahifalar orasida o'tish uchun next/link dan Link komponenti ishlatiladi. U oddiy <a> chizadi, lekin bosilganda sahifani to'liq qayta yuklamaydi — React Router dagi <Link to> kabi. Farqi bitta: atribut nomi href.

tsx
import Link from "next/link";

<Link href="/loyihalar">Loyihalar</Link>

Link ning to'liq imkoniyatlari (oldindan yuklash, replace, scroll) va useRouter — Navigatsiya darsida. Bugun faqat "manzilni o'zgartiradigan havola" sifatida ishlatamiz.

2.4 Natija oynasida: fayl-routing modeli

Saytdagi natija oynasida server yo'q va app/ papkasi ham yo'q. Shuning uchun Next.js'ning o'zini u yerda ishga tushirib bo'lmaydi. Lekin next/link va next/navigation uchun IlmHamroh'da kichik o'quv modeli bor: <Link>, usePathname(), useParams() va boshqa hook'lar xotiradagi manzil bilan ishlaydi.

Diqqat: natija oynasidagi next/link — o'quv modeli, haqiqiy Next.js emas. Haqiqiy Next.js'da havola ortida server, oldindan yuklash (prefetch) va RSC so'rovi bor. Modelda esa faqat xotiradagi manzil o'zgaradi. Manzil satri ham yo'q — joriy manzilni ko'rish uchun usePathname() ni ekranga chiqaramiz.

Quyidagi blokda Next.js ichida nima bo'layotganini qo'lda qilamiz. FILES obyekti — "app papkasi": kalit — papka yo'li, qiymat — sahifa komponenti. App joriy manzilni oladi va mos "faylni" topadi.

tsx
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { ReactNode } from "react";

// "app/" papkasi: kalit — page.tsx turgan papka yo'li
const FILES: Partial<Record<string, () => ReactNode>> = {
  "/": () => <h1>Bosh sahifa</h1>,
  "/haqimda": () => <h1>Men haqimda</h1>,
  "/loyihalar": () => <h1>Loyihalar</h1>,
};

export default function App() {
  const pathname = usePathname();
  const Page = FILES[pathname];
  console.log(pathname, Page ? "topildi" : "404");
  return (
    <>
      <nav>
        <Link href="/">Bosh sahifa</Link>{" "}
        <Link href="/haqimda">Men haqimda</Link>{" "}
        <Link href="/loyihalar">Loyihalar</Link>
      </nav>
      <p>Manzil: {pathname}</p>
      {Page ? <Page /> : <h1>404 — sahifa topilmadi</h1>}
    </>
  );
}

Konsolda:

text
/ topildi
/loyihalar topildi
/haqimda topildi

Ekranda:

text
Bosh sahifa Men haqimda Loyihalar
Manzil: /haqimda
Men haqimda

«Ishga tushir» ni bosing va havolalarni bosib ko'ring. Har bosishda usePathname() yangi manzilni qaytaradi, App qayta chiziladi va FILES dan boshqa sahifa olinadi. Next.js aynan shu ishni qiladi, faqat FILES ni u app/ papkasidan o'zi yig'adi. Siz obyektga qator yozmaysiz — papka yaratasiz.

O'zingiz sinab ko'ring: FILES ga "/aloqa" qatorini va menyuga havolasini qo'shing. Keyin "/haqimda" qatorini o'chiring — «Men haqimda» bosilganda "404" chiqadi. Xuddi page.tsx ni o'chirgandek.

3. Dinamik segment: [slug]

3.1 Bitta fayl — ko'p loyiha

v1 da har loyiha — alohida papka edi. Next.js'da buning o'rniga papka nomini kvadrat qavsga olasiz: app/loyihalar/[slug]/. Bu dinamik segment (dynamic segment): "shu joyga istalgan qiymat tushadi, uni slug deb ata". React Router'dagi :slug ning aynan o'zi, faqat yozilishi boshqa.

React Router Next.js
Dinamik segment path: "/loyihalar/:slug" app/loyihalar/[slug]/page.tsx
Qiymatni o'qish useParams() params prop (sahifada)
Qiymat turi satr satr

Qavs ichidagi nom — sizning tanlovingiz: [slug], [id], [nomi]. Shu nom params obyektidagi kalit bo'ladi.

portfolio kanonidagi loyiha sahifasining boshi (pastki qismi — case study jadvali, darsga kerak emas):

tsx
import { LOYIHALAR, loyihaniTop } from "@/malumot/loyihalar";
import { oyniYoz } from "@/malumot/sana";

// Cache Components yoqilgan: params — "runtime data". Ro'yxat
// oldindan ma'lum bo'lsa, build vaqtida hammasi yaratiladi
// (N-17 da chuqurroq); aks holda build "uncached or runtime data
// during prerendering" bilan to'xtaydi.
export function generateStaticParams() {
  return LOYIHALAR.map((l) => ({ slug: l.slug }));
}

// Dinamik segment: /loyihalar/vazifalar → params.slug ===
// "vazifalar" (N-03). Next 15+ da params — Promise.
export default async function LoyihaSahifasi({
  params,
}: PageProps<"/loyihalar/[slug]">) {
  const { slug } = await params;
  const loyiha = loyihaniTop(slug);
  if (!loyiha) {
    return (
      <main id="asosiy">
        <h1>Loyiha topilmadi</h1>
      </main>
    );
  }
  // … sarlavha, sana, texnologiyalar, case study
}

Kanondagi izohlar telefonga sig'ishi uchun qatorlarga bo'lindi, ma'nosi o'sha. Qatorma-qator:

  1. generateStaticParams — "qaysi slug lar borligini build vaqtida ayt" funksiyasi. Uni generateStaticParams darsida chuqur ochamiz; nega bu yerda shartligini pastda «Haqiqiy build xatosi» bo'limida ko'rasiz.
  2. async function — sahifa asinxron komponent. Bu faqat Server Component'da mumkin; oddiy React'da (natija oynasida ham) komponent async bo'la olmaydi.
  3. await params — manzildagi qiymat. /loyihalar/vazifalar da slug — "vazifalar".
  4. loyihaniTop(slug) — src/malumot/loyihalar.ts dagi ro'yxatdan qidiradi. Topilmasa — "Loyiha topilmadi".

Ma'lumot fayli oddiy TypeScript massivi: uchta loyiha (vazifalar, bahor, landing), har birida slug, nomi, sana, texnologiyalar. Yangi loyiha qo'shish — massivga bitta obyekt. Sahifa fayliga tegmaysiz.

3.2 params — nega Promise?

async / await darsidan bilasiz: Promise — "qiymat keyin keladi" degan va'da. Next.js 15 dan boshlab params ham shunday. Sabab — tezlik: Next.js sahifaning manzilga bog'liq bo'lmagan qismini (sarlavha, menyu) params tayyor bo'lishini kutmasdan chiza boshlaydi. await params yozilgan joyda esa haqiqatan kutadi.

Bu bloklar sahifa funksiyasini oddiy funksiya sifatida chaqiradi — Next.js ham ichida shunday qiladi:

ts
async function ProjectPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return `Loyiha: ${slug}`;
}

const params = Promise.resolve({ slug: "vazifalar" });
ProjectPage({ params }).then((html) => console.log(html));

Konsolda:

text
Loyiha: vazifalar

Promise.resolve(...) — darhol tayyor bo'lgan va'da. Next.js params ni xuddi shunday Promise qilib beradi.

Eng ko'p uchraydigan xato — await ni unutish. Eski (Next 14) darslik va maqolalarda params.slug to'g'ridan-to'g'ri yozilgan. TypeScript buni ushlaydi:

ts
async function ProjectPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  return `Loyiha: ${params.slug}`;
}

Xato:

text
TS2339: Property 'slug' does not exist on type 'Promise<{ slug: string; }>'.

Tarjimasi: "Promise<{ slug: string }> turida slug xususiyati yo'q". To'g'ri: slug Promise'ning ichida, Promise'ning o'zida emas. Qutini ochmasdan ichidagi narsani ololmaysiz — await qutini ochadi.

TypeScript bo'lmasa-chi? Biz tajriba qildik: Next 16.4 da params ni turini chetlab o'tib (as unknown as …) to'g'ridan o'qidik. Sahifa yiqilmadi, lekin qiymat bo'sh:

text
/sinxron/vazifalar -> 200 SINXRON=undefined PROMISE=true AWAIT=vazifalar

params.slug — undefined, params instanceof Promise — true, await bilan esa "vazifalar". Next 15 da eski kod uchun vaqtinchalik ko'prik (ogohlantirish bilan ishlash) bor edi; Next 16 da u olib tashlangan. Jim undefined — eng yomon xato turi: sahifa "Loyiha topilmadi" deydi, sababi ko'rinmaydi.

Tekshirib ko'ring: /loyihalar/bahor ochilganda await params nima qaytaradi? /loyihalar ochilganda-chi?

Javob

{ slug: "bahor" }. /loyihalar esa [slug] papkasiga umuman tushmaydi — u app/loyihalar/page.tsx (ro'yxat sahifasi). Dinamik segment bo'sh bo'la olmaydi: [slug] uchun manzilda shu joyda albatta bir segment bo'lishi kerak.

3.3 PageProps — yo'lni biladigan tur

Kanonda sahifa props turi qo'lda yozilmagan: PageProps<"/loyihalar/[slug]">. Bu — global tur, import kerak emas. Uni next typegen buyrug'i yaratadi: Next.js app/ papkasini o'qiydi va har marshrut uchun aniq tur yozadi. portfolio da bu buyruq npm run tip skriptining ichida:

bash
npm run tip
text
> portfolio@2.0.0 tip
> next typegen && tsc --noEmit

Generating route types...
✓ Types generated successfully

Foydasi — yo'l xato yozilsa, TypeScript darhol aytadi. Biz ataylab ikki xato qildik: birida await ni unutdik, ikkinchisida yo'lni "/xato/[slug]" deb yozdik (papka aslida xato2/[slug]):

text
src/app/xato1/[slug]/page.tsx(2,24): error TS2339: Property 'slug' does not exist on type 'Promise<{ slug: string; }>'.
src/app/xato2/[slug]/page.tsx(1,55): error TS2344: Type '"/xato/[slug]"' does not satisfy the constraint 'AppRoutes'.
src/app/xato2/[slug]/page.tsx(2,11): error TS2339: Property 'slug' does not exist on type 'unknown'.

Ikkinchi xabar: ""/xato/[slug]" turi AppRoutes shartiga mos kelmaydi". AppRoutes — loyihadagi hamma marshrutlar ro'yxati (typegen yozgan). Bunday yo'l yo'q — demak xato. Uchinchi xabar ikkinchisining natijasi: tur topilmagani uchun params ichi unknown bo'lib qoldi. Qoida: xatolar zanjirida birinchisini tuzating, qolgani o'zi yo'qoladi.

Muhim: papka qo'shgach yoki nomini o'zgartirgach, turlar eskiradi. next dev ishlab turganda ular o'zi yangilanadi; aks holda npm run tip ni qayta ishga tushiring.

3.4 Natija oynasida: useParams

Sahifa komponenti params ni prop sifatida oladi. Lekin sahifa ichidagi kichik interaktiv komponent (masalan «Ulashish» tugmasi) ham slug ni bilishi kerak bo'lsa-chi? Unga useParams() hook'i bor — React Router'dagidek. Faqat u brauzerda ishlaydigan client komponentda ishlaydi (Client Components darsida), va unda Promise yo'q — qiymat darhol keladi.

Natija oynasida boshlang'ich manzil har doim /. Boshqa manzil va dinamik segment kerak bo'lsa, model sozlamasi bor: ih.boshla(manzil, shablon). Shablon — page.tsx turgan papka yo'li, masalan "/loyihalar/[slug]". Bu Next.js API'si emas, faqat o'quv modelining tugmasi — haqiqiy loyihada bunday qator yozilmaydi. Next.js shablonni app/ papkasidan o'zi biladi.

tsx
import { ih, useParams, usePathname } from "next/navigation";

// Faqat o'quv modeli: /loyihalar/vazifalar sahifasidamiz,
// papka yo'li — app/loyihalar/[slug]/page.tsx
ih.boshla("/loyihalar/vazifalar", "/loyihalar/[slug]");

function ShareButton() {
  const { slug } = useParams<{ slug: string }>();
  return <button>«{slug}» havolasini nusxalash</button>;
}

export default function App() {
  const pathname = usePathname();
  return (
    <main>
      <p>Manzil: {pathname}</p>
      <ShareButton />
    </main>
  );
}

Ekranda:

text
Manzil: /loyihalar/vazifalar
«vazifalar» havolasini nusxalash

Model manzilni shablon bilan solishtiradi: [slug] turgan joydagi segment — slug. Shuning uchun <Link> bilan /loyihalar/bahor ga o'tsangiz, useParams() { slug: "bahor" } beradi — haqiqiy Next.js'dagidek. Farq shundaki, Next.js shablonlarni app/ papkasidan o'zi yig'adi, modelga esa uni qo'lda aytamiz.

4. Hammasini ushlovchi segmentlar

4.1 [...qism] — qolgan hamma segment

Faraz qiling, portfolio da hujjatlar bo'limi bor: /hujjatlar/kirish, /hujjatlar/next/routing, /hujjatlar/next/routing/misollar. Chuqurlik oldindan noma'lum. [slug] faqat bitta segment ushlaydi — bu yerda yetmaydi.

Uch nuqtali dinamik segment — hammasini ushlovchi segment (catch-all): app/hujjatlar/[...qism]/page.tsx. U shu joydan boshlab qolgan hamma segmentni oladi va massiv qilib beradi. React Router'dagi * ning Next.js'dagi o'xshashi, faqat nomli.

Biz tajriba loyihasida shunday sahifa yozdik va next build + next start bilan so'radik. Sahifa JSON.stringify(await params) ni chiqarardi:

text
/hujjatlar/kirish -> 200 PARAMS={"qism":["kirish"]}
/hujjatlar/next/routing -> 200 PARAMS={"qism":["next","routing"]}
/hujjatlar/a/b/c -> 200 PARAMS={"qism":["a","b","c"]}
/hujjatlar -> 404

Uchta narsa:

  • Bitta segment bo'lsa ham — massiv: ["kirish"], satr emas.
  • /hujjatlar/a/b/c — oldindan tayyorlanmagan manzil ham ishladi (status 200).
  • /hujjatlar ning o'zi — 404. Hammasini ushlovchi segment kamida bitta segment talab qiladi.

4.2 [[...qism]] — bo'sh manzil ham

Ikki qavat qavs — ixtiyoriy hammasini ushlovchi segment (optional catch-all): app/qollanma/[[...qism]]/page.tsx. Farqi bitta: /qollanma ning o'zi ham shu sahifaga tushadi. Tajriba:

text
/qollanma -> 200 PARAMS={} kalitlar= qism=undefined
/qollanma/a/b -> 200 PARAMS={"qism":["a","b"]} kalitlar=qism qism=a,b
/qollanma/x -> 200 PARAMS={"qism":["x"]} kalitlar=qism qism=x

/qollanma da params — bo'sh obyekt {}: qism kaliti umuman yo'q, params.qism — undefined. Bo'sh massiv [] emas! Kodda buni tekshirish shart: qism?.length ?? 0 yoki qism ?? [].

Papka /hujjatlar /hujjatlar/a /hujjatlar/a/b
[slug] 404 { slug: "a" } 404
[...qism] 404 { qism: ["a"] } { qism: ["a", "b"] }
[[...qism]] {} { qism: ["a"] } { qism: ["a", "b"] }

4.3 Biz topdik: params dekodlanmagan

Tajribada yana bir narsa chiqdi. Segmentda bo'sh joy yoki maxsus harf bo'lsa, brauzer uni foiz-kodlash (percent-encoding) bilan yuboradi: bo'sh joy — %20 (URL va URLSearchParams darsida ko'rgansiz). Next 16.4 dagi params da u shu holicha qoldi:

text
/hujjatlar/salom%20dunyo -> 200 PARAMS={"qism":["salom%20dunyo"]}
/hujjatlar/o%CA%BBzbek -> 200 PARAMS={"qism":["o%CA%BBzbek"]}
/hujjatlar/a?x=1 -> 200 PARAMS={"qism":["a"]}

"salom dunyo" emas, "salom%20dunyo". Bunday qiymatni ekranga chiqarishdan oldin decodeURIComponent dan o'tkazing. Oxirgi qatorga ham qarang: ?x=1 — query qismi, segment emas; u params ga kirmaydi (uni searchParams o'qiydi, Navigatsiya darsida).

portfolio slug'lari faqat lotin harf va - dan iborat (vazifalar, bahor), shuning uchun kanonda bu muammo chiqmaydi. Lekin o'quvchi slug'ga kirill yoki ' qo'ysa, darhol chiqadi.

4.4 Modelda: segmentlarni moslash

Next.js qanday qilib /hujjatlar/next/routing ni [...qism] ga moslaydi? Quyidagi funksiya — soddalashtirilgan model (Next.js'ning haqiqiy kodi emas), lekin natijalari yuqoridagi haqiqiy tajribaga to'liq mos:

js
function matchRoute(pattern, pathname) {
  const pat = pattern.split("/").filter(Boolean);
  const url = pathname.split("/").filter(Boolean);
  const params = {};
  for (let i = 0; i < pat.length; i++) {
    const seg = pat[i];
    const rest = url.slice(i);
    if (seg.startsWith("[[...")) {
      if (rest.length > 0) params[seg.slice(5, -2)] = rest;
      return params;
    }
    if (seg.startsWith("[...")) {
      if (rest.length === 0) return null;
      params[seg.slice(4, -1)] = rest;
      return params;
    }
    if (url[i] === undefined) return null;
    if (seg.startsWith("[")) params[seg.slice(1, -1)] = url[i];
    else if (seg !== url[i]) return null;
  }
  return url.length === pat.length ? params : null;
}

const show = (pattern, path) =>
  console.log(path, JSON.stringify(matchRoute(pattern, path)));

show("/loyihalar/[slug]", "/loyihalar/bahor");
show("/loyihalar/[slug]", "/loyihalar/bahor/rasm");
show("/hujjatlar/[...qism]", "/hujjatlar/next/routing");
show("/hujjatlar/[...qism]", "/hujjatlar");
show("/qollanma/[[...qism]]", "/qollanma");

Konsolda:

text
/loyihalar/bahor {"slug":"bahor"}
/loyihalar/bahor/rasm null
/hujjatlar/next/routing {"qism":["next","routing"]}
/hujjatlar null
/qollanma {}

null — "mos kelmadi" (Next.js'da — 404). Har shablon segmentini manzil segmenti bilan yonma-yon solishtiramiz: oddiy nom — aynan teng bo'lishi kerak, [x] — istalgan bitta qiymat, [...x] — qolgan hammasi (kamida bitta), [[...x]] — qolgan hammasi (nol ham mumkin).

O'zingiz sinab ko'ring: show("/qollanma/[[...qism]]", "/qollanma/a/b") qatorini qo'shing. Natija tajribadagi {"qism":["a","b"]} bilan bir xil chiqishi kerak.

5. Qaysi papka yutadi?

5.1 Statik segment dinamikdan kuchli

app/loyihalar/[slug]/ bor. Endi app/loyihalar/yangi/page.tsx ni ham qo'shsak, /loyihalar/yangi qaysi sahifani ochadi? Ikkalasi ham mos keladi-ku.

Tajriba javobi:

text
/loyihalar/yangi -> 200 STATIK yangi sahifa

Qoida React Router'dagidek: aniq nom har doim yutadi. Tartib — statik segment, keyin [slug], keyin [...qism], oxiri [[...qism]]. Papkalarni qaysi tartibda yaratganingiz ahamiyatsiz — Next.js o'zi saralaydi.

5.2 Ikki xil ism bitta joyda — build xatosi

Bir darajada ikkita dinamik papka bo'lsa-chi? app/loyihalar/[slug]/ yoniga [id]/ qo'shdik:

text
> Build error occurred
Error: Ambiguous app routes detected:
Ambiguous route pattern "/loyihalar/[*]" matches multiple routes:
  - /loyihalar/[id]
  - /loyihalar/[slug]
These routes cannot be distinguished from each other when matching URLs. Please ensure that dynamic segments have unique patterns or use different static segments.

Tarjimasi: "Noaniq marshrutlar topildi. /loyihalar/[*] shabloni bir nechta marshrutga mos keladi. Manzilni moslashda ularni bir-biridan ajratib bo'lmaydi." Haqiqatan: /loyihalar/bahor — bu id mi yoki slug mi? Ajratib bo'lmaydi. Yechim — bitta dinamik papka yoki boshqa statik segment (/loyihalar/raqam/[id]).

Ikkinchi to'qnashuv: app/qollanma/page.tsx va app/qollanma/[[...qism]]/ birga:

text
> Build error occurred
Error: You cannot define a route with the same specificity as an optional catch-all route ("/qollanma" and "/qollanma/[[...qism]]").

"Ixtiyoriy hammasini ushlovchi segment bilan bir xil aniqlikdagi marshrut yozib bo'lmaydi". Sababi «[[...qism]] — bo'sh manzil ham» bo'limidagi jadvalda: [[...qism]] /qollanma ning o'zini ham oladi — page.tsx bilan ikkalasi bitta manzilga da'vo qiladi. Yechim — bittasini tanlang: yo [[...qism]], yo page.tsx + [...qism].

Tekshirib ko'ring: papkalar app/blog/page.tsx, app/blog/[slug]/page.tsx, app/blog/arxiv/page.tsx. /blog/arxiv, /blog/salom va /blog/2026/10 qaysi sahifani ochadi?

Javob

/blog/arxiv — statik arxiv (aniq nom yutadi). /blog/salom — [slug], { slug: "salom" }. /blog/2026/10 — 404: [slug] faqat bitta segment oladi, ikkitasi uchun [...qism] kerak edi.

6. Haqiqiy build xatosi: Cache Components va params

portfolio skeleti create-next-app 16.4 dan keldi va next.config.ts da Cache Components yoqilgan (cacheComponents: true). Bu rejimda Next.js har sahifani build vaqtida oldindan tayyorlashga urinadi. [slug] sahifasi esa params ni kutadi — build vaqtida qaysi slug'lar borligi noma'lum.

Kanon generateStaticParams ni yozmasdan build qilib ko'rdi. Natija:

text
✓ Compiled successfully in 2.3s
Error: Route "/loyihalar/[slug]": Next.js encountered uncached or runtime data during prerendering.
  - Start the app in development mode by running `next dev`, then open "/loyihalar/[slug]" in your browser to investigate the error.
Error occurred prerendering page "/loyihalar/[slug]". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /loyihalar/[slug]/page: /loyihalar/[slug], exiting the build.

Tarjimasi: "/loyihalar/[slug] marshruti: oldindan chizish (prerender) paytida Next.js keshlanmagan yoki ish vaqtidagi (runtime) ma'lumotga duch keldi". Bu yerda "ish vaqtidagi ma'lumot" — aynan params. Next 15 da Next.js bunday sahifani jimgina "har so'rovda chiziladigan" qilardi. Next 16 + Cache Components esa jim qolmaydi: qaror qabul qilishni so'raydi.

Bir yechim — generateStaticParams(): build'ga "3 ta slug bor" deb aytish. Kanon shuni tanladi va build o'tdi:

text
Route (app)
┌ ○ /
├ ○ /_not-found
├ ○ /aloqa
├ ○ /haqimda
├ ○ /icon.svg
├ ○ /loyihalar
└   /loyihalar/[slug]
  ├ ◐ /loyihalar/[slug]
  ├ ○ /loyihalar/vazifalar
  ├ ○ /loyihalar/bahor
  └ ○ /loyihalar/landing


○  (Static)             prerendered as static content
◐  (Partial Prerender)  prerendered as static HTML with dynamic server-streamed content

Jadvalni o'qing:

Belgi Ma'nosi
○ Statik — build vaqtida tayyor HTML (v1 dagi index.html kabi)
◐ PPR (Partial Prerendering — qisman prerender): tayyor qobiq, noma'lum slug kelsa qolgani so'rovda chiziladi

Uchta loyiha — ○: tayyor fayllar. ◐ /loyihalar/[slug] — qolgan har qanday slug uchun qobiq (shell). Rendering turlari Rendering strategiyalari va Cache Components I darslarida to'liq ochiladi; bugun belgilarni tanish yetarli.

Next 16.4 build jadvalida "Size" va "First Load JS" ustunlari yo'q (eski versiyalarda bor edi). Shuning uchun kanon JS hajmini brauzerda o'lchaydi.

6.1 Topilma: noma'lum loyiha — status 200

Kanon next start ustida har sahifani so'radi. Bitta qator tashvishli:

text
### /loyihalar/yoq — status 200, TTFB 119 ms, DCL 518 ms; JS 6 fayl 135.4 KB tarmoq (gzip), 454.2 KB xom, CSS 1.8 KB, shrift 0.0 KB
axe: 0
konsol: 0 (hydration 0)
  cache-control: private, no-cache, no-store, max-age=0, must-revalidate
  x-nextjs-prerender: 1

Mavjud bo'lmagan loyiha — 200 (muvaffaqiyat) statusi bilan "Loyiha topilmadi" sahifasi. Odam uchun to'g'ri ko'rinadi. Google uchun esa bu "haqiqiy sahifa": u /loyihalar/yoq ni indekslashi mumkin. Bunday sahifaning nomi — soft 404 (yumshoq 404). To'g'ri yo'l — Next.js'ning notFound() funksiyasi va 404 statusi. Uni keyingi darsdan keyin qo'shamiz, kanon ham shunday qiladi.

Tekshirib ko'ring: build jadvalida /loyihalar/vazifalar oldida qaysi belgi turibdi va bu nima degani?

Javob

○ — statik: build vaqtida tayyor HTML yaratilgan, har so'rovda qayta chizilmaydi. Buni generateStaticParams ta'minladi — u vazifalar slug'ini build'ga oldindan aytdi.

7. Portfolio qadami: v1 sahifalari Next.js'da

Bu dars va keyingi dars portfolio ning bitta holatini quradi. Bugun — sahifalar va dinamik segment. Layout va template (header, footer, kirish animatsiyasi) — keyingi darsda, commit ham o'sha yerda.

7.1 Fayllar

text
src/
├── app/
│   ├── page.tsx              → /
│   ├── haqimda/page.tsx      → /haqimda
│   ├── loyihalar/
│   │   ├── page.tsx          → /loyihalar
│   │   └── [slug]/page.tsx   → /loyihalar/vazifalar …
│   └── aloqa/page.tsx        → /aloqa
├── komponentlar/
│   └── LoyihaKartasi.tsx
└── malumot/
    ├── loyihalar.ts          (3 loyiha)
    └── sana.ts               (oyniYoz)

v1 dagi loyihalar/bahor/index.html endi alohida papka emas — u loyihalar.ts dagi bitta obyekt. vazifalar (09–22-qismlardagi ilovangiz) case study bilan birinchi loyiha bo'ldi: vazifa, yechim bosqichlari, raqamlar jadvali, saboqlar.

sana.ts dagi oyniYoz("2026-09") — "2026-yil sentabr". Nega Intl.DateTimeFormat emas? 21-qismda Chrome Intl o'zbek lotin sanasini to'g'ri chiqarmasligini topgandik. Kanon oy nomlarini qo'lda yozdi. Bu qaror Hydration darsida yana bir sababga ega bo'ladi.

Ro'yxat sahifasidagi kartalar LoyihaKartasi komponentida. Kartada sarlavha — <Link href={/loyihalar/${loyiha.slug}}>: dinamik segmentga havola oddiy shablon satri bilan yasaladi.

7.2 Tekshiruv

npm run lint, npm run tip, npm run format:check — 0 xato. Build — yuqoridagi jadval (8 s). Kanonning brauzer o'lchovi:

Sahifa Status JS (gzip)
/, /haqimda, /loyihalar, /aloqa 200 135.4 KB
/loyihalar/vazifalar, bahor, landing 200 135.4 KB
/loyihalar/yoq 200 (xato — keyingi qadam) 135.4 KB
/yoq 404 135.4 KB

JS bo'sh skeletdagi 132.4 KB dan 135.4 KB ga oshdi: +3.0 KB — next/link. TTFB (birinchi bayt vaqti) statik sahifalarda 4–12 ms. axe — 0 buzilish (faqat /yoq da 3 ta: Next.js'ning standart 404 sahifasida main yo'q — maxsus fayllar darsida o'zimiznikini yozamiz).

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Papka bor, sahifa 404 Ichida page.tsx yo'q (yoki Page.tsx, index.tsx) Fayl nomi aynan page.tsx
TS2344 … AppPageConfig page.tsx da export default yo'q export default function …
Property 'slug' does not exist on type 'Promise<…>' await params unutilgan const { slug } = await params
slug — undefined, xato yo'q params.slug turini chetlab o'qilgan await; as bilan tur aldamang
TS2344 … 'AppRoutes' PageProps dagi yo'l papkaga mos emas Papka yo'lini aynan yozing, npm run tip
Ambiguous app routes detected [id] va [slug] bir joyda Bitta dinamik papka
… same specificity as an optional catch-all route page.tsx va [[...x]] birga Bittasini tanlang
uncached or runtime data during prerendering Cache Components + params, ro'yxat yo'q generateStaticParams
Matnda %20 params dekodlanmagan decodeURIComponent
qism.length da TypeError [[...qism]] ning bo'sh manzilida qism yo'q qism ?? []

9. Mashqlar

Mashqlar kurs/mashqlar/23/03-routing/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): papka va manzil

app/ ichidagi papkalar: page.tsx, blog/page.tsx, blog/[slug]/page.tsx, dokon/[...kategoriya]/page.tsx, yordam/[[...mavzu]]/page.tsx.

  • /blog/salom → params: { slug: "" }
  • /dokon/kiyim/erkaklar → { kategoriya: ] }
  • /dokon → status
  • /yordam → params:
  • /yordam/tolov → { mavzu: ] }
Yechim
  • salom — [slug] bitta segment oladi, satr sifatida.
  • ["kiyim", "erkaklar"] — hammasini ushlovchi segment doim massiv beradi.
  • 404 — [...kategoriya] kamida bitta segment talab qiladi, dokon/page.tsx esa yo'q.
  • {} — ixtiyoriy variantda bo'sh manzil ham mos, lekin kalit umuman yo'q ([] emas).
  • ["tolov"] — bitta segment bo'lsa ham massiv.

2-mashq (o'rta): modelga blog qo'shing

«Fayl-routing modeli» bo'limidagi blokni oling. FILES ga "/blog" sahifasini qo'shing: unda <Link href="/blog/salom"> havolasi bo'lsin. Keyin /blog/[slug] uchun sahifa qo'shing — u "Maqola: salom" deb yozsin. Buning uchun App da FILES[pathname] topilmasa, pathname ni /blog/ bilan boshlanishini tekshiring va qolgan qismini slug deb oling (pathname.slice(6) — satr metodlari). Ichma-ich / bo'lsa (/blog/a/b) — 404.

Yechim
tsx
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { ReactNode } from "react";

const FILES: Partial<Record<string, () => ReactNode>> = {
  "/": () => <h1>Bosh sahifa</h1>,
  "/blog": () => (
    <>
      <h1>Blog</h1>
      <Link href="/blog/salom">Salom maqolasi</Link>
    </>
  ),
};

// app/blog/[slug]/page.tsx o'rnida
function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>Maqola: {params.slug}</h1>;
}

function findPage(pathname: string): ReactNode {
  const Page = FILES[pathname];
  if (Page) return <Page />;
  if (pathname.startsWith("/blog/")) {
    const slug = pathname.slice(6);
    if (slug && !slug.includes("/")) {
      return <BlogPost params={{ slug }} />;
    }
  }
  return <h1>404 — sahifa topilmadi</h1>;
}

export default function App() {
  const pathname = usePathname();
  return (
    <>
      <nav>
        <Link href="/">Bosh sahifa</Link>{" "}
        <Link href="/blog">Blog</Link>
      </nav>
      <p>Manzil: {pathname}</p>
      {findPage(pathname)}
    </>
  );
}

Ekranda:

text
Bosh sahifa Blog
Manzil: /blog/salom
Maqola: salom

Statik FILES avval tekshiriladi — «Qaysi papka yutadi?» bo'limidagi qoida: aniq nom dinamikdan kuchli. /blog/arxiv ni FILES ga qo'shsangiz, u BlogPost ga tushmaydi.

3-mashq (qiyin): qo'llanma uchun "non yo'li"

app/qollanma/[[...qism]]/page.tsx uchun yordamchi funksiya yozing: breadcrumb(qism) — params.qism ni oladi va "Qo'llanma › next › salom dunyo" kabi satr qaytaradi. Shartlar:

  • qism — undefined bo'lishi mumkin (/qollanma): natija "Qo'llanma".
  • Segmentlar dekodlanmagan keladi ("salom%20dunyo") — decodeURIComponent dan o'tkazing.
  • Buzuq kodlash ("%E0") decodeURIComponent da URIError tashlaydi — bunday segmentni o'zgartirmasdan qoldiring (try/catch — xatolarni ushlash).

Sinov: breadcrumb(undefined), breadcrumb(["next", "salom%20dunyo"]), breadcrumb(["%E0"]).

Yechim
js
function decodeSafe(segment) {
  try {
    return decodeURIComponent(segment);
  } catch {
    return segment; // buzuq kodlash — o'zgarishsiz
  }
}

function breadcrumb(qism) {
  const parts = (qism ?? []).map(decodeSafe);
  return ["Qo'llanma", ...parts].join(" › ");
}

console.log(breadcrumb(undefined));
console.log(breadcrumb(["next", "salom%20dunyo"]));
console.log(breadcrumb(["%E0"]));

Konsolda:

text
Qo'llanma
Qo'llanma › next › salom dunyo
Qo'llanma › %E0

?? [] — undefined ni bo'sh massivga aylantiradi, shunda map xatosiz ishlaydi. catch qavssiz (catch {) — xato obyekti kerak bo'lmasa shunday yoziladi.

4-mashq: Portfolio qadami

O'z portfolio ingizga v1 sahifalarini ko'chiring: app/page.tsx, haqimda/, loyihalar/, loyihalar/[slug]/, aloqa/. Loyihalar — src/malumot/loyihalar.ts massivida (kamida 3 ta, birinchisi — vazifalar, case study bilan). Matnlarni v1 dan oling — o'z ismingiz, o'z loyihalaringiz. [slug] sahifasida await params va generateStaticParams. Tekshiring: npm run tip va npm run build toza, build jadvalida har loyiha ○.

Yechim

Namuna — «Portfolio qadami» bo'limidagi tuzilma va loyiha sahifasi. Build jadvalingizda ◐ /loyihalar/[slug] va har loyihangiz uchun bitta ○ qator bo'lishi kerak. /loyihalar/yoq hali 200 qaytaradi — bu kutilgan, uni maxsus fayllar darsida tuzatamiz. Commit hali qilmang: keyingi darsda layout bilan birga bitta commit bo'ladi.

10. Real ishda

  • Har Next.js loyihasi shu tuzilmada: intervyuda "app/shop/[...slug]/page.tsx qaysi manzillarni ochadi?" kabi savollar tez-tez beriladi.
  • Do'konlar kategoriya daraxti uchun [...kategoriya], hujjat saytlari (Next.js'ning o'z hujjatlari ham) [[...slug]] ishlatadi: bitta sahifa fayli minglab sahifaga xizmat qiladi.
  • Next 14 → 15/16 ko'chishi — haqiqiy loyihalarda eng ko'p ish talab qilgan o'zgarishlardan biri aynan params ning Promise bo'lishi. Next.js buning uchun avtomatik tuzatuvchi (codemod) chiqargan — Versiyalarni yangilash darsida.
  • PageProps va typegen — havola va yo'l xatolarini build'dan oldin ushlaydi. Katta jamoalarda "sahifa nomini o'zgartirdim, 40 ta havola buzildi" muammosining oldini oladi.

Xulosa

  • app/ ichidagi papka — URL segmenti, page.tsx — shu segmentning sahifasi; page.tsx siz papka ochilmaydi.
  • page.tsx da export default majburiy; funksiya nomi manzilga ta'sir qilmaydi.
  • [slug] — bitta segment (satr), [...qism] — kamida bitta (massiv), [[...qism]] — nol yoki ko'p (bo'shda {}).
  • params — Promise: const { slug } = await params; PageProps<"/yo'l"> turi typegen'dan keladi.
  • Aniq nom dinamikdan kuchli; bir darajada ikki xil dinamik ism — build xatosi.
  • Cache Components'da dinamik sahifa generateStaticParams so'raydi; noma'lum slug hozircha 200 qaytaradi.

Keyingi dars: Layout va template — header va footer har sahifada takrorlanmasligi uchun umumiy qobiq, va nega template har o'tishda qayta yaratiladi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js routing: page.tsx, papkalar va dinamik segmentlar ([slug], [...slug]) — IlmHamroh