IlmHamroh
JavaScript Full-stack/23-qism. Next.js15/43-dars23 daqiqa
Mundarija (33)

Next.js'da serverda ma'lumot olish: async komponent, fetch va Promise.all

Qisqacha: Next.js'da Server Component async funksiya bo'la oladi: ichida await fetch(...) yoki ma'lumotlar bazasi so'rovini yozasiz va natijani to'g'ridan JSX'ga qo'yasiz — useEffect ham, "yuklanmoqda" state'i ham kerak emas. So'rov serverdan ketadi: kalit va sirlar brauzerga tushmaydi, tashrif buyuruvchining IP manzili tashqi API'ga bormaydi. Bir-biriga bog'liq bo'lmagan so'rovlarni Promise.all bilan parallel boshlang — ketma-ket await qilinsa vaqtlar qo'shiladi (waterfall). Tashqi API yiqilishi mumkin: xatoni ushlab, sahifaga zaxira ko'rsating, lekin Next.js'ning o'z signallarini yutib yubormang.

Bu darsda

  • Async Server Component yozasiz va u useEffect bilan ma'lumot olishdan nimasi bilan farq qilishini tushuntirasiz.
  • Waterfall'ni kodda topasiz va uni Promise.all yoki preload naqshi bilan tuzatasiz.
  • Tashqi API javobini unknown dan xavfsiz turga aylantirasiz va xato, limit, tarmoq uzilishida zaxira qaytarasiz.
  • Kanon topgan haqiqiy tuzoqni ko'rasiz: try/catch build vaqtidagi Next.js signalini "tarmoq xatosi" deb yutib yubordi.
  • portfolio ning «Men haqimda» sahifasiga GitHub profilini serverda olib kelasiz.

Oldin bilishingiz kerak: Rendering strategiyalari, Server Components, Ketma-ket va parallel bajarish, Promise kombinatorlari, Bekor qilish: AbortController, Server state va client state.

1. Nega bu kerak?

«Men haqimda» sahifasida GitHub profilingiz ko'rinsa yaxshi bo'lardi: nechta ochiq repo, oxirgi uchta loyiha. Bu ma'lumot sizning kodingizda emas — u GitHub serverida. Uni qandaydir yo'l bilan olib kelish kerak.

React'da buni qanday qilardingiz? 19-qismda TanStack Query bilan: brauzer sahifani ochadi, JavaScript yuklanadi, keyin fetch ketadi, oxirida ma'lumot chiziladi. Ya'ni o'quvchi avval "yuklanmoqda" ni ko'radi. Undan oldin esa JavaScript to'plamining yuklanishini kutadi.

Bu yo'lning yana uchta kamchiligi bor:

Muammo Brauzerdan fetch Serverdan fetch
Birinchi ekran "Yuklanmoqda…" Tayyor matn (yoki oqim)
Kalit yoki sir Brauzerga tushadi Serverda qoladi
Tashrif buyuruvchi IP'si GitHub'ga ketadi Ketmaydi
JavaScript o'chiq Ma'lumot yo'q Ma'lumot bor

Next.js'da Server Component serverda ishlaydi (Server Components darsi). Demak u ma'lumotni o'zi olib kela oladi — xuddi oshpaz ovqatni oshxonada tayyorlab, mijozga tayyor likopchada chiqargandek. Mijoz (brauzer) "go'sht qayerdan keldi" deb bozorga bormaydi.

2. Async Server Component

2.1 Eng oddiy shakl

Server Component — oddiy funksiya, va u async bo'la oladi. Ichida await yozasiz, natijani JSX'ga qo'yasiz:

tsx
// app/haqimda/page.tsx — eng oddiy shakl (hali kanon emas)
export default async function HaqimdaSahifasi() {
  const javob = await fetch("https://api.github.com/users/octocat");
  const profil = await javob.json();
  return (
    <main>
      <h1>Men haqimda</h1>
      <p>{profil.public_repos} ta ochiq repo</p>
    </main>
  );
}

Uch narsaga qarang:

  • useState yo'q, useEffect yo'q. Komponent ma'lumot kelguncha kutadi, keyin bir marta chiziladi.
  • "Yuklanmoqda" holatini o'zingiz yozmaysiz. Kutish vaqtini qanday ko'rsatish — keyingi darsdagi Suspense'ning ishi.
  • fetch — brauzerdagi fetch ning o'zi (fetch asoslari). Node.js'da ham global fetch bor, Next.js uni serverda ishlatadi.

Client Component'da async funksiya komponent bo'la olmaydi — React xato beradi. Bu imkoniyat faqat serverda. Sababi oddiy: brauzer komponentni har bosishda qayta chizadi, server esa sahifani bir marta tayyorlab yuboradi.

Tekshirib ko'ring: nega Server Component'da useEffect ichida fetch qilish ma'nosiz?

Javob

useEffect faqat brauzerda, komponent ekranga chiqqandan keyin ishlaydi. Server Component esa brauzerda umuman ishlamaydi — uning kodi brauzerga yuborilmaydi. Shuning uchun Server Component'da hooklar (useState, useEffect) yo'q: ma'lumot to'g'ridan await bilan olinadi.

2.2 fetch faqat bitta yo'l

Server Component ichida istalgan asinxron ishni qilish mumkin:

Manba Misol Kursda
Tashqi HTTP API fetch("https://api.github.com/…") shu dars
Fayl tizimi readFile("src/blog/salom.md") Server Components — blog
Ma'lumotlar bazasi (ORM) db.post.findMany() 26–27-qismlar
O'z backend'ingiz fetch(process.env.API_URL + "/vazifalar") 25-qismdan

ORM (Object-Relational Mapping) — ma'lumotlar bazasi bilan oddiy funksiyalar orqali gaplashish kutubxonasi (Prisma, Drizzle). Hozir bilish shart emas, 26-qismda o'rganamiz. Muhimi: Server Component kodi brauzerga yuborilmagani uchun baza paroli ham, so'rov ham serverda qoladi.

Kanon portfolio da bunga ikki misol bor. Blog postlari — fayldan (Server Components darsida qo'shilgan). GitHub profili — tashqi API'dan, bugun qo'shamiz.

2.3 fetch Next 16 da keshlanmaydi

Next.js 13–14 da serverdagi fetch sukut bo'yicha keshlanardi: bir marta olingan javob keyingi so'rovlarda qayta ishlatilardi. Bu ko'p chalkashlik tug'dirdi. Next 15 dan boshlab va 16.4 da ham fetch keshlanmaydi. Next.js hujjati 16.4-bob shunday deydi:

text
fetch requests are not cached by default and will block the page
from rendering until the request is complete.

Tarjimasi: "fetch so'rovlari sukut bo'yicha keshlanmaydi va so'rov tugaguncha sahifa chizilishini to'xtatib turadi". Ya'ni ikkita tanlov bor: natijani keshlash ("use cache" — Cache Components I) yoki kutish vaqtini oqim bilan ko'rsatish (<Suspense> — Streaming). Bugun biz so'rovning o'zini to'g'ri yozishni o'rganamiz.

Yana bir foydali detal: bitta sahifa chizilishida bir xil fetch (manzil va sozlamalar bir xil) bir necha komponentda chaqirilsa, Next.js uni bir marta bajaradi — bu memoizatsiya (memoization). Shuning uchun ma'lumotni props bilan pastga uzatish o'rniga, kerakli komponentning o'zida fetch qilsa bo'ladi. Fayl va baza so'rovlari uchun bu ish React.cache() bilan qilinadi — React cache() va DAL darsida.

3. Waterfall va Promise.all

3.1 Ikki so'rov — ikki yo'l

GitHub profilini to'liq ko'rsatish uchun ikkita so'rov kerak: profil (/users/octocat) va oxirgi repolar (/users/octocat/repos). Ular bir-biriga bog'liq emas — ikkinchisi birinchisining javobini kutmaydi.

Ketma-ket yozsak:

ts
const user = await ol(`${API}/users/${login}`);
const repos = await ol(`${API}/users/${login}/repos`);

Ikkinchi so'rov birinchisi tugagandan keyin boshlanadi. Vaqtlar qo'shiladi. Bu waterfall ("sharshara") deyiladi: suv pog'onama-pog'ona tushgandek, har so'rov oldingisini kutadi (J-130 dagi sikl ichidagi await bilan bir xil muammo).

Parallel yozsak:

ts
const [user, repos] = await Promise.all([
  ol(`${API}/users/${login}`),
  ol(`${API}/users/${login}/repos`),
]);

Ikkala ol(...) chaqiruvi darhol ishga tushadi, await esa ikkalasini birga kutadi. Umumiy vaqt — eng uzun so'rov vaqti.

sequenceDiagram
  participant S as Server
  participant G as GitHub
  Note over S,G: Ketma-ket (waterfall)
  S->>G: /users/octocat
  G-->>S: profil
  S->>G: /users/octocat/repos
  G-->>S: repolar
  Note over S,G: Parallel (Promise.all)
  par
    S->>G: /users/octocat
  and
    S->>G: /users/octocat/repos
  end
  G-->>S: profil va repolar

Diagrammaning yuqori qismida ikkinchi so'rov birinchisining javobidan keyin ketadi. Pastki qismida ikkalasi bir paytda chiqadi.

3.2 O'zingiz o'lchab ko'ring

GitHub o'rniga taymer bilan soxta so'rov yasaymiz: profil 300 ms, repolar 200 ms. «Ishga tushir» ni bosing:

js
const wait = (ms, value) =>
  new Promise((resolve) => setTimeout(() => resolve(value), ms));

async function sequential() {
  const user = await wait(300, "profil");
  const repos = await wait(200, "repolar");
  return [user, repos];
}

function parallel() {
  return Promise.all([wait(300, "profil"), wait(200, "repolar")]);
}

const round = (ms) => Math.round(ms / 100) * 100;

let start = performance.now();
console.log(await sequential());
console.log("ketma-ket:", round(performance.now() - start), "ms");

start = performance.now();
console.log(await parallel());
console.log("parallel:", round(performance.now() - start), "ms");

Konsolda:

text
[ 'profil', 'repolar' ]
ketma-ket: 500 ms
[ 'profil', 'repolar' ]
parallel: 300 ms

Natija bir xil, vaqt boshqacha: 300 + 200 = 500 ms va max(300, 200) = 300 ms. Vaqtni 100 ms ga yaxlitladik — taymerlar bir necha millisekund kechikishi mumkin (Taymerlar).

Kanon portfolio da GitHub'dan ikkala so'rov ham Promise.all bilan ketadi. Kanon o'lchovida GitHub bloki issiq serverda 155–164 ms da tayyor bo'ldi — bu ikkita so'rovning yig'indisi emas, kattarog'i.

3.3 Qachon ketma-ket bo'lishi shart

Ba'zan waterfall — xato emas, zarurat. Ikkinchi so'rovga birinchisining javobi kerak bo'lsa, kutmasdan iloji yo'q. Masalan, avval foydalanuvchi profilini olasiz, keyin uning id si bilan buyurtmalarini so'raysiz.

Qoida: har await oldidan o'zingizga savol bering — "keyingi qator shu natijani ishlatadimi?". Ishlatmasa, so'rovlarni oldinroq boshlang.

Tekshirib ko'ring: sahifada uchta so'rov bor: ob-havo (200 ms), valyuta kursi (100 ms) va kurs bo'yicha narx (valyuta kursini talab qiladi, 150 ms). Eng tez yo'l bilan qancha vaqt ketadi?

Javob

Taxminan 250 ms. Ob-havo va valyuta kursi bir-biriga bog'liq emas — parallel boshlanadi. Narx so'rovi valyuta kursini kutadi: 100 + 150 = 250 ms. Ob-havo shu vaqt ichida (200 ms) tugaydi. Umumiy vaqt — eng uzun zanjir: 250 ms. Hammasi ketma-ket bo'lsa 450 ms bo'lardi.

3.4 Komponentlar orasidagi waterfall

Waterfall faqat bitta funksiya ichida emas. Ichma-ich async komponentlar ham uni yaratadi:

tsx
async function Sahifa() {
  const profil = await githubProfil("octocat"); // 1) kutadi
  return <Repolar login={profil.login} />;
}

async function Repolar({ login }: { login: string }) {
  const repos = await repolar(login); // 2) 1-dan keyin boshlanadi
  return <ul>{/* … */}</ul>;
}

Repolar komponenti Sahifa ning await i tugamaguncha yaratilmaydi ham — uning fetch i kech boshlanadi. Bu kodda ko'zga tashlanmaydi: har komponent alohida qaraganda "to'g'ri" ko'rinadi.

Ikkita davo bor:

  1. Ikkala so'rovni ota komponentda Promise.all bilan boshlash (kanon shunday qildi — githubProfil ichida).
  2. Preload naqshi (preload pattern) — keyingi bo'lim.

3.5 Preload naqshi

Ba'zan bola komponent ma'lumotini ota komponent oldinroq bilmaydi, lekin kirish qiymati (masalan slug) allaqachon bor. Shunda so'rovni await siz boshlab qo'yasiz, javobni esa bola komponentda olasiz. Next.js hujjatidagi naqsh:

tsx
// item.tsx — preload komponent yonida turadi
async function getItem(id: string) {
  const res = await fetch(`https://api.example.com/items/${id}`);
  return res.json();
}

export const preload = (id: string) => {
  void getItem(id); // boshlaymiz, lekin kutmaymiz
};

export default async function Item({ id }: { id: string }) {
  const item = await getItem(id); // o'sha so'rovning javobi
  return <div>{item.name}</div>;
}
tsx
// page.tsx
const { id } = await params;
preload(id); // Item so'rovi shu yerda boshlandi
const isAvailable = await checkIsAvailable(id); // bu kutayotganda
return isAvailable ? <Item id={id} /> : null;

void — "natija menga kerak emas" degan belgi; ESLint "kutilmagan Promise" deb ogohlantirmasligi uchun. Naqsh ishlashi uchun getItem ikkinchi chaqiruvda o'sha so'rovni qaytarishi shart. fetch bilan bu o'zi bo'ladi (bir xil fetch memoizatsiyasi). Baza so'rovi bo'lsa — React.cache() bilan o'raladi (React cache() va DAL).

4. Kanon kodi: github.ts

4.1 Butun fayl

Endi portfolio dagi haqiqiy faylni o'qiymiz. U uchta ishni qiladi: ikkita so'rovni parallel yuboradi, javobni tekshiradi va har qanday xatoda null qaytaradi.

ts
import "server-only";

// GitHub ochiq REST API — kalitsiz (N-15). Cheklov: IP boshiga
// soatiga 60 so'rov; tarmoq yo'q yoki limit tugasa — null.
// So'rov serverdan ketadi: tashrif buyuruvchining IP manzili
// GitHub'ga bormaydi.

export type GithubProfil = {
  login: string;
  ochiqRepolar: number;
  kuzatuvchilar: number;
  songgiRepolar: {
    nomi: string;
    url: string;
    yulduz: number;
    yangilangan: string;
  }[];
};

// Test va tajriba uchun boshqa manzil berish mumkin
const API = process.env.GITHUB_API_URL ?? "https://api.github.com";
const SARLAVHALAR = {
  Accept: "application/vnd.github+json",
  "User-Agent": "portfolio-v2",
};

Kanon fayldagi uzun izoh qatorlari telefonga sig'ishi uchun bo'lindi va qisqartirildi — ma'nosi o'zgarmadi.

Qator Nima qiladi
import "server-only" Bu fayl client komponentga import qilinsa — build xatosi (Client va server kompozitsiyasi)
GithubProfil Sahifaga kerakli maydonlar — GitHub javobining hammasi emas
GITHUB_API_URL Muhit o'zgaruvchisi: testda ishlamaydigan manzil berib, zaxirani ko'rish uchun
User-Agent GitHub API usiz so'rovni rad etadi

Kalitsiz degani — GitHub token yo'q. Kalitsiz API IP manzil boshiga soatiga 60 ta so'rovga ruxsat beradi (kanon /rate_limit dan "limit": 60 ni ko'rgan). Bu muhim raqam, unga Streaming va Cache Components I darslarida qaytamiz.

4.2 Bitta so'rov: ol()

ts
async function ol(url: string): Promise<unknown> {
  const javob = await fetch(url, {
    headers: SARLAVHALAR,
    signal: AbortSignal.timeout(3000),
  });
  if (!javob.ok)
    throw new Error(
      `GitHub ${javob.status} (${javob.headers.get("x-ratelimit-remaining") ?? "?"} so'rov qoldi)`,
    );
  return javob.json();
}

Uchta muhim detal:

  • AbortSignal.timeout(3000) — GitHub 3 soniyada javob bermasa, so'rov bekor qilinadi (AbortController). Busiz sekin API sahifani cheksiz ushlab turishi mumkin.
  • javob.ok — fetch 403 yoki 500 da xato tashlamaydi, faqat tarmoq uzilganda tashlaydi (fetch asoslari). Shuning uchun statusni o'zimiz tekshiramiz.
  • x-ratelimit-remaining — GitHub'ning sarlavhasi: "yana nechta so'rov qoldi". Xato matniga qo'shilgan — logda limit tugaganini darhol ko'rasiz.
  • Qaytgan tur — unknown. Tashqi API nima yuborishini kodimiz kafolatlay olmaydi (unknown).

throw new Error(...) qatori kanonda 70 belgidan uzun — kanon kodini aynan ko'chirdik.

4.3 Parallel va tekshiruv

ts
function sonmi(x: unknown): number {
  return typeof x === "number" ? x : 0;
}

export async function githubProfil(
  login: string,
): Promise<GithubProfil | null> {
  try {
    // Ikkala so'rov parallel — ketma-ket await qilinsa
    // "waterfall" (vaqt qo'shiladi)
    const [user, repos] = await Promise.all([
      ol(`${API}/users/${encodeURIComponent(login)}`),
      ol(
        `${API}/users/${encodeURIComponent(login)}/repos?sort=pushed&per_page=3`,
      ),
    ]);
    if (typeof user !== "object" || user === null || !Array.isArray(repos))
      return null;
    const u = user as Record<string, unknown>;
    return {
      login,
      ochiqRepolar: sonmi(u.public_repos),
      kuzatuvchilar: sonmi(u.followers),
      songgiRepolar: repos.flatMap((r: unknown) => {
        if (typeof r !== "object" || r === null) return [];
        const x = r as Record<string, unknown>;
        if (typeof x.name !== "string" || typeof x.html_url !== "string")
          return [];
        return [
          {
            nomi: x.name,
            url: x.html_url,
            yulduz: sonmi(x.stargazers_count),
            yangilangan: String(x.pushed_at ?? ""),
          },
        ];
      }),
    };
  } catch (xato) {
    console.warn(
      "GitHub profili olinmadi:",
      xato instanceof Error ? xato.message : xato,
    );
    return null;
  }
}

Qatorma-qator asosiylari:

Joy Ma'nosi
encodeURIComponent(login) Login ichidagi / yoki ? manzilni buzmaydi
Promise.all([...]) Ikkala so'rov bir paytda; bittasi yiqilsa — ikkalasi ham catch ga
typeof user !== "object" Narrowing: unknown ni obyektga toraytirish
sonmi(...) Son bo'lmasa 0 — sahifada undefined ta repo chiqmaydi
flatMap Noto'g'ri repo yozuvi tashlanadi ([]), to'g'risi qoladi
catch → null Har qanday xato — zaxira UI uchun signal

Nega Zod emas? 15-qismda qo'lda tekshirishni o'rgangan edingiz, va bu yerda faqat beshta maydon bor. Kanon serverga qo'shimcha paket olib kirmadi. Zod forma uchun Server Actions II darsida keladi.

5. Xatolarni boshqarish

5.1 Ikki strategiya

Tashqi API yiqilganda sahifa nima qilishi kerak? Ikki yo'l bor:

Strategiya Qanday Qachon
Xatoni tashlash throw → eng yaqin error.tsx Sahifa ma'lumotsiz ma'nosiz (masalan blog posti)
Zaxira qaytarish catch → null → "olib bo'lmadi" matni Ma'lumot qo'shimcha, sahifaning qolgani muhimroq

error.tsx ni Maxsus fayllar darsida ko'rgansiz. GitHub bloki esa «Men haqimda» sahifasining kichik qismi. GitHub ishlamasa ham, ism, bilimlar va o'qish rejasi ko'rinishi kerak. Shuning uchun kanon ikkinchi yo'lni tanladi: null va zaxira matn.

5.2 Natija oynasida: uch holat

fetch ni parametr qilib beramiz — shunda haqiqiy GitHub'siz uch vaziyatni sinash mumkin: muvaffaqiyat, limit tugashi (403) va tarmoq uzilishi. Bu kanon funksiyasining kichraytirilgan nusxasi:

ts
type Fetcher = (url: string) => Promise<Response>;
type Profile = { login: string; repos: number };

function toNumber(x: unknown): number {
  return typeof x === "number" ? x : 0;
}

async function githubProfile(
  login: string,
  fetcher: Fetcher,
): Promise<Profile | null> {
  try {
    const res = await fetcher(`/users/${login}`);
    if (!res.ok) throw new Error(`GitHub ${res.status}`);
    const data: unknown = await res.json();
    if (typeof data !== "object" || data === null) return null;
    const u = data as Record<string, unknown>;
    return { login, repos: toNumber(u.public_repos) };
  } catch (error) {
    console.log(
      "olinmadi:",
      error instanceof Error ? error.message : error,
    );
    return null;
  }
}

const works: Fetcher = async () => Response.json({ public_repos: 8 });
const limited: Fetcher = async () =>
  new Response("rate limit", { status: 403 });
const offline: Fetcher = async () => {
  throw new TypeError("fetch failed");
};

async function main() {
  console.log(await githubProfile("octocat", works));
  console.log(await githubProfile("octocat", limited));
  console.log(await githubProfile("octocat", offline));
}
main();

Konsolda:

text
{ login: 'octocat', repos: 8 }
olinmadi: GitHub 403
null
olinmadi: fetch failed
null

Response.json(...) va new Response(...) — Web standart klasslar (Request va Response), Node'da ham, brauzerda ham bor. Kanondagi console.warn o'rniga bu yerda console.log — natija bitta oqimda ko'rinsin. fetch failed — Node'ning fetch i tarmoq uzilganda aynan shu matnli TypeError tashlaydi. Kanon ham GitHub'ni o'chirib build qilganda logda shu satrni ko'rgan:

text
GitHub profili olinmadi: fetch failed

5.3 Biz topdik: try/catch Next.js signalini yutdi

Endi darsning eng muhim saboqi. Kanon GithubBlok komponentini birinchi marta yozganda next build quyidagini ikki marta chiqardi (xom chiqish, uzun qator bo'linmagan):

text
GitHub profili olinmadi: During prerendering, fetch() rejects when the prerender is complete. Typically these errors are handled by React but if you move fetch() to a different context by using `setTimeout`, `after`, or similar functions you may observe this error and you should handle it in that context. This occurred at route "/haqimda".

Tarjimasi: "Prerender paytida, prerender tugaganda fetch() rad etiladi. Odatda bu xatolarni React o'zi ushlaydi, lekin fetch() ni boshqa kontekstga ko'chirsangiz… bu xatoni ko'rishingiz mumkin. Bu /haqimda marshrutida bo'ldi."

Nima bo'ldi? Build paytida Next.js sahifani oldindan chizadi (prerender — Rendering strategiyalari). GitHub bloki Suspense ichida edi, va Next.js uni "dinamik teshik" deb tashlab ketdi. Lekin bungacha blok fetch ni boshlab ulgurgan edi. Prerender tugaganda Next.js bu fetch ni ataylab rad etdi — bu xato emas, "bu qism keyin, so'rov paytida chiziladi" degan ichki signal.

Bizning catch esa farqni bilmaydi: u signalni ushladi, "GitHub profili olinmadi" deb yozdi va null qaytardi. Ikki marta — chunki Promise.all ichida ikkita fetch bor edi. Sahifa baribir to'g'ri ishladi (◐ /haqimda), lekin build logi yolg'on gapirdi: GitHub ishlab turgan edi.

flowchart TB
  B["next build: /haqimda prerender"] --> F["GithubBlok fetch boshladi"]
  F --> E["prerender tugadi: fetch rad etildi"]
  E --> C["bizning catch: 'olinmadi'"]
  C --> L["logda yolg'on xato, 2 marta"]

Kanon tuzatishi — blok boshida bitta qator:

tsx
import { connection } from "next/server";

export async function GithubBlok() {
  // connection() — "bu blok har so'rovda": prerender shu yerda
  // to'xtaydi, build vaqtida fetch boshlanmaydi
  await connection();
  const profil = await githubProfil(GITHUB_LOGIN);
  // …
}

connection() — "bu yerdan keyingi kod faqat haqiqiy so'rov kelganda ishlasin" degan belgi. Next.js hujjati: await connection() // prerendering stops here. Endi build vaqtida fetch boshlanmaydi, rad etish ham, yolg'on log ham yo'q. Build chiqishida route o'zgarmadi — ◐ /haqimda (statik qobiq va dinamik teshik).

Ikkinchi yo'l ham bor — Next.js'ning unstable_rethrow(xato) funksiyasi catch boshida Next.js'ning o'z xatolarini (notFound(), redirect() va boshqalar) qayta tashlaydi. Nomidagi unstable_ — API hali o'zgarishi mumkin. Kanon uni ishlatmadi: connection() muammoning ildizini — build vaqtidagi keraksiz so'rovni — yo'q qiladi.

Umumiy saboq: keng catch — xavfli. Framework ichida ishlaganda catch faqat sizning xatolaringizni emas, framework signallarini ham ushlashi mumkin. Belgisi — logdagi g'alati xabar va "lekin hammasi ishlayapti". Shunday xabarni ko'rsangiz, uni o'qing, tashlab ketmang.

Tekshirib ko'ring: connection() qo'shilgandan keyin GitHub profili qachon olinadi — build paytidami yoki sahifa ochilgandami?

Javob

Sahifa ochilganda — har so'rovda. connection() prerender'ni shu joyda to'xtatadi, shuning uchun build vaqtida GitHub'ga so'rov ketmaydi. Bu esa yangi muammo tug'diradi: har ko'rish = GitHub'ga 2 ta so'rov, kalitsiz limit soatiga 60 ta. Uni Cache Components I darsida "use cache" bilan yechamiz.

6. O'z backend'ingizni chaqirish

Keyingi qismlarda vazifalar uchun alohida backend yozasiz (NestJS — 25+ qismlar). Next.js sahifasi uni ham xuddi GitHub kabi serverdan chaqiradi. Sxema (hozir ishga tushirilmaydi — backend hali yo'q):

ts
// malumot/vazifalar.ts — sxema, 25-qismdan keyin
import "server-only";

export async function vazifalar(): Promise<unknown> {
  const javob = await fetch(`${process.env.API_URL}/vazifalar`, {
    headers: { Authorization: `Bearer ${process.env.API_KALIT}` },
    signal: AbortSignal.timeout(3000),
  });
  if (!javob.ok) throw new Error(`API ${javob.status}`);
  return javob.json();
}

Bu yerda ikki narsa serverda qolishi muhim:

  • API_KALIT — sir. U NEXT_PUBLIC_ bilan boshlanmaydi, demak brauzer to'plamiga tushmaydi (Env o'zgaruvchilar va sirlar darsida batafsil).
  • API_URL — ichki manzil bo'lishi mumkin (masalan http://api:4000). Brauzer uni umuman ko'rmaydi.

Bunday tuzilma BFF (Backend for Frontend — frontend uchun backend) deyiladi: Next.js server foydalanuvchi bilan haqiqiy backend o'rtasida turadi. Qachon BFF, qachon to'g'ridan backend — Route Handler, Server Action yoki alohida backend? darsida.

7. Portfolio qadami: GitHub profili serverda

Bu dars, Rendering strategiyalari va Streaming darslari portfolio ning bitta holatini quradi. Bugun — ma'lumot qatlami. Suspense va commit — keyingi darsda. Hali commit qilmang — npm run build ham <Suspense> qo'shilguncha to'xtaydi (4-mashqda).

7.1 Fayllar

Fayl Nima
src/malumot/sozlama.ts GITHUB_LOGIN = "octocat" — o'zingiznikiga almashtirasiz
src/malumot/github.ts githubProfil(): kalitsiz, parallel, unknown tekshiruvi, xatoda null
src/app/(sayt)/haqimda/_komponentlar/GithubBlok.tsx async Server Component + zaxira matn

octocat — GitHub'ning rasmiy namuna hisobi. O'z loginingizni yozing — sahifada o'z repolaringiz chiqadi.

GithubBlok ning asosiy qismi:

tsx
export async function GithubBlok() {
  await connection();
  const profil = await githubProfil(GITHUB_LOGIN);
  if (!profil) {
    return (
      <p>
        GitHub ma'lumotini hozir olib bo'lmadi.{" "}
        <a href={`https://github.com/${GITHUB_LOGIN}`}>
          Profilni GitHub'da oching
        </a>
        .
      </p>
    );
  }
  return (
    <p>
      <a href={`https://github.com/${profil.login}`}>@{profil.login}</a>:{" "}
      {profil.ochiqRepolar} ta ochiq repo, {profil.kuzatuvchilar} ta
      kuzatuvchi.
    </p>
    // + oxirgi uchta repo ro'yxati
  );
}

Zaxira matnda ham foydali havola bor: GitHub ishlamasa ham, o'quvchi profilga o'zi o'ta oladi. Bo'sh joy yoki "Xato!" degan quruq matndan ko'ra yaxshiroq.

7.2 Zaxirani sinash

GitHub'ni "o'chirish" uchun internetni uzish shart emas. GITHUB_API_URL ga ishlamaydigan manzil bering — kanon shunday qildi:

bash
GITHUB_API_URL=http://127.0.0.1:9 npm run dev

127.0.0.1:9 — kompyuteringizdagi 9-port, unda hech kim tinglamaydi. fetch darhol fetch failed beradi, sahifada zaxira matn chiqadi. Windows PowerShell'da bu sintaksis ishlamaydi — u yerda $env:GITHUB_API_URL="http://127.0.0.1:9" qatorini alohida yozasiz, keyin npm run dev.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Sahifa sekin, so'rovlar logda birin-ketin Bog'liq bo'lmagan await lar ketma-ket Promise.all
Bola komponent so'rovi kech boshlanadi Ota await dan keyin yaratiladi So'rovni otada boshlang yoki preload
403/500 da sahifa "muvaffaqiyatli" javob.ok tekshirilmagan if (!javob.ok) throw …
Cannot read properties of undefined Tashqi javob kutilgan shaklda emas unknown + tekshiruv
Sekin API sahifani uzoq ushlaydi Vaqt chegarasi yo'q AbortSignal.timeout(ms)
Build logida fetch() rejects when the prerender is complete catch Next.js signalini yutgan await connection() yoki unstable_rethrow
Bitta API yiqilsa hammasi yo'qoladi Promise.all bitta rad etishda yiqiladi Kerak bo'lsa Promise.allSettled
Kalit brauzerda ko'rindi NEXT_PUBLIC_ prefiksi Sirni prefikssiz, faqat serverda

9. Mashqlar

Mashqlar kurs/mashqlar/23/15-malumot-olish/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): waterfall'ni toping

Quyidagi kodda qaysi await lar parallel bo'la oladi? Har qatorni "kutishi shart" yoki "parallel bo'la oladi" deb belgilang.

ts
const user = await getUser(id);          // A
const settings = await getSettings(id);  // B
const orders = await getOrders(user.id); // C
const weather = await getWeather(city);  // D
  • B hech qaysi natijani ishlatmaydi: B —
  • C user.id ni ishlatadi: C — A ni
  • D hech qaysi natijani ishlatmaydi: D —
Yechim

A, B va D bir-biriga bog'liq emas — uchalasi Promise.all bilan birga boshlanadi. C ga user.id kerak, demak u A dan keyin. Eng tez variant:

ts
const [user, settings, weather] = await Promise.all([
  getUser(id),
  getSettings(id),
  getWeather(city),
]);
const orders = await getOrders(user.id);

Yana tezroq yo'l ham bor: C ni A tugashi bilan boshlash, B va D ni kutmasdan — getUser(id).then((u) => getOrders(u.id)) ni ham Promise.all ga qo'shish. Bu holda umumiy vaqt = max(A + C, B, D).

2-mashq (o'rta): bitta API yiqilsa ham

«Men haqimda» sahifasi profil va repolarni oladi. Repolar API'si yiqilsa ham, profil ko'rinishi kerak (repolar o'rnida bo'sh ro'yxat). Promise.allSettled (Promise kombinatorlari) bilan loadAbout() funksiyasini yozing. Soxta so'rovlar: profile() 100 ms dan keyin { login: "octocat", repos: 8 }, repos() 50 ms dan keyin Error("GitHub 403") bilan rad etiladi. Natijada { profile, repos: [] } va konsolda bitta ogohlantirish (console.log) chiqsin.

Yechim
js
const wait = (ms) => new Promise((r) => setTimeout(r, ms));

async function profile() {
  await wait(100);
  return { login: "octocat", repos: 8 };
}

async function repos() {
  await wait(50);
  throw new Error("GitHub 403");
}

async function loadAbout() {
  const [p, r] = await Promise.allSettled([profile(), repos()]);
  if (r.status === "rejected") {
    console.log("repolar olinmadi:", r.reason.message);
  }
  return {
    profile: p.status === "fulfilled" ? p.value : null,
    repos: r.status === "fulfilled" ? r.value : [],
  };
}

console.log(await loadAbout());

Konsolda:

text
repolar olinmadi: GitHub 403
{ profile: { login: 'octocat', repos: 8 }, repos: [] }

Promise.all bo'lganda repolar xatosi profilni ham "olib ketardi" — butun natija catch ga tushardi. allSettled har so'rov natijasini alohida beradi. Kanon esa ataylab Promise.all ni tanladi: GitHub limiti tugasa, ikkala so'rov ham yiqiladi — alohida ishlov foyda bermaydi. Bu ham qaror: avval qanday xato bo'lishi mumkinligini o'ylang.

3-mashq (qiyin): vaqt chegarasi

getWithTimeout(fetcher, ms) funksiyasini yozing: fetcher(signal) ga AbortSignal.timeout(ms) beradi va ms dan uzoq ketsa null qaytaradi, konsolga vaqt tugadi yozadi. Soxta slowApi(signal) 500 ms dan keyin "javob" qaytaradi, lekin signal bekor qilinsa darhol signal.reason bilan rad etiladi. getWithTimeout(slowApi, 1000) va getWithTimeout(slowApi, 100) ni sinang. Ishora: vaqt tugaganda signal.reason — TimeoutError nomli DOMException (AbortController).

Yechim
js
function slowApi(signal) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => resolve("javob"), 500);
    signal.addEventListener("abort", () => {
      clearTimeout(timer);
      reject(signal.reason);
    });
  });
}

async function getWithTimeout(fetcher, ms) {
  try {
    return await fetcher(AbortSignal.timeout(ms));
  } catch (error) {
    if (error.name === "TimeoutError") {
      console.log("vaqt tugadi");
      return null;
    }
    throw error; // boshqa xatolarni yutmaymiz
  }
}

console.log(await getWithTimeout(slowApi, 1000));
console.log(await getWithTimeout(slowApi, 100));

Konsolda:

text
javob
vaqt tugadi
null

catch faqat kutilgan xatoni (TimeoutError) ushlaydi, qolganini qayta tashlaydi — «Biz topdik» bo'limidagi saboq. Haqiqiy fetch ham signal ni xuddi shunday hurmat qiladi: vaqt tugasa TimeoutError bilan rad etiladi.

4-mashq: Portfolio qadami

O'z portfolio ingizga src/malumot/sozlama.ts (o'z GitHub loginingiz), src/malumot/github.ts va GithubBlok.tsx ni qo'shing. GithubBlok ni «Men haqimda» sahifasiga «GitHub» sarlavhasi ostida qo'ying. next dev da tekshiring: profil chiqdimi? Keyin GITHUB_API_URL=http://127.0.0.1:9 bilan qayta ishga tushiring — zaxira matn chiqishi kerak. Commit — keyingi darsda.

Yechim

Namuna — «Kanon kodi: github.ts» va «Portfolio qadami» bo'limlaridagi kod. Tekshiruv ro'yxati:

  • terminal logida GitHub profili olinmadi faqat GITHUB_API_URL bilan chiqadi;
  • DevTools → Network: brauzerdan api.github.com ga birorta ham so'rov ketmaydi — hammasi serverda.

Hozircha sahifa GitHub javobini kutib turadi (Suspense hali yo'q). Shuning uchun next dev terminalida qizil xabar chiqadi — sahifa esa ochilaveradi (biz ishga tushirdik, holat 16 nusxasi Suspense siz):

text
Error: Route "/haqimda": Next.js encountered uncached data during prerendering.
`fetch(...)` or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

Bu Rendering strategiyalari darsidagi [stream]/[cache]/[block] xatosi. npm run build esa hozir shu xato bilan to'xtaydi — bu kutilgan holat. Keyingi darsda GithubBlok ni <Suspense> ga o'raysiz: build toza o'tadi va fetch() rejects when the prerender is complete xabari ham chiqmaydi.

10. Real ishda

  • Har Next.js loyihasida ma'lumotning katta qismi Server Component'da olinadi: e-commerce mahsulot sahifasi, blog, dashboard. useEffect + fetch — App Router'da kam uchraydigan naqsh.
  • Waterfall — Next.js loyihalaridagi sekinlikning eng ko'p sababi. Senior dasturchi code review'da birinchi bo'lib ketma-ket await larni qidiradi.
  • Tashqi API limitlari (GitHub, ob-havo, valyuta kursi) — real muammo. Kalit serverda qoladi, natija keshlanadi, xatoda zaxira ko'rsatiladi.
  • BFF — katta kompaniyalarda keng tarqalgan: Next.js server bir nechta ichki xizmatdan ma'lumot yig'adi va brauzerga bitta sahifa beradi.
  • Intervyuda: "Server Component'da ma'lumot olishning afzalligi nima?", "Waterfall nima va qanday oldini olasiz?" — tez-tez beriladi.

Xulosa

  • Server Component async bo'la oladi: await fetch(...) va natija to'g'ridan JSX'da; hooklar kerak emas.
  • So'rov serverdan ketadi: sir va kalit serverda qoladi, tashrif buyuruvchi IP'si tashqi API'ga bormaydi.
  • Next 15+ da fetch sukut bo'yicha keshlanmaydi; bir render ichidagi bir xil fetch lar esa birlashtiriladi.
  • Bog'liq bo'lmagan so'rovlar — Promise.all; komponentlar orasidagi waterfall — otada boshlash yoki preload naqshi.
  • Tashqi javob — unknown: ok, shakl va vaqt chegarasini tekshiring; xatoda zaxira qaytaring.
  • Keng catch Next.js signalini yutishi mumkin — kanon connection() bilan tuzatdi.

Keyingi dars: Streaming va Suspense — GitHub bloki sekin bo'lsa ham, sahifaning qolgani darhol chiqadi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js'da serverda ma'lumot olish: async komponent, fetch va Promise.all — IlmHamroh