IlmHamroh
JavaScript Full-stack/23-qism. Next.js6/43-dars22 daqiqa
Mundarija (37)

Next.js navigatsiya: Link, useRouter, usePathname va useSearchParams

Qisqacha: Next.js'da sahifalar orasida next/link dagi <Link href> bilan o'tiladi: u oddiy <a> chizadi, lekin sahifani qayta yuklamaydi va havola ekranda ko'ringanda manzilni oldindan yuklab (prefetch) qo'yadi. Kodda o'tish kerak bo'lsa — useRouter() (push, replace, back, refresh). Joriy manzilni usePathname(), query qismini (?teg=React) useSearchParams() o'qiydi — bular faqat client komponentda ishlaydi. Serverda esa redirect() 307, permanentRedirect() 308 statusi bilan boshqa manzilga yuboradi.

Bu darsda

  • <Link> ning href, replace, scroll, prefetch imkoniyatlarini va prefetch narxini o'lchangan raqam bilan bilasiz.
  • usePathname bilan menyuda joriy sahifani belgilaysiz.
  • useRouter bilan «Orqaga» tugmasi va dasturiy o'tish yozasiz.
  • Teg filtrini URL'ga ko'chirasiz (?teg=React) va nega useSearchParams <Suspense> talab qilishini haqiqiy build xatosi bilan ko'rasiz.
  • redirect va permanentRedirect farqini statuslar bilan tushuntirasiz.

Oldin bilishingiz kerak: Maxsus fayllar, Fayl-asosli routing, React Router III: dasturiy navigatsiya, URL — holat saqlash joyi.

1. Nega bu kerak?

v1 portfolio'da ikkita kichik JavaScript funksiya bor edi. Birinchisi — joriyHavolaniBelgila (Atributlar va dataset darsida): menyuda hozirgi sahifaning havolasiga aria-current="page" qo'yardi. Ikkinchisi — loyihalar teg filtri (Ko'p elementni samarali chizish): «React» bosilsa, faqat React loyihalari qolardi.

Filtrning bitta kamchiligi bor edi: tanlov xotirada yashardi. Do'stingizga "mening React loyihalarimni ko'r" deb havola yuborsangiz, u hamma loyihani ko'rardi. Sahifani yangilasangiz ham filtr yo'qolardi.

URL — holat saqlash joyi darsida bu muammoning yechimini bilasiz: filtr URL'da bo'lsin — /loyihalar?teg=React. Havolani ulashsa ham, yangilasa ham — tanlov joyida. React Router'da buni useSearchParams va <Link> bilan qilgandingiz.

Next.js'da g'oyalar o'sha, nomlar biroz boshqa. Bu darsda portfolio ga uchta kichik komponent qo'shamiz: joriy havola (NavHavola), «Orqaga» tugmasi (OrqagaTugmasi) va URL'dagi teg filtri (TegFiltri).

Hayotiy o'xshatish: taksi ilovasi. Manzilni yozasiz — ilova yo'lni oldindan hisoblab qo'yadi (prefetch). «Buyurtma» bosilganda mashina darhol yo'lga chiqadi. Manzil (URL) — buyurtmaning o'zida, telefonni o'chirib yoqsangiz ham yo'qolmaydi.

2.1 href: satr yoki obyekt

<Link> ni Fayl-asosli routing darsidan bilasiz. href ikki ko'rinishda bo'ladi:

tsx
import Link from "next/link";

<Link href="/loyihalar?teg=React">React loyihalari</Link>

<Link href={{ pathname: "/loyihalar", query: { teg: "React" } }}>
  React loyihalari
</Link>

Ikkalasi ham bir xil <a href="/loyihalar?teg=React"> chizadi. Obyekt shakli query'da maxsus belgi (bo'sh joy, &) bo'lganda qulay: kodlashni Next.js o'zi qiladi.

Natija oynasida tekshiramiz. Eslatma: u yerdagi next/link — o'quv modeli, haqiqiy Next.js'dagi server va prefetch u yerda yo'q.

tsx
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";

const TAG = "Node va Express";

export default function App() {
  const pathname = usePathname();
  const tag = useSearchParams().get("teg");
  return (
    <>
      <Link href={{ pathname: "/loyihalar", query: { teg: TAG } }}>
        Node va Express
      </Link>
      <p>Manzil: {pathname}</p>
      <p>teg: {tag ?? "yo'q"}</p>
    </>
  );
}

Ekranda:

text
Node va Express
Manzil: /loyihalar
teg: Node va Express

Havolaning haqiqiy href atributi — /loyihalar?teg=Node+va+Express: bo'sh joy + ga kodlandi. get("teg") esa uni qaytarib ochib berdi. Kodlashni qo'lda qilsangiz, encodeURIComponent kerak bo'lardi — kanondagi TegFiltri aynan shunday qiladi.

2.2 replace, scroll, prefetch

Prop Sukut Nima qiladi
replace false Tarixga yangi qadam qo'shmaydi, joriysini almashtiradi
scroll true O'tgach sahifa boshi ko'rinmasa — tepaga aylantiradi
prefetch "auto" Havola ekranda ko'ringanda manzilni oldindan yuklaydi
onNavigate — O'tishdan oldin chaqiriladi, e.preventDefault() to'xtatadi

replace ni React Router III darsidan bilasiz: filtr har bosishda tarixga yangi qadam qo'shsa, «Orqaga» tugmasi foydalanuvchini har bir tegdan qaytarib chiqaradi. Ba'zan shuni xohlaysiz, ba'zan yo'q.

scroll ga Next.js hujjatidagi qoida: yangi sahifa ekranda ko'rinib turgan bo'lsa, joy saqlanadi; ko'rinmasa — sahifa boshiga aylantiriladi. scroll={false} bu aylantirishni o'chiradi. Teg filtri uchun aynan kerak: foydalanuvchi ro'yxatning o'rtasida «React» ni bosdi — sahifa tepaga sakramasligi kerak.

2.3 Prefetch: biz o'lchadik

Prefetch — <Link> ning eng katta afzalligi va eng katta narxi. Havola ekranga chiqishi bilan Next.js shu sahifa uchun ma'lumotni (RSC payload — Server Components darsida ochamiz) fonda so'rab qo'yadi. Bosilganda tarmoqni kutmaydi.

Vaqt bo'yicha:

sequenceDiagram
  participant B as Brauzer
  participant S as Server
  Note over B: Sahifa ochildi, havolalar ko'rindi
  B->>S: RSC: /loyihalar (prefetch)
  B->>S: RSC: /aloqa (prefetch)
  S-->>B: payload'lar xotiraga
  Note over B: Foydalanuvchi «Loyihalar» ni bosdi
  Note over B: Tarmoqsiz — darhol yangi sahifa

Prefetch faqat production'da ishlaydi (next dev da yo'q). Biz portfolio ning holat 06 nusxasini next build + next start bilan ishga tushirdik. Headless Chrome'da bosh sahifani ochib, RSC: 1 sarlavhali so'rovlarni sanadik, keyin menyudagi «Loyihalar» ni bosdik:

text
yuklash: / (prefetch) ×1
yuklash: /haqimda (prefetch) ×2
yuklash: /loyihalar (prefetch) ×2
yuklash: /aloqa (prefetch) ×2
yuklash: /loyihalar/vazifalar (prefetch) ×2
yuklash: /loyihalar/bahor (prefetch) ×1
manzil: /loyihalar | h1: Loyihalar | to'liq yuklash: 1 1

O'qiymiz:

  • Sahifa ochilishi bilan 10 ta prefetch so'rovi ketdi — ekrandagi har havola uchun (menyu va bosh sahifadagi loyiha kartalari). Ba'zisi ikki marta: Next 16.4 da Partial Prefetching yoqilgan — avval sahifaning statik qobig'i, keyin qolgani.
  • «Loyihalar» bosilgandan keyin yangi so'rov yo'q — hammasi oldindan olingan edi. Ro'yxatda "bosilgach" qatori umuman chiqmadi.
  • to'liq yuklash: 1 — brauzer sahifani bir marta yuklagan, o'tish qayta yuklashsiz.

Narxi: foydalanuvchi bosmagan sahifalar ham yuklandi. Kanon Performance bosqichida buni yakuniy holatda o'lchadi: sahifa ochilganda 13–22 ta RSC so'rov, 56–61 KB (xom). Kanon prefetch'ni o'chirmadi — portfolio kichik, o'tish bir zumda. Yuzlab havolali sahifada (katalog, jadval) esa prefetch={false} o'ylab ko'rishga arziydi.

Tekshirib ko'ring: next dev da Network panelini ochib menyudan o'tsangiz, prefetch so'rovlarini ko'rasizmi?

Javob

Yo'q. Prefetch faqat production build'da (next build + next start) ishlaydi. Dev'da sahifa bosilganda so'raladi. Shuning uchun prefetch'ni o'lchash yoki tekshirish — doim prod rejimda.

3. usePathname — joriy manzil

3.1 Kanondagi NavHavola

Layout va template darsida va'da bergan edik: layout joriy manzilni bilmaydi, uni kichik client komponent o'qiydi. Mana u:

tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

// Joriy sahifani belgilash (v1 W-05 dagi joriyHavolaniBelgila)
// — endi usePathname bilan (N-06).
export function NavHavola({
  href,
  children,
}: {
  href: string;
  children: React.ReactNode;
}) {
  const yol = usePathname();
  const joriy =
    href === "/" ? yol === "/" : yol === href || yol.startsWith(`${href}/`);
  return (
    <Link href={href} aria-current={joriy ? "page" : undefined}>
      {children}
    </Link>
  );
}

Uchta narsa:

  • "use client" — usePathname hook, hook'lar esa faqat brauzerda ham ishlaydigan komponentda. Nega aynan shunday — Client Components darsida.
  • joriy qoidasi. /loyihalar/vazifalar da ham «Loyihalar» joriy bo'lishi kerak — shuning uchun startsWith(${href}/). Lekin / hamma manzilning boshi: uni alohida, faqat aniq tenglik bilan tekshiramiz.
  • / qo'shilgani bejiz emas. startsWith(href) yozilsa, /loyihalar-arxiv ham «Loyihalar» ni yoqib yuborardi.

SaytSarlavhasi (server komponent) menyuni MENYU massividan chizadi va har bandni NavHavola ga beradi. Layout serverda qoladi — faqat to'rtta kichik havola brauzerda jonlanadi.

3.2 Natija oynasida

Kanondagi qoida jonli. Model ih.boshla bilan /loyihalar/vazifalar dan boshlaydi (bu — o'quv modeli sozlamasi, Next.js API'si emas):

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

ih.boshla("/loyihalar/vazifalar");

type Props = { href: string; children: ReactNode };

function NavLink({ href, children }: Props) {
  const path = usePathname();
  const current =
    href === "/"
      ? path === "/"
      : path === href || path.startsWith(`${href}/`);
  console.log(`${href}: ${current ? "joriy" : "-"}`);
  return (
    <Link href={href} aria-current={current ? "page" : undefined}>
      {current ? `[${children}]` : children}
    </Link>
  );
}

export default function App() {
  return (
    <nav>
      <NavLink href="/">Bosh sahifa</NavLink>{" "}
      <NavLink href="/loyihalar">Loyihalar</NavLink>
    </nav>
  );
}

Konsolda:

text
/: -
/loyihalar: joriy
/: joriy
/loyihalar: -

Ekranda:

text
[Bosh sahifa] Loyihalar

Avval /loyihalar/vazifalar da «Loyihalar» joriy edi — ichki sahifa bo'lsa ham. «Bosh sahifa» bosilgach belgi ko'chdi. Kvadrat qavs — faqat natija oynasida ko'rinsin deb; kanonda belgi CSS'da ([aria-current="page"] selektori).

4. useRouter — kodda o'tish

4.1 Metodlar

useRouter() — next/navigation dagi hook. React Router dagi useNavigate ning o'rni, faqat metodlari alohida:

Metod Nima qiladi
push(href) Yangi manzilga o'tadi, tarixga qo'shadi
replace(href) O'tadi, joriy tarix qadamini almashtiradi
back() / forward() Brauzer tarixida orqaga / oldinga
refresh() Server komponentlarni qayta so'raydi, client state saqlanadi
prefetch(href) Manzilni qo'lda oldindan yuklaydi

Diqqat: next/router emas, next/navigation. next/router — eski Pages Router'niki. App Router'da undan import qilsangiz, Next.js "NextRouter was not mounted" xatosini beradi (Pages Router merosi darsida). Eski maqolalarda ko'p uchraydi.

refresh() ning g'alati tomoni: u sahifani qayta yuklamaydi. Serverdan yangi ma'lumot oladi va faqat server qismlarini yangilaydi; inputga yozilgan matn, ochiq menyu saqlanadi. Buni Cache Components II darsida ishlatamiz.

Qachon <Link>, qachon router.push? Qoida oddiy: foydalanuvchi bosadigan havola bo'lsa — <Link>. U <a> chizadi: yangi tabda ochish, havolani nusxalash, ekran o'quvchi — hammasi ishlaydi. router.push — kod natijasida o'tish uchun: forma saqlangandan keyin, taymer tugaganda.

4.2 OrqagaTugmasi

Kanondagi komponent — 404 sahifasidagi «Orqaga qaytish»:

tsx
"use client";

import { useRouter } from "next/navigation";

// useRouter — dasturiy navigatsiya (N-06): tarixda bir qadam
// orqaga.
export function OrqagaTugmasi() {
  const router = useRouter();
  return (
    <button type="button" onClick={() => router.back()}>
      Orqaga qaytish
    </button>
  );
}

Bu yerda <Link> emas, <button>: "orqaga" — bitta manzil emas, foydalanuvchining shaxsiy tarixi. Kanon oqim tekshiruvi (/loyihalar dan noto'g'ri havolaga o'tib, 404 da «Orqaga qaytish»):

text
Orqaga: /loyihalar

Foydalanuvchi qaerdan kelgan bo'lsa, o'sha yerga qaytdi.

4.3 Natija oynasida: tarix

push, replace va back ning farqi — tarixda. Ketma-ket bosing:

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

export default function App() {
  const router = useRouter();
  const pathname = usePathname();
  console.log("manzil:", pathname);
  return (
    <>
      <button onClick={() => router.push("/a")}>push /a</button>{" "}
      <button onClick={() => router.replace("/b")}>
        replace /b
      </button>{" "}
      <button onClick={() => router.back()}>back</button>
      <p>Manzil: {pathname}</p>
    </>
  );
}

Konsolda:

text
manzil: /
manzil: /a
manzil: /b
manzil: /

Ekranda:

text
push /a replace /b back
Manzil: /

Tarix qadamma-qadam:

Qadam Tarix Joriy
boshlanish / /
push("/a") /, /a /a
replace("/b") /, /b /b
back() /, /b /

replace /a ni /b bilan almashtirdi — shuning uchun back() /a ga emas, to'g'ridan / ga qaytdi.

5. useSearchParams — URL'dagi filtr

5.1 Faqat o'qish uchun

useSearchParams() — ?teg=React qismini URLSearchParams ko'rinishida qaytaradi: .get("teg"), .has(), .getAll() ishlaydi. Lekin u faqat o'qish uchun: .set() chaqirsangiz, Next.js xato beradi.

tsx
import { useSearchParams } from "next/navigation";

export default function App() {
  const params = useSearchParams();
  function tryChange() {
    try {
      params.set("teg", "React");
    } catch (error) {
      console.log((error as Error).message);
    }
  }
  return <button onClick={tryChange}>Sinash</button>;
}

Konsolda:

text
Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams

Tarjimasi: "ReadonlyURLSearchParams da bu metod yo'q". Model bu xabarni Next.js'dagi matn bilan beradi. Sabab: URL o'zgarmasa, React sahifani qayta chizmaydi. Query'ni o'zgartirish yo'li bitta — yangi manzilga o'tish: <Link href="?teg=React"> yoki router.push(...). Yangi qiymatlar to'plamini yasash kerak bo'lsa — nusxa oling: new URLSearchParams(params.toString()), o'zgartiring, keyin o'ting.

5.2 Kanondagi TegFiltri

tsx
"use client";

import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";

type Element = { kalit: string; teglar: string[]; tugun: React.ReactNode };

// v1 dagi teglar filtri (W-09/W-11) — endi URL'da (?teg=React).
export function TegFiltri({ elementlar }: { elementlar: Element[] }) {
  const teg = useSearchParams().get("teg");
  const yol = usePathname();
  const hammaTeglar = [...new Set(elementlar.flatMap((e) => e.teglar))].sort();
  const korinadi = teg
    ? elementlar.filter((e) => e.teglar.includes(teg))
    : elementlar;

  return (
    <>
      <nav aria-label="Texnologiya bo'yicha filtr">
        <ul className="teglar">
          <li>
            <Link
              href={yol}
              aria-current={teg ? undefined : "page"}
              scroll={false}
            >
              Hammasi
            </Link>
          </li>
          {hammaTeglar.map((t) => (
            <li key={t}>
              <Link
                href={`${yol}?teg=${encodeURIComponent(t)}`}
                aria-current={t === teg ? "page" : undefined}
                scroll={false}
              >
                {t}
              </Link>
            </li>
          ))}
        </ul>
      </nav>
      <p role="status">{korinadi.length} ta loyiha ko'rsatilmoqda</p>
      <ul className="loyihalar">
        {korinadi.map((e) => (
          <li key={e.kalit}>{e.tugun}</li>
        ))}
      </ul>
    </>
  );
}

Kanon izohi qisqartirildi (ikkinchi yarmi — tugun haqida — Kompozitsiya darsida). Ikki qator kanonda 70 belgidan uzun, aynan qoldirildi.

Qatorma-qator:

Qism Nima qiladi
useSearchParams().get("teg") ?teg= qiymati yoki null
new Set(…flatMap…) Hamma loyihalardagi teglar, takrorsiz
filter Tanlangan tegli loyihalar
scroll={false} Bosilganda sahifa tepaga sakramaydi
role="status" Ekran o'quvchi "1 ta loyiha" ni o'qiydi

Filtr tugmalari — havolalar, onClick li tugmalar emas. Har teg — haqiqiy manzil: yangi tabda ochish, nusxalash, ulashish ishlaydi. v1 dagi filtrning kamchiligi shu bilan yopildi.

5.3 Natija oynasida: filtr modeli

Kanon komponentining soddalashtirilgan nusxasi (tugun o'rniga oddiy nom):

tsx
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";

const PROJECTS = [
  { slug: "vazifalar", tags: ["React", "TypeScript"] },
  { slug: "bahor", tags: ["HTML", "CSS"] },
  { slug: "landing", tags: ["HTML"] },
];

export default function App() {
  const tag = useSearchParams().get("teg");
  const path = usePathname();
  const allTags = [...new Set(PROJECTS.flatMap((p) => p.tags))];
  allTags.sort();
  const visible = tag
    ? PROJECTS.filter((p) => p.tags.includes(tag))
    : PROJECTS;
  return (
    <>
      <nav>
        <Link href={path} scroll={false}>Hammasi</Link>
        {allTags.map((t) => (
          <span key={t}>
            {" "}
            <Link
              href={`${path}?teg=${encodeURIComponent(t)}`}
              scroll={false}
            >
              {t === tag ? `[${t}]` : t}
            </Link>
          </span>
        ))}
      </nav>
      <p role="status">{visible.length} ta loyiha ko'rsatilmoqda</p>
      <ul>
        {visible.map((p) => (
          <li key={p.slug}>{p.slug}</li>
        ))}
      </ul>
    </>
  );
}

Ekranda:

text
Hammasi CSS HTML [React] TypeScript
1 ta loyiha ko'rsatilmoqda
vazifalar

Avval «HTML» (2 ta), keyin «React» (1 ta). Kanonning haqiqiy brauzer tekshiruvi ham xuddi shu raqamlarni bergan:

text
?teg=React: 1 ta loyiha ko'rsatilmoqda | joriy: [ 'Loyihalar', 'React' ]
HTML bosildi: /loyihalar?teg=HTML 2 ta loyiha ko'rsatilmoqda
Hammasi: 3 ta loyiha ko'rsatilmoqda
nav joriy: Loyihalar

joriy: [ 'Loyihalar', 'React' ] — sahifada ikkita aria-current="page": menyuda «Loyihalar», filtrda «React». Ikkalasi ham to'g'ri — har biri o'z nav ichida.

5.4 Biz topdik: <Suspense> siz build to'xtaydi

useSearchParams qiymati build vaqtida noma'lum: /loyihalar statik (○) sahifa, uni bitta HTML qilib tayyorlash kerak, ?teg= esa faqat so'rov kelganda ma'lum bo'ladi. Kanon TegFiltri ni <Suspense> ga o'ragan. Biz o'ramni olib tashlab build qildik:

text
Error: Route "/loyihalar": Next.js encountered URL data `useSearchParams()` in a Client Component outside of `<Suspense>`.
This blocks prerendering because the value is only available at runtime.
Ways to fix this:
  - [stream] Wrap the component in `<Suspense fallback={...}>` so the hook value streams in after prerendering
  - [block] Set `export const instant = false` to allow a blocking route

Tarjimasi: "/loyihalar marshruti: Next.js <Suspense> dan tashqaridagi Client Component'da URL ma'lumotiga (useSearchParams()) duch keldi. Qiymat faqat ish vaqtida ma'lum, shuning uchun oldindan chizish to'xtaydi." Yechimlar: [stream] — komponentni <Suspense> ga o'rang; [block] — sahifani "bloklovchi" qiling (ya'ni har so'rovda to'liq chizilsin). Cache Components o'chiq bo'lgan eski loyihalarda bu xato bo'lmaydi — u yerda Next.js sahifani jim "client'da chiziladigan" qiladi.

Kanondagi yechim — loyihalar/page.tsx da:

tsx
{/* useSearchParams prerender'da noma'lum — Suspense'gacha
    client'da chiziladi; zaxira — filtrsiz to'liq ro'yxat */}
<Suspense fallback={<ul className="loyihalar">{kartalar}</ul>}>
  <TegFiltri elementlar={…} />
</Suspense>

fallback — oddiy "Yuklanmoqda…" emas, to'liq ro'yxat. Statik HTML'da hamma uchta karta bor: JS o'chiq brauzer va qidiruv boti ham loyihalarni ko'radi. JS yuklangach TegFiltri o'z joyini egallaydi va ?teg= ni hisobga oladi. Natija: /loyihalar ○ statik qoldi.

Tekshirib ko'ring: sahifa (page.tsx) ham searchParams prop oladi. TegFiltri o'rniga sahifaning o'zida await searchParams qilsak nima o'zgaradi?

Javob

Sahifa statik bo'lmay qoladi: query faqat so'rovda ma'lum, demak sahifa har so'rovda serverda chiziladi (yoki uni ham Suspense ichiga olish kerak). Kanon filtrni brauzerga berdi — sahifa ○ qoldi, filtr esa bir zumda ishlaydi. Statik va dinamik farqini Rendering strategiyalari darsida o'lchaymiz.

6. Boshqa hook'lar

6.1 useParams

Dinamik segment qiymati — client komponentda. Fayl-asosli routing darsida ko'rgansiz: useParams<{ slug: string }>() → { slug: "vazifalar" }. Server sahifadagi params dan farqi: Promise emas, qiymat darhol.

6.2 useSelectedLayoutSegment

Layout ichidagi client komponent "mendan pastda qaysi segment ochiq?" deb so'rashi mumkin: app/loyihalar/layout.tsx ichida /loyihalar/vazifalar ochiq bo'lsa — "vazifalar". useSelectedLayoutSegments() (ko'plik) — pastdagi hamma segmentlar massivi. Bo'lim ichidagi tablar uchun qulay. Natija oynasidagi modelda bu hook'lar ildizga nisbatan ishlaydi (haqiqiy Next.js'da — layout'ga nisbatan), shuning uchun jonli misol bermaymiz.

6.3 useLinkStatus

<Link> ichidagi komponent useLinkStatus() dan { pending } oladi: o'tish kutilayotganda true. Sekin tarmoqda havola yonida kichik indikator ko'rsatish uchun. Prefetch qilingan sahifada u deyarli doim false — o'tish darhol. Natija oynasidagi modelda o'tish sinxron, u yerda doim { pending: false }.

7. redirect va permanentRedirect — serverda yo'naltirish

7.1 Ikki status

Ba'zan foydalanuvchini sahifa chizilmasdan boshqa manzilga yuborish kerak: eski URL, kirmagan foydalanuvchi. Server komponentda buning uchun next/navigation dan ikki funksiya:

tsx
import { redirect } from "next/navigation";

export default function EskiYol() {
  redirect("/loyihalar");
}

Biz ikkala variantni tajriba sahifalariga qo'yib, curl -D - bilan so'radik:

text
== /eski-yol
HTTP/1.1 307 Temporary Redirect
location: /loyihalar
== /eski-doimiy
HTTP/1.1 308 Permanent Redirect
location: /loyihalar
Funksiya Status Qachon
redirect() 307 — vaqtinchalik Kirish talab qilinadi, vaqtinchalik ko'chish
permanentRedirect() 308 — doimiy URL abadiy o'zgardi (slug yangilandi)

Google uchun farq katta: 308 da u eski manzilni indeksdan olib, "kuchini" yangisiga o'tkazadi; 307 da eski manzilni saqlaydi. Holat kodlari HTTP metodlari va status kodlari darslarida chuqur keladi.

Ikkala sahifa build jadvalida ○ — statik. Yo'naltirish ham build vaqtida tayyorlanib qo'yildi (x-nextjs-cache: HIT).

7.2 Natija oynasida va tuzoqlar

redirect() ham notFound() kabi xato tashlaydi — NEXT_REDIRECT. Modelda hodisa ichida sinab ko'ramiz (render ichida emas: modelda render paytidagi redirect() ni ushlab, manzilni almashtiradigan qism yo'q — natija oynasida u ushlanmagan xato bo'ladi; haqiqiy Next.js'da esa uni router ushlaydi):

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

export default function App() {
  const pathname = usePathname();
  function goLogin() {
    try {
      redirect("/kirish");
    } catch (error) {
      const e = error as Error & { digest?: string };
      console.log(e.message, "|", e.digest);
    }
  }
  return (
    <>
      <button onClick={goLogin}>Kirish</button>
      <p>Manzil: {pathname}</p>
    </>
  );
}

Konsolda:

text
NEXT_REDIRECT | NEXT_REDIRECT;replace;/kirish;307;

Ekranda:

text
Kirish
Manzil: /kirish

digest ichida hamma ma'lumot: tur (replace), manzil, status (307). Next.js aynan shu xatoni ushlab, javobni 307 ga aylantiradi.

Ikki qoida:

  • try/catch ichida chaqirmang (notFound() dagidek). Modelda biz ataylab ushladik — ichini ko'rish uchun. Haqiqiy kodda catch yo'naltirishni yutib yuboradi.
  • Client komponentda hodisa ichida redirect() emas, router.push() ishlating. redirect() — render paytida (server komponent, Server Action) ishlatish uchun.

8. Portfolio qadami: navigatsiya va commit

8.1 Fayllar

Maxsus fayllar darsidagi fayllarga qo'shiladi:

Fayl Nima
komponentlar/NavHavola.tsx Client: usePathname → aria-current="page"
komponentlar/SaytSarlavhasi.tsx Menyu <Link> o'rniga NavHavola
komponentlar/OrqagaTugmasi.tsx Client: router.back(), 404 sahifasida
komponentlar/TegFiltri.tsx Client: ?teg=, scroll={false}, role="status"
app/loyihalar/page.tsx TegFiltri <Suspense> ichida, zaxira — to'liq ro'yxat

8.2 Tekshiruv va commit

Build jadvali o'zgarmadi: hamma sahifa ○, ◐ /loyihalar/[slug]. Kanon o'lchovi:

Holat 04 Holat 06
JS (gzip) 135.4 KB, 6 fayl 137.4 KB, 9 fayl
axe /yoq da 3 0
hydration 0 0

+2.0 KB — uchta client komponent (NavHavola, OrqagaTugmasi, TegFiltri). Server komponentlar (sahifalar, kartalar) brauzerga JS qo'shmadi. Lint, tip, format — 0.

bash
git add -A
git commit

Commit (kanon): Navigatsiya: not-found, error, global-error, loading; joriy havola, teg filtri URL'da, Orqaga tugmasi.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
NextRouter was not mounted next/router dan import next/navigation
Method unavailable on ReadonlyURLSearchParams useSearchParams().set() Yangi URL yasab o'ting
… useSearchParams() … outside of <Suspense> Statik sahifada <Suspense> siz Komponentni <Suspense> ga o'rang
Hook'lar server komponentda xato beradi "use client" yo'q Kichik client komponentga ajrating
Menyuda «Bosh sahifa» doim joriy startsWith("/") / ni aniq tenglik bilan
Filtr bosilganda sahifa tepaga sakraydi scroll sukut true scroll={false}
redirect() ishlamaydi try/catch ichida try dan tashqarida
Prefetch so'rovlari ko'rinmaydi next dev next build + next start
Tugma bilan o'tish, yangi tabda ochib bo'lmaydi router.push havola o'rnida Bosiladigan havola — <Link>

10. Mashqlar

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

1-mashq (oson): qaysi vosita?

  • Menyudagi «Blog» havolasi —
  • Forma saqlangandan keyin /rahmat ga o'tish —
  • Joriy manzil (/blog/salom) —
  • ?sahifa=2 qiymati —
  • Eski /portfolio URL'i abadiy /loyihalar ga —
Yechim
  • Link — foydalanuvchi bosadigan havola; prefetch va <a> imkoniyatlari bilan.
  • router.push — kod natijasida o'tish.
  • usePathname — client komponentda joriy yo'l.
  • useSearchParams — faqat o'qish; .get("sahifa").
  • permanentRedirect — 308: Google kuchni yangi manzilga o'tkazadi.

2-mashq (o'rta): sahifalash

Blog ro'yxati uchun sahifalash yozing. POSTS — 5 ta post nomi, har sahifada 2 tadan. Joriy sahifa — ?sahifa= dan (Number(...), yo'q yoki noto'g'ri bo'lsa — 1). «Oldingi» va «Keyingi» — <Link>, birinchi sahifada «Oldingi», oxirgisida «Keyingi» chizilmasin. Ekranda: Sahifa 2 / 3 va shu sahifadagi postlar. Ishora: Math.ceil va slice.

Yechim
tsx
import Link from "next/link";
import { useSearchParams } from "next/navigation";

const POSTS = ["Salom", "Next.js", "Server", "Kesh", "Deploy"];
const PER_PAGE = 2;

export default function App() {
  const raw = Number(useSearchParams().get("sahifa"));
  const total = Math.ceil(POSTS.length / PER_PAGE);
  const valid = Number.isInteger(raw) && raw >= 1 && raw <= total;
  const page = valid ? raw : 1;
  const items = POSTS.slice((page - 1) * PER_PAGE, page * PER_PAGE);
  return (
    <>
      <p>
        Sahifa {page} / {total}
      </p>
      <ul>
        {items.map((p) => (
          <li key={p}>{p}</li>
        ))}
      </ul>
      <nav>
        {page > 1 && (
          <Link href={`?sahifa=${page - 1}`}>Oldingi</Link>
        )}{" "}
        {page < total && (
          <Link href={`?sahifa=${page + 1}`}>Keyingi</Link>
        )}
      </nav>
    </>
  );
}

Ekranda:

text
Sahifa 3 / 3
Deploy
Oldingi

?sahifa=abc yoki ?sahifa=99 kelsa — 1-sahifa: URL'dagi qiymatga ishonmaymiz, tekshiramiz (URL — holat saqlash joyi darsidagi qoida). href="?sahifa=2" — faqat query: yo'l (/blog) o'zgarmaydi.

3-mashq (qiyin): boshqa parametrlarni saqlab qolish

Teg filtrini kengaytiring: URL'da ?teg= dan tashqari ?tartib=yangi ham bo'lishi mumkin. Teg bosilganda tartib saqlansin. Funksiya yozing: withParam(current, key, value) — joriy URLSearchParams nusxasini oladi, key ni value ga o'rnatadi (value === null — o'chiradi) va "?…" satr qaytaradi. Boshlang'ich manzil: ih.boshla("/loyihalar?tartib=yangi") (o'quv modeli sozlamasi). Tugmalar: «React», «HTML», «Hammasi».

Yechim
tsx
import Link from "next/link";
import { ih, useSearchParams } from "next/navigation";

ih.boshla("/loyihalar?tartib=yangi");

function withParam(
  current: URLSearchParams,
  key: string,
  value: string | null,
): string {
  const next = new URLSearchParams(current.toString());
  if (value === null) next.delete(key);
  else next.set(key, value);
  const query = next.toString();
  return query ? `?${query}` : "?";
}

export default function App() {
  const params = useSearchParams();
  console.log("query:", params.toString());
  return (
    <nav>
      <Link href={withParam(params, "teg", "React")}>React</Link>{" "}
      <Link href={withParam(params, "teg", "HTML")}>HTML</Link>{" "}
      <Link href={withParam(params, "teg", null)}>Hammasi</Link>
    </nav>
  );
}

Konsolda:

text
query: tartib=yangi
query: tartib=yangi&teg=React
query: tartib=yangi&teg=HTML
query: tartib=yangi

new URLSearchParams(current.toString()) — nusxa: useSearchParams qaytargan obyektni o'zgartirib bo'lmaydi (yuqoridagi xato), nusxani esa bemalol. set mavjud kalitni almashtiradi — shuning uchun «HTML» bosilganda teg=React qolmadi. tartib=yangi har qadamda saqlandi.

4-mashq: Portfolio qadami

O'z portfolio ingizga NavHavola (menyuda), OrqagaTugmasi (404 sahifasida) va TegFiltri (/loyihalar da, <Suspense> ichida, zaxira — to'liq ro'yxat) qo'shing. Tekshiring: npm run build jadvalida /loyihalar ○ bo'lib qolsin; next start da /loyihalar?teg=React ochilsin va to'g'ri son chiqsin; DevTools'da JS o'chirib (Command Menu → "Disable JavaScript") /loyihalar ni oching — kartalar baribir ko'rinishi kerak. Keyin commit.

Yechim

Namuna — «Portfolio qadami» bo'limidagi jadval va kanon komponentlari. JS o'chiq bo'lsa filtr tugmalari ishlamaydi (ular client komponentda), lekin fallback dagi to'liq ro'yxat ko'rinadi — bu kutilgan xulq. Build jadvalida /loyihalar ○ emas, boshqa belgi bo'lsa yoki build to'xtasa — <Suspense> ni tekshiring. Commit xabari — kanondagidek yoki o'zingiznikini yozing: bu holat Maxsus fayllar darsidagi o'zgarishlarni ham o'z ichiga oladi.

11. Real ishda

  • Do'kon va katalog filtrlari — deyarli doim URL'da (?kategoriya=…&narx=…&sahifa=2). Sabab: ulashish, orqaga tugmasi, qidiruv indeksi. Uzum, Olx kabi saytlarda manzil satriga qarang — xuddi shu naqsh.
  • Prefetch narxi — katta jadval yoki cheksiz ro'yxatda yuzlab <Link> yuzlab so'rov beradi. Real loyihalarda prefetch={false} yoki hover'da prefetch (onMouseEnter + router.prefetch) ko'p uchraydi.
  • Yo'naltirishlar — sayt tuzilmasi o'zgarganda eski URL'larni 308 bilan yangisiga bog'lash SEO'ning majburiy qismi. Ko'pincha bu next.config.ts dagi redirects() da yoziladi (next.config chuqur).
  • Intervyu savoli: "<Link> va router.push farqi?", "nega useSearchParams Suspense talab qiladi?" — ikkalasiga javob endi tayyor.

Xulosa

  • <Link href> — <a> + qayta yuklashsiz o'tish + prefetch; href satr yoki { pathname, query }; replace, scroll={false}.
  • Prefetch faqat prod'da: holat 06 da bosh sahifa 10 ta RSC so'rov yubordi, bosish esa yangi so'rovsiz o'tdi.
  • usePathname — joriy yo'l (menyudagi aria-current); useRouter — push, replace, back, refresh; import next/navigation dan.
  • useSearchParams — faqat o'qish; o'zgartirish = yangi URL'ga o'tish; statik sahifada <Suspense> shart, aks holda build to'xtaydi.
  • redirect() — 307, permanentRedirect() — 308; ikkalasi xato tashlaydi, try/catch ga o'ramang.

Keyingi dars: Route groups, private papkalar va loyiha tashkili — URL'ni o'zgartirmasdan app/ ni tartibga solamiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js navigatsiya: Link, useRouter, usePathname va useSearchParams — IlmHamroh