13.5-bob: Data fetching va Server Actions
13-QISM — Next.js · 5-mavzu
1. Kirish va motivatsiya
13.3 va 13.4-boblarda Server Components va rendering'ni o'rgandik — endi ma'lumot bilan ishlashning ikki yo'nalishini chuqur ko'ramiz: ma'lumotni olish (read — fetch/DB) va ma'lumotni o'zgartirish (write/mutation — yaratish, yangilash, o'chirish). SPA'da (11-QISM) bu ikki narsa murakkab edi: olish uchun useEffect+useState+loading 11.5-bob yoki TanStack Query 12.4-bob, o'zgartirish uchun esa alohida backend API (Express/NestJS — POST/PUT/DELETE endpoint) yozish kerak edi. Next.js bularni inqilobiy soddalashtiradi: ma'lumot olish Server Component'da await fetch/DB (13.3 — useEffect yo'q), va eng muhimi — Server Actions bilan ma'lumotni o'zgartirish uchun alohida API kerak emas — server funksiyasini to'g'ridan komponentdan chaqirasiz.
Server Actions — bu Next.js'ning eng yangi va eng kuchli imkoniyatlaridan biri. G'oya: server'da ishlaydigan funksiya ("use server" direktivasi bilan), uni to'g'ridan komponentdan (forma yoki tugma orqali) chaqirish mumkin — Next.js o'rtada avtomatik so'rovni (network) boshqaradi. Ya'ni: foydalanuvchi formani to'ldiradi Server Action chaqiriladi (server'da ishlaydi — DB'ga yozadi) sahifa yangilanadi — va siz hech qanday API endpoint yozmadingiz (fetch("/api/...") yo'q, route.ts yo'q). Bu — frontend va backend o'rtasidagi chegarani "yo'qotadi" (full-stack bir joyda). Bundan tashqari, Server Actions progressive enhancement beradi (JavaScript o'chirilgan bo'lsa ham forma ishlaydi — HTML forma kabi), Zod validatsiya (10-QISM), va useOptimistic (darrov UI) bilan birlashadi.
Bu bob: Server Component'da data fetching (fetch/DB — chuqur), parallel vs sequential (waterfall muammosi), request memoization (dedup), Server Actions ("use server" — nima, nega), forma bilan Server Actions (action prop, progressive enhancement), useActionState/useFormStatus (pending, xato), Client Component'dan Server Action (hodisa), revalidation (mutation'dan keyin — 13.4), redirect (action'dan keyin), validatsiya (Zod — Server Action'da), xato boshqaruvi, useOptimistic (darrov UI), va Server Actions xavfsizligi. Har mavzu har bir kod misolida maqsad + izoh + "nima qiladi" bilan to'liq ochib beriladi.
O'xshatish: Server Actions — bu restoran ofitsianti (mehmon bilan oshxona o'rtasidagi ko'prik). Eski usulda (SPA + alohida backend), mehmon (brauzer) oshxona (backend)ga buyurtma berish uchun o'zi telefon qilib, raqamni terib, gaplashib (API endpoint yozish,
fetch, JSON), keyin javobni kutishi kerak edi — ko'p qadam, ko'p kod. Server Action — bu ofitsiant: mehmon faqat "menga shu taom" deydi (formani to'ldiradi, tugmani bosadi), ofitsiant (Next.js) o'zi oshxonaga boradi (server'da funksiya ishlaydi), buyurtmani beradi (DB'ga yozadi), va taomni olib keladi (natija). Mehmon telefon raqamni (API endpoint) bilishi shart emas — faqat xohlishini aytadi (Server Action'ni chaqiradi), qolganini ofitsiant qiladi. Bu — frontend (mehmon) va backend (oshxona) o'rtasidagi ko'prik avtomatlashgan (ofitsiant — Next.js — o'rtada).
Nega muhim?
- Backend'siz mutation — Server Actions bilan alohida API (Express/route.ts) kerak emas (full-stack bir joyda).
- Soddaroq data fetching — Server Component'da
await(useEffect/loading yo'q — 11.5'dagi og'riq yo'q). - Progressive enhancement — forma JavaScript'siz ham ishlaydi (HTML forma — ishonchli).
- Xavfsiz — Server Action server'da (validatsiya, DB, secret — brauzerga chiqmaydi — 13.3).
2. Nazariya — chuqur tushuntirish
2.1. Server Component'da data fetching (read)
MA'LUMOT OLISH — Server Component'da TO'G'RIDAN (13.3 davomi — chuqur):
// app/products/page.tsx (Server Component)
export default async function ProductsPage() {
// 1-usul: fetch (tashqi API yoki o'z API):
const res = await fetch("https://api.example.com/products");
const products = await res.json();
// 2-usul: DB'dan TO'G'RIDAN (ORM — Prisma — 6.12):
// const products = await db.product.findMany();
return <ProductList products={products} />;
}
AFZALLIKLAR (SPA'dagi useEffect'dan farq — 11.5):
async/await (loading/error state YO'Q — server kutadi)
DB'ga to'g'ridan (API qatlami shart emas — server'da)
Secret xavfsiz (server'da — brauzerga yo'q — 13.3: 2.9)
SEO (ma'lumot HTML ichida — Google ko'radi)
Server Component'da ma'lumot — async/await (fetch yoki DB to'g'ridan)
useState/useEffect/loading KERAK EMAS (server ma'lumotni kutib, HTML beradi)Server Component'da data fetching — ma'lumot olishning Next.js usuli (13.3 davomi — chuqur). Server Component'da ma'lumot to'g'ridan
async/awaitbilan olinadi: (1)fetch(tashqi API yoki o'z API'dan); yoki (2) DB'dan to'g'ridan (Prisma/Drizzle ORM — 6.12 — server'da). Misol:export default async function ProductsPage() { const res = await fetch(...); const products = await res.json(); ... }— komponent server'da ishlaydi, ma'lumotni kutadi, keyin HTML render qiladi. Afzalliklar (SPA'dagiuseEffect'dan farq — 11.5): (1)async/await— loading/error state kerak emas (server ma'lumotni kutadi, keyin tayyor HTML beradi — 11.5'dagiisLoading/useState/race muammolari yo'q); (2) DB'ga to'g'ridan (alohida API qatlami shart emas — server'da xavfsiz); (3) secret xavfsiz (server'da — brauzerga chiqmaydi — 13.3: 2.9); (4) SEO (ma'lumot HTML ichida render bo'ladi — Google ko'radi — 13.1: 2.1). Ikki nuqta: (1) Server Component'da ma'lumot —async/await(fetch yoki DB to'g'ridan); (2)useState/useEffect/loading kerak emas (server ma'lumotni kutib, tayyor HTML beradi). Bu — Next.js'ning eng katta "wow" momenti (11.5'dagi butun fetch og'rig'i yo'qoladi — kod 5 baravar qisqa, tez, xavfsiz). Ma'lumot olish — Server Component'ning tabiiy ishi.
2.2. Parallel vs sequential (waterfall muammosi)
WATERFALL MUAMMOSI — ketma-ket fetch (biri ikkinchisini kutadi — SEKIN):
SEQUENTIAL (waterfall — biri tugab, keyin ikkinchisi — sekin):
const user = await fetch("/api/user"); // 1s kutadi
const posts = await fetch("/api/posts"); // user tugagach — yana 1s
// JAMI 2s (ketma-ket — biri ikkinchisini kutdi)
PARALLEL (birga — ikkalasi bir vaqtda — TEZ):
const [user, posts] = await Promise.all([ // ikkalasi BIRGA boshlanadi
fetch("/api/user"),
fetch("/api/posts"),
]);
// JAMI 1s (parallel — ikkalasi bir vaqtda — tez)
QACHON SEQUENTIAL KERAK (biri ikkinchisiga bog'liq):
const user = await fetch("/api/user"); // avval user
const posts = await fetch(`/api/posts?userId=${user.id}`); // user.id kerak (bog'liq)
bu yerda ketma-ket MAJBUR (posts user'ga bog'liq)
Bog'liq bo'lmagan fetch'lar Promise.all (parallel — tez); waterfall'dan saqlanish
Faqat biri ikkinchisiga bog'liq bo'lsa — sequential (majburan)Parallel vs sequential (waterfall muammosi) — data fetching performance'ining asosiy masalasi. Waterfall (sharshara) muammosi — ketma-ket
await(biri ikkinchisini kutadi):const user = await fetch(...)(1 soniya), keyinconst posts = await fetch(...)(yana 1 soniya) — jami 2 soniya (sequential — biri tugab, keyin ikkinchisi — sekin), garchi ular bir-biriga bog'liq emas. Yechim — parallel (Promise.all— 3.10):const [user, posts] = await Promise.all([fetch(...), fetch(...)])— ikkalasi bir vaqtda boshlanadi, jami 1 soniya (parallel — tez). Ya'ni bog'liq bo'lmagan fetch'larni birga ishga tushirish (biri ikkinchisini kutmasin). Qachon sequential majbur (biri ikkinchisiga bog'liq):const user = await fetch("/api/user"), keyinconst posts = await fetch(\/api/posts?userId=${user.id}`)— bu yerdapostsuser.idga bog'liq, shuning uchun ketma-ket **majbur** (avval user, keyin posts — boshqa iloj yo'q). Ikki nuqta: (1) bog'liq bo'lmagan fetch'larPromise.all` (parallel — tez — waterfall'dan saqlanish); (2) faqat biri ikkinchisiga bog'liq bo'lsa — sequential (majburan). Bu — Next.js data fetching'ning eng keng performance xatosi (waterfall — bog'liq bo'lmagan so'rovlarni ketma-ket qilib, sahifani sekinlashtirish). To'g'ri yondashuv: parallel default, sequential faqat bog'liqlik bo'lganda.
2.3. Request memoization (dedup)
REQUEST MEMOIZATION — bir XIL fetch bir render'da BIR MARTA (Next.js avtomatik dedup):
MUAMMO: bir render'da bir xil ma'lumot bir necha komponentda kerak:
// Layout'da user kerak, Page'da ham user kerak, Navbar'da ham:
async function getUser() { return fetch("/api/user").then(r => r.json()); }
// Layout: await getUser() ┐
// Page: await getUser() ├─ bir XIL fetch 3 marta? YO'Q — Next.js dedup
// Navbar: await getUser() ┘
NEXT.JS AVTOMATIK: bir render'da bir XIL fetch (URL+opsiya) BIR MARTA so'rov:
3 marta getUser() chaqirilsa ham — TARMOQqa 1 marta boradi (qolgani keshdan)
props "drilling" kerak emas (har komponent o'zi chaqiraveradi — dedup qiladi)
┌────────────────────────────────────────────────────────────┐
│ Request memoization: bir render'da bir xil fetch 1 so'rov │
│ (avtomatik — komponentlar mustaqil chaqiraveradi — dedup) │
└────────────────────────────────────────────────────────────┘
Bir render'da bir xil fetch (URL+opsiya) Next.js BIR marta so'rov (avtomatik dedup)
Props drilling kerak emas — har komponent o'zi getUser() chaqirsa ham (dedup)Request memoization (dedup) — Next.js'ning nozik, lekin juda foydali optimizatsiyasi. Muammo: bir render'da bir xil ma'lumot bir necha joyda kerak bo'lishi mumkin — masalan
Layoutda foydalanuvchi kerak,Pageda ham,Navbarda ham (har birigetUser()chaqiradi). Bir xil so'rov 3 marta tarmoqqa borsa — isrof. Next.js avtomatik dedup (request memoization): bir render davomida bir xilfetch(bir xil URL + opsiya) — bir marta so'rov qilinadi (tarmoqqa bir marta boradi), qolgan chaqiruvlar keshdan (xotirada) javob oladi. DemakgetUser()ni 3 ta komponent chaqirsa ham — tarmoqqa faqat 1 marta boradi (Next.js qolganini keshdan beradi). Bu nima beradi: props drilling kerak emas (12.1: 2.1 — props'ni yuqoridan pastga uzatish) — har komponent o'ziga kerakli ma'lumotni mustaqil chaqiraveradi (getUser()), Next.js takrorni yo'qotadi. Ya'niuserni Layout'dan Page'ga, Navbar'ga props orqali uzatish shart emas — har birigetUser()chaqiraversin (dedup tufayli faqat 1 so'rov). Ikki nuqta: (1) bir render'da bir xil fetch (URL+opsiya) Next.js bir marta so'rov (avtomatik dedup — Reactfetchni o'rab keshlaydi); (2) props drilling kerak emas — har komponent o'zigetUser()chaqirsa ham (dedup). Bu — Server Components'ning toza arxitekturasining (har komponent o'z ma'lumotini oladi, dedup takrorni oldini oladi) asosi. Eslatma: bu faqatfetchuchun avtomatik; DB so'rovlari uchun React'ningcache()funksiyasini ishlatasiz (o'rab).
2.3a. fetch keshlash opsiyalari va Data Cache (ISR)
NEXT.JS fetch — kengaytirilgan fetch (KESHLASH opsiyalari — 13.4 davomi):
1) KESHLANMAYDI (har so'rovda yangi — dinamik ma'lumot):
const res = await fetch(url, { cache: "no-store" }); // har render'da yangi
2) KESHLANADI (bir marta olib, keshdan beradi — statik):
const res = await fetch(url, { cache: "force-cache" }); // Data Cache (doimiy)
3) ISR — vaqt bo'yicha yangilash (har N soniyada bir marta qayta oladi):
const res = await fetch(url, { next: { revalidate: 60 } }); // 60s'da bir yangilanadi
4) TEG bilan (revalidateTag bilan qo'lda o'chirish uchun):
const res = await fetch(url, { next: { tags: ["products"] } });
// keyin Server Action'da: revalidateTag("products") shu fetch kesh o'chadi
┌────────────────────────────────────────────────────────────┐
│ Data Cache: fetch natijasi SERVER'da saqlanadi (so'rovlar │
│ orasida, deploy orasida ham) — Request Memoization'dan farq │
│ (memoization — bitta render ichida; Data Cache — doimiy) │
└────────────────────────────────────────────────────────────┘
cache: "no-store" — dinamik (har so'rov yangi); "force-cache" — statik (keshlanadi)
next: { revalidate: N } — ISR (N soniyada bir yangilanadi); tags — teg bilan o'chirishfetch keshlash opsiyalari va Data Cache (ISR) — Next.js'ning
fetchfunksiyasi standart brauzerfetch'ining kengaytirilgan versiyasi: unga keshlash opsiyalarini berish mumkin (13.4 rendering davomi). Ikki kesh qatlamini ajratish muhim (ular chalkashtiriladi): (1) Request Memoization — 2.3 — bitta render ichida bir xilfetchni dedup qiladi (vaqtinchalik, bir render tugagach yo'qoladi); (2) Data Cache — bu bo'lim —fetchnatijasini server'da doimiy saqlaydi (so'rovlar orasida, hatto qayta deploy orasida ham). Data Cache'nifetchopsiyalari boshqaradi: (a)cache: "no-store"— keshlanmaydi (har so'rovda tarmoqqa boradi — dinamik ma'lumot uchun: masalan real-time narx, foydalanuvchiga xos ma'lumot); (b)cache: "force-cache"— keshlanadi (bir marta olib, keyin keshdan beradi — statik ma'lumot uchun: masalan blog matni); (c)next: { revalidate: 60 }— ISR (Incremental Static Regeneration — 13.4): natija keshlanadi, lekin har 60 soniyada bir marta orqada yangilanadi (statiklik tezligi + yangilikning muvozanati — masalan yangiliklar sahifasi); (d)next: { tags: ["products"] }—fetchga teg qo'yadi, keyin Server Action'darevalidateTag("products")chaqirilsa 2.8-bob — aynan shu teg bilan belgilangan keshlar o'chadi (mutation'dan keyin aniq keshni tozalash uchun). Ikki nuqta: (1)cache: "no-store"— dinamik (har so'rov yangi),"force-cache"— statik (keshlanadi),next: { revalidate: N }— ISR (vaqt bo'yicha); (2) Data Cache doimiy (Request Memoization — bir render ichida — 2.3'dan farq). Kesh strategiyasi to'g'ridan render strategiyasiga (statik/dinamik — 13.4) ta'sir qiladi — bu ikki mavzu bir-biriga bog'liq. Data Cache va kesh qatlamlari 07-QISM (kesh nazariyasi) bilan chuqurroq bog'lanadi.
2.3b. Streaming va Suspense (loading)
STREAMING — sahifani QISMLARGA bo'lib yuborish (sekin qism butun sahifani tutmasin):
MUAMMO: sahifada bitta SEKIN fetch (masalan tavsiyalar — 2s) — butun sahifa 2s kutadi
YECHIM: sekin qismni <Suspense> ichiga o'ra qolgan sahifa DARROV, sekin qism keyin
// app/page.tsx
import { Suspense } from "react";
export default function Page() {
return (
<div>
<h1>Bosh sahifa</h1> {/* DARROV ko'rinadi */}
<Suspense fallback={<Skeleton />}>
<SlowRecommendations /> {/* tayyor bo'lganda "oqib" keladi */}
</Suspense>
</div>
);
}
// SlowRecommendations — o'z ma'lumotini oladigan async Server Component:
async function SlowRecommendations() {
const data = await fetch(".../recommendations", { cache: "no-store" });
return <List items={await data.json()} />;
}
loading.tsx — BUTUN sahifa uchun avtomatik Suspense 13.2-bob:
// app/products/loading.tsx products yuklanayotganda avtomatik ko'rsatiladi
<Suspense fallback> — sekin qism yuklanguncha fallback, tayyor bo'lsa oqib keladi
loading.tsx — sahifa darajasidagi avtomatik Suspense (skeleton — 13.2)Streaming va Suspense (loading) — sekin ma'lumot butun sahifani ushlab turmasligi uchun. Muammo: agar sahifada bitta sekin
fetchbo'lsa (masalan 2 soniya oladigan tavsiyalar), Server Component uniawaitqilganda — butun sahifa shu 2 soniyani kutadi (foydalanuvchi bo'sh ekranga qaraydi). Yechim — streaming: sekin qismni React'ning<Suspense>chegarasiga o'rash. Shunda Next.js sahifani qismlarga bo'lib yuboradi: (1) tez qismlar (sarlavha, tugmalar) darrov ko'rinadi; (2) sekin qism o'rnigafallback(masalan skeleton/spinner) ko'rsatiladi; (3) sekin qism tayyor bo'lganda — u sahifaga "oqib keladi" (stream), fallback almashadi. Misol:<Suspense fallback={<Skeleton />}><SlowRecommendations /></Suspense>— bu yerdaSlowRecommendationso'z ma'lumotini oladigan alohida async Server Component (o'ziningawait fetch'i bilan). Muhim naqsh: har sekin komponent o'z ma'lumotini o'zi oladi (ma'lumot fetch'ini pastga,<Suspense>ichidagi komponentga surish — shunda faqat o'sha qism kutadi). Sahifa darajasida esaloading.tsx13.2-bob avtomatik<Suspense>beradi — butun sahifa yuklanayotganda skeleton ko'rsatiladi (qo'lda<Suspense>yozmasdan). Ikki nuqta: (1)<Suspense fallback>— sekin qism yuklanguncha fallback, tayyor bo'lganda oqib keladi (ketma-ket ko'rinish); (2)loading.tsx— sahifa darajasidagi avtomatik Suspense 13.2-bob. Streaming — foydalanuvchi tez qismni darrov ko'rishini ta'minlaydi (idrok etilgan tezlik oshadi), sekin qism fonda kelaveradi. Bu — Server Components'ning katta ustunligi (SPA'da butun sahifa bir vaqtda yuklanardi).
2.3c. Xato boshqaruvi (error.tsx va try/catch)
XATO BOSHQARUVI — ikki daraja (render xatosi va action xatosi):
1) RENDER XATOSI (Server Component'da fetch/DB xato) error.tsx 13.2-bob:
// app/products/error.tsx ("use client" — MAJBUR):
"use client";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<p>Xatolik yuz berdi: {error.message}</p>
<button onClick={reset}>Qayta urinish</button> {/* segmentni qayta render */}
</div>
);
}
2) ACTION XATOSI (Server Action ichida) try/catch + return { error }:
export async function save(prev, formData) {
try {
await db.item.create({ ... });
return { success: true };
} catch {
return { error: "Saqlab bo'lmadi" }; // useActionState ko'rsatadi 2.6-bob
}
}
error.tsx — render (Server Component fetch/DB) xatosini ushlaydi ("use client" majbur)
Server Action xatosi — try/catch + return { error } (useActionState ko'rsatadi)Xato boshqaruvi (error.tsx va try/catch) — data fetching va mutation xatolarini nafis boshqarish. Ikki xil xato, ikki xil yechim: (1) Render xatosi — Server Component'da
fetch/DB so'rovi muvaffaqiyatsiz bo'lsa (masalan API o'chgan, DB ulanmadi) — komponent render bo'lolmaydi. Next.js bundaerror.tsx13.2-bob fayliga o'tadi: bu segment darajasidagi xato chegarasi (React Error Boundary — 11.13). U"use client"bo'lishi majbur (chunkiresetinteraktiv), va ikki prop oladi:error(xato obyekti) vareset(segmentni qayta render qilishga urinuvchi funksiya — "Qayta urinish" tugmasi). Shunday qilib butun ilova qulamaydi — faqat xato yuz bergan segment o'rnida xato UI ko'rsatiladi. (2) Action xatosi — Server Action ichida DB yozish yoki tashqi so'rov muvaffaqiyatsiz bo'lsa — bunitry/catchbilan ushlab,return { error: "..." }qaytarish kerak (Misol 8 —useActionStatebu xatoni forma'da ko'rsatadi — 2.6). Ya'ni action xatosinithrowqilib error.tsx'ga tashlash o'rniga, uni nafis qaytarib (forma o'z joyida xato ko'rsatadi) foydalanuvchi tajribasi yaxshiroq bo'ladi. Ikki nuqta: (1)error.tsx— render (Server Component fetch/DB) xatosini ushlaydi ("use client"majbur,resetbilan qayta urinish); (2) Server Action xatosi —try/catch+return { error }(useActionStateko'rsatadi). Eslatma:redirectvanotFoundmaxsus (ular xato "tashlaydi", lekin Next.js ularni ichki boshqaradi —try/catchularni ushlab qolmasin — 2.8, Misol 6). To'liq ilovada har fetch/action ehtimoliy xatoni hisobga olishi kerak (tarmoq ishonchsiz).
2.3d. N+1 muammosi va ma'lumot qayerda olinadi
N+1 MUAMMOSI — ro'yxat + har element uchun alohida so'rov (SEKIN):
N+1 (1 ta ro'yxat so'rovi + N ta element so'rovi):
const posts = await db.post.findMany(); // 1 so'rov
for (const post of posts) {
post.author = await db.user.findUnique({ where: { id: post.authorId } }); // +N so'rov!
}
// 100 post 101 DB so'rov (JUDA SEKIN)
JOIN/include (bitta so'rovda bog'liq ma'lumot — Prisma):
const posts = await db.post.findMany({ include: { author: true } }); // 1 so'rov (JOIN)
// 100 post ham bo'lsa — 1 so'rov (author'lar birga keladi)
MA'LUMOT QAYERDA OLINADI (qaror):
Server Component (DEFAULT) — o'qish, SEO, tez 13.3-bob — deyarli har doim
Client (TanStack Query — 12.4) — faqat interaktiv/real-time (infinite scroll,
tez-tez yangilanuvchi, foydalanuvchi hodisasiga bog'liq) — 12-QISM
N+1 — ro'yxatda har element uchun alohida so'rov JOIN/include (bitta so'rov)
Ma'lumot default Server'da; Client (TanStack Query) faqat interaktiv holatdaN+1 muammosi va ma'lumot qayerda olinadi — data fetching'ning ikki amaliy qarori. N+1 muammosi (backend'ning eng keng performance xatosi — 6-QISM DB): ro'yxatni olib, keyin har element uchun alohida bog'liq so'rov qilish. Misol: 100 ta post'ni olib (
findMany— 1 so'rov), keyin har post uchun muallifni alohida olish (forichidafindUnique— yana 100 so'rov) — jami 101 so'rov (juda sekin, DB'ni bo'g'adi). Yechim — ORM'ninginclude/joini:db.post.findMany({ include: { author: true } })— bitta so'rovda postlar va mualliflarni birga oladi (Prisma orqada JOIN qiladi — 6.12) — 100 post ham bo'lsa 1 so'rov. Server Component'da to'g'ridan DB so'rovi qulay, lekin N+1'dan ehtiyot bo'lish kerak (ro'yxatda har element uchun so'rov qilinmasin — bog'liq ma'lumotniincludebilan birga oling). Ikkinchi qaror — ma'lumot qayerda olinadi: (1) Server Component (default) — deyarli har doim (o'qish, SEO, tez, xavfsiz — 13.3): sahifa yuklanishida kerak bo'lgan ma'lumot server'da olinadi; (2) Client (TanStack Query — 12.4) — faqat interaktiv/real-time holatlarda: infinite scroll, tez-tez yangilanuvchi ma'lumot (jonli hisob), foydalanuvchi hodisasiga bog'liq qidiruv/filter (server'ga qayta-qayta bormasdan client'da keshlash kerak bo'lganda — 12-QISM). Ikki nuqta: (1) N+1 — ro'yxatda har element uchun alohida so'rov (JOIN/includebilan bitta so'rovga birlashtiring); (2) ma'lumot default Server'da (Server Component), Client (TanStack Query) faqat interaktiv holatda. Qoida: dastlabki sahifa ma'lumoti — Server'da (tez, SEO); keyingi interaktiv ma'lumot — vaziyatga qarab Client'da (TanStack Query — 12.4).
2.4. Server Actions — nima va nega
SERVER ACTION — server'da ishlaydigan funksiya, KOMPONENTDAN to'g'ridan chaqiriladi:
// app/actions.ts
"use server"; // bu fayldagi funksiyalar — SERVER ACTIONS (server'da ishlaydi)
export async function createPost(formData: FormData) {
const title = formData.get("title");
await db.post.create({ data: { title } }); // server'da DB'ga yozadi
}
KOMPONENTDAN TO'G'RIDAN (API endpoint YO'Q!):
// app/page.tsx
import { createPost } from "./actions";
<form action={createPost}> {/* Server Action to'g'ridan forma action'i */}
<input name="title" />
<button>Saqlash</button>
</form>
ESKI USUL (Server Actions'siz — ko'p kod):
API endpoint yoz (app/api/posts/route.ts — POST handler)
Client'da fetch("/api/posts", { method: "POST", body... })
JSON serializatsiya, xato boshqaruvi qo'lda
Server Action — server funksiya, to'g'ridan komponentdan (API endpoint, fetch YO'Q)
"use server" — funksiyani Server Action qiladi (Next.js network'ni avtomatik boshqaradi)Server Actions — nima va nega — Next.js'ning eng inqilobiy imkoniyati. Server Action — server'da ishlaydigan funksiya, lekin uni to'g'ridan komponentdan (forma yoki tugma) chaqirasiz — Next.js o'rtada so'rovni (network) avtomatik boshqaradi. Belgilash: faylga (yoki funksiyaga)
"use server"direktivasi (13.3'dagi"use client"ning "teskarisi" — bu funksiya server'da ishlasin). Misol:// app/actions.tsichida"use server"+export async function createPost(formData) { await db.post.create(...) }— server funksiya. Keyin komponentda:<form action={createPost}>— Server Action'ni to'g'ridan formaaction'iga berasiz (HTML forma kabi, lekin server funksiya). Eski usul (Server Actions'siz — ko'p kod): (1) API endpoint yozish (app/api/posts/route.ts— POST handler — 13.6); (2) client'dafetch("/api/posts", { method: "POST", body... }); (3) JSON serializatsiya, xato boshqaruvi qo'lda. Server Action bu uch qadamni bir funksiyaga siqadi (API yo'q, fetch yo'q — to'g'ridan). Ikki nuqta: (1) Server Action — server funksiya, to'g'ridan komponentdan (API endpoint,fetchyo'q); (2)"use server"— funksiyani Server Action qiladi (Next.js network'ni avtomatik boshqaradi — siz ko'pmaysiz ham). Bu — full-stack rivojlanishni soddalashtiradi (frontend va backend bir joyda — alohida backend server kerak bo'lmasligi mumkin). Server Actions — mutation (yaratish/yangilash/o'chirish) uchun Next.js'ning asosiy usuli.
2.5. Forma bilan Server Actions (progressive enhancement)
FORMA + SERVER ACTION — eng tabiiy birikma (HTML forma kabi, lekin server funksiya):
// Server Action (forma ma'lumotini oladi):
"use server";
export async function addTodo(formData: FormData) {
const text = formData.get("text") as string;
await db.todo.create({ data: { text } });
revalidatePath("/todos"); // ro'yxatni yangila (13.4: 2.5)
}
// Forma (action — Server Action):
<form action={addTodo}>
<input name="text" required /> {/* name — formData.get("text") */}
<button type="submit">Qo'shish</button>
</form>
PROGRESSIVE ENHANCEMENT (Server Actions'ning kuchi):
JavaScript YUKLANGAN bo'lsa: client-side (tez, sahifa yangilanmaydi)
JavaScript O'CHIQ/YUKLANMAGAN bo'lsa: oddiy HTML forma (server'ga POST — baribir ISHLAYDI!)
ya'ni forma HAR DOIM ishlaydi (JS bo'lmasa ham — ishonchli, sekin internetda ham)
<form action={serverAction}> — forma ma'lumotini Server Action oladi (FormData)
Progressive enhancement — JS bo'lmasa ham forma ishlaydi (HTML forma — ishonchli)Forma bilan Server Actions (progressive enhancement) — Server Actions'ning eng tabiiy va kuchli birikmasi. Forma
actionatributiga Server Action'ni berasiz:<form action={addTodo}>— forma yuborilganda,addTodoServer Action chaqiriladi va forma ma'lumotini (FormData) oladi. Server Action ichida:formData.get("text")(input'ningname="text"qiymati), keyin DB'ga yozish, keyinrevalidatePath("/todos")(ro'yxatni yangilash — 13.4: 2.5). Eng muhim afzallik — progressive enhancement (bosqichma-bosqich yaxshilanish): (1) agar JavaScript yuklangan bo'lsa — Server Action client-side ishlaydi (tez, sahifa qayta yuklanmaydi — SPA tajribasi); (2) agar JavaScript o'chiq yoki hali yuklanmagan bo'lsa (sekin internet, eski brauzer, JS xato) — forma oddiy HTML forma kabi ishlaydi (server'ga POST so'rov — sahifa qayta yuklanadi, lekin baribir ishlaydi!). Ya'ni forma har doim ishlaydi (JavaScript bo'lmasa ham) — bu juda ishonchli (foydalanuvchi internet sekin bo'lsa yoki JS yuklanmagan bo'lsa ham forma yuborila oladi). SPA'da (11-QISM) bu mumkin emas edi (JS yuklanmasa — hech narsa ishlamasdi — bo'sh sahifa). Ikki nuqta: (1)<form action={serverAction}>— forma ma'lumotini Server ActionFormDataorqali oladi (input'larnamebilan); (2) progressive enhancement — JS bo'lmasa ham forma ishlaydi (HTML forma — ishonchli, kirish imkoni — accessibility). Bu — Server Actions'ning web'ning asoslariga (HTML forma) qaytishi, lekin zamonaviy tajriba bilan. Forma + Server Action — Next.js'da mutation'ning eng toza usuli.
2.6. useActionState va useFormStatus (pending, xato)
FORMA HOLATI — pending (yuborilmoqda) va xato (Client Component'da):
// useActionState — Server Action natijasi + pending holati (React 19):
"use client";
import { useActionState } from "react";
import { createPost } from "./actions";
export default function PostForm() {
// [holat, action, pending] — Server Action'ni o'rab, holat boshqaradi:
const [state, formAction, isPending] = useActionState(createPost, { error: null });
return (
<form action={formAction}>
<input name="title" />
{state.error && <p className="error">{state.error}</p>} {/* server'dan xato */}
<button disabled={isPending}>{isPending ? "Saqlanmoqda..." : "Saqlash"}</button>
</form>
);
}
// useFormStatus — forma yuborilish holati (forma ICHIDAGI komponentda):
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus(); // forma yuborilmoqdami?
return <button disabled={pending}>{pending ? "..." : "Yuborish"}</button>;
}
useActionState — Server Action + holat (xato/natija) + pending (forma natijasi)
useFormStatus — forma yuborilish holati (pending) — forma ichidagi tugmadauseActionState va useFormStatus — forma holatini (pending, xato) boshqarish (Client Component'da). Server Action server'da ishlaydi, lekin forma holati (yuborilmoqdami, xato bormi) client'da ko'rsatiladi — buning uchun ikki hook: (1)
useActionState(React 19 — eski nomiuseFormState) — Server Action'ni o'rab, uning natijasi (state — masalan xato xabari) va pending holatini boshqaradi:const [state, formAction, isPending] = useActionState(createPost, { error: null })—state(Server Action qaytargan natija — xato/muvaffaqiyat),formAction(formaga beriladigan o'ralgan action),isPending(yuborilmoqdami). Forma:<form action={formAction}>, xato ko'rsatish{state.error && <p>...}, tugmadisabled={isPending}(ikki marta bosishning oldini olish). (2)useFormStatus(react-dom'dan) — forma yuborilish holatini beradi (const { pending } = useFormStatus()), lekin forma ichidagi komponentda ishlaydi (masalan alohidaSubmitButtonkomponenti — formani o'rab turgan forma holatini biladi). Ikki nuqta: (1)useActionState— Server Action + holat (xato/natija) + pending (forma natijasini boshqarish — eng keng); (2)useFormStatus— forma yuborilish holati (pending) — forma ichidagi tugmada (alohida komponent). Bular — Server Action bilan yaxshi UX (yuklanish ko'rsatish, xato ko'rsatish, ikki marta yuborishning oldini olish) beradigan hooklar. Forma + Server Action +useActionState— Next.js'da to'liq forma oqimining standart naqshi.
2.7. Client Component'dan Server Action (hodisa)
SERVER ACTION'ni TUGMA HODISASIDAN (forma'siz — Client Component'da):
"use client";
import { deletePost } from "./actions"; // Server Action
export default function DeleteButton({ postId }: { postId: string }) {
async function handleDelete() {
// Server Action'ni to'g'ridan chaqir (forma'siz — hodisada):
await deletePost(postId); // server'da o'chiradi (fetch/API YO'Q)
}
return <button onClick={handleDelete}>O'chirish</button>;
}
yoki TO'G'RIDAN action prop (argument bilan — bind):
<form action={deletePost.bind(null, postId)}> {/* postId'ni bog'la */}
<button>O'chirish</button>
</form>
┌────────────────────────────────────────────────────────────┐
│ Server Action: forma (action) YOKI hodisa (onClick) orqali │
│ ikkala holda ham server'da ishlaydi (fetch/API yo'q) │
└────────────────────────────────────────────────────────────┘
Server Action — forma (action) yoki tugma (onClick) — ikkalasidan chaqirsa bo'ladi
Argument bilan: to'g'ridan chaqir (onClick) yoki .bind(null, arg) (forma action)Client Component'dan Server Action (hodisa) — Server Action'ni forma'siz, tugma hodisasidan chaqirish. Server Action'ni faqat forma orqali emas, tugma hodisasidan (
onClick) ham chaqirsa bo'ladi (Client Component'da). Misol:"use client"+async function handleDelete() { await deletePost(postId); }+<button onClick={handleDelete}>O'chirish</button>— tugma bosilgandadeletePostServer Action chaqiriladi (server'da post'ni o'chiradi —fetch/API yo'q, to'g'ridan funksiya chaqiruvi). Bu — argumentli Server Action uchun qulay (masalanpostId— o'chiriladigan post). Yoki formaactionda argument bilan:<form action={deletePost.bind(null, postId)}>—.bind(null, postId)postIdni Server Action'ga "bog'laydi" (forma yuborilgandadeletePost(postId, formData)chaqiriladi). Ikki nuqta: (1) Server Action — forma (action) yoki tugma (onClick) — ikkalasidan chaqirsa bo'ladi (ikkalasida ham server'da ishlaydi —fetch/API yo'q); (2) argument bilan: to'g'ridan chaqir (onClick—deletePost(postId)) yoki.bind(null, arg)(formaaction). Demak Server Actions moslashuvchan: forma submit (eng tabiiy — 2.5), tugma hodisasi (interaktiv — bu bo'lim), yoki argumentli bind. Har holda ham server funksiyasi to'g'ridan chaqiriladi (network avtomatik). Bu — mutation'ning turli holatlariga (forma, tugma, ro'yxat elementi) moslashishi.
2.8. Revalidation va redirect (mutation'dan keyin)
MUTATION'DAN KEYIN — keshni yangilash (revalidate) va yo'naltirish (redirect):
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
import { redirect } from "next/navigation";
export async function createPost(formData: FormData) {
const post = await db.post.create({ data: { title: formData.get("title") } });
// 1. KESHHI YANGILA (yangi post ro'yxatda ko'rinsin — 13.4: 2.5):
revalidatePath("/posts"); // /posts sahifani qayta generatsiya
// revalidateTag("posts"); // yoki teg bo'yicha
// 2. YO'NALTIR (yangi post sahifasiga — ixtiyoriy):
redirect(`/posts/${post.id}`); // yangi post'ga o'tkaz
}
NEGA REVALIDATE KERAK:
Server Action DB'ni o'zgartirdi, lekin sahifa KESHDA (eski) — yangilanmaydi
revalidatePath/Tag kesh'ni o'chiradi sahifa yangi ma'lumot bilan
Mutation'dan keyin revalidatePath/Tag (yangi ma'lumot ko'rinsin — kesh yangilansin)
redirect — action'dan keyin boshqa sahifaga (yangi post, dashboard)Revalidation va redirect (mutation'dan keyin) — Server Action'dan keyingi muhim qadamlar. Server Action DB'ni o'zgartirgandan keyin ikki narsa kerak bo'lishi mumkin: (1) revalidation — keshni yangilash. Muammo: Server Action DB'ni o'zgartirdi (yangi post qo'shdi), lekin sahifa (masalan
/postsro'yxati) keshda (eski — 13.4) — avtomatik yangilanmaydi. Yechim —revalidatePath("/posts")(yokirevalidateTag("posts")—next/cache'dan) — bu kesh'ni o'chiradi, sahifa keyingi ko'rishda yangi ma'lumot bilan render bo'ladi (yangi post ko'rinadi). Bu majburiy (revalidate'siz, foydalanuvchi yangi post'ni ko'rmaydi — eski keshlangan ro'yxat). (2) redirect — action'dan keyin boshqa sahifaga yo'naltirish:redirect(\/posts/${post.id}`)(next/navigation'dan) — masalan yangi post yaratilgach, o'sha post sahifasiga o'tkazish (yoki forma yuborilgach dashboard'ga). Ikki nuqta: (1) mutation'dan keyin **revalidatePath/revalidateTag** (yangi ma'lumot ko'rinsin — kesh yangilansin — bu eng keng unutiladigan qadam); (2) **redirect`** — action'dan keyin boshqa sahifaga (yangi yaratilgan element sahifasiga, yoki muvaffaqiyat sahifasiga). Bu ikki — mutation oqimining yakuni (DB o'zgardi kesh yangilandi foydalanuvchi yangi holatni ko'radi/yo'naltirildi). Revalidate'ni unutish — eng keng Server Actions xatosi (DB o'zgardi, lekin ekranda eski — chunki kesh yangilanmagan).
2.9. Validatsiya Server Action'da (Zod)
VALIDATSIYA — Server Action'da kirish ma'lumotini TEKSHIRISH (Zod — 11.10, 10-QISM):
"use server";
import { z } from "zod";
const PostSchema = z.object({
title: z.string().min(3, "Sarlavha kamida 3 harf"),
content: z.string().min(10, "Matn kamida 10 harf"),
});
export async function createPost(prevState: any, formData: FormData) {
// 1. Tekshir (Zod — server'da — ISHONCHLI, brauzer'ni aylanib o'tib bo'lmaydi):
const result = PostSchema.safeParse({
title: formData.get("title"),
content: formData.get("content"),
});
if (!result.success) {
return { error: result.error.flatten().fieldErrors }; // xatolar (forma ko'rsatadi)
}
// 2. Faqat VALID ma'lumot DB'ga:
await db.post.create({ data: result.data });
revalidatePath("/posts");
return { success: true };
}
Server Action'da validatsiya (Zod) — server'da ISHONCHLI (client tekshiruvi aylanib o'tilsa ham)
safeParse xato bo'lsa forma'ga qaytar (useActionState bilan ko'rsat — 2.6)Validatsiya Server Action'da (Zod) — kirish ma'lumotini xavfsiz tekshirish. Server Action foydalanuvchi ma'lumotini (forma) oladi, va uni DB'ga yozishdan oldin tekshirish kerak (validatsiya). Buning uchun Zod (11.10, 10-QISM — schema validatsiya):
const PostSchema = z.object({ title: z.string().min(3, "..."), content: z.string().min(10, "...") })— sxema (qoidalar). Server Action ichida:PostSchema.safeParse({ title: formData.get("title"), ... })— ma'lumotni tekshiradi; agar xato (!result.success) — xatolarni forma'ga qaytaradi (return { error: ... }—useActionStatebilan ko'rsatiladi — 2.6); agar to'g'ri — faqat valid ma'lumotni (result.data) DB'ga yozadi. Eng muhim: validatsiya server'da (Server Action'da) — bu ishonchli (client-side validatsiya — 11.10 — foydalanuvchi tomonidan aylanib o'tilishi mumkin: brauzer devtools, to'g'ridan API so'rov — lekin server validatsiyasini aylanib o'tib bo'lmaydi). Client validatsiya (RHF+Zod — 11.10) — UX uchun (darrov xato ko'rsatish), server validatsiya (Server Action'da) — xavfsizlik uchun (haqiqiy himoya — 14-QISM). Ikki nuqta: (1) Server Action'da validatsiya (Zod) — server'da ishonchli (client tekshiruvi aylanib o'tilsa ham server ushlaydi — bu majburiy xavfsizlik); (2)safeParsexato bo'lsa forma'ga qaytar (useActionStatebilan ko'rsat — 2.6). Bu — har Server Action'ning birinchi qadami (ma'lumotni tekshirish, keyin ishlatish) — xavfsiz va ishonchli mutation'ning asosi. Hech qachon foydalanuvchi ma'lumotiga ishonmaslik kerak (validatsiyasiz DB'ga yozmaslik shart).
2.10. useOptimistic (darrov UI)
useOptimistic — Server Action javobini KUTMASDAN darrov UI (optimistic — 12.4: 2.10):
"use client";
import { useOptimistic } from "react";
import { addTodo } from "./actions";
export default function TodoList({ todos }: { todos: Todo[] }) {
// Optimistic holat — darrov qo'shilgandek ko'rsat (server tasdiqlamasdan):
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newTodo: string) => [...state, { id: "temp", text: newTodo, pending: true }]
);
async function formAction(formData: FormData) {
const text = formData.get("text") as string;
addOptimistic(text); // DARROV UI'ga qo'sh (server kutmasdan)
await addTodo(formData); // keyin server'da (tasdiqlash)
}
return (
<form action={formAction}>
<input name="text" />
<ul>{optimisticTodos.map((t) => <li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.text}</li>)}</ul>
</form>
);
}
useOptimistic — Server Action javobini kutMASDAN darrov UI (tez his — optimistic)
Server tasdiqlasa — qoladi; xato bo'lsa — avtomatik orqaga (React boshqaradi)useOptimistic (darrov UI) — Server Action bilan darrov, silliq UX.
useOptimistic(React 19) — Server Action javobini kutmasdan UI'ni darrov yangilash (optimistic update — 12.4: 2.10 — TanStack Query'ning Next.js versiyasi). Muammo: foydalanuvchi todo qo'shganda, Server Action server'da ishlaydi (DB'ga yozadi — bir oz vaqt) — agar javobni kutsak, todo darrov ko'rinmaydi (sekin his). Yechim —useOptimistic:const [optimisticTodos, addOptimistic] = useOptimistic(todos, (state, newTodo) => [...state, { ...newTodo, pending: true }])— bu "optimistic" holat yaratadi (haqiqiy + kutilayotgan). Forma action'ida:addOptimistic(text)(todo'ni darrov UI'ga qo'shadi — server kutmasdan — foydalanuvchi darrov ko'radi), keyinawait addTodo(formData)(server'da haqiqatan yozadi). Agar server muvaffaqiyatli — optimistic todo qoladi (real bo'ladi); agar xato — React avtomatik optimistic'ni orqaga qaytaradi (todo yo'qoladi — chunki server rad etdi).pending: truebilan optimistic todo'ni biroz xira (opacity: 0.5) ko'rsatish mumkin (hali tasdiqlanmagan). Ikki nuqta: (1)useOptimistic— Server Action javobini kutmasdan darrov UI (tez his — optimistic, foydalanuvchi kutmaydi); (2) server tasdiqlasa — qoladi, xato bo'lsa — avtomatik orqaga (React boshqaradi — siz qo'lda rollback yozmaysiz). Bu — Server Actions bilan eng silliq UX (darrov javob, lekin server haqiqati bilan). Optimistic update — zamonaviy ilova tajribasining belgisi (Twitter like, todo qo'shish — darrov ko'rinadi).
2.11. Server Actions xavfsizligi va best practices
SERVER ACTIONS XAVFSIZLIGI (server'da — lekin EHTIYOT bo'l):
VALIDATSIYA (Zod — har doim — kirish ma'lumotini tekshir — 2.9)
AVTORIZATSIYA (kim chaqiryapti? — har Server Action'da tekshir):
"use server";
export async function deletePost(id: string) {
const session = await getServerSession(); // kim?
if (!session) throw new Error("Ruxsat yo'q"); // autentifikatsiya tekshir
const post = await db.post.findUnique({ where: { id } });
if (post.authorId !== session.user.id) throw new Error("Bu sizniki emas"); // egalik
await db.post.delete({ where: { id } });
}
Server Action OCHIQ endpoint kabi — HAR KIM chaqira oladi (validatsiya+avtorizatsiya SHART)
BEST PRACTICES:
"use server" alohida faylda (actions.ts — tartibli)
Validatsiya (Zod) + avtorizatsiya (session) — har Server Action'da
revalidatePath/Tag mutation'dan keyin 2.8-bob
useActionState (xato/pending — 2.6); useOptimistic (darrov UI — 2.10)
Server Component'da fetch parallel (Promise.all — waterfall'dan saqlanish — 2.2)
Server Action — ochiq endpoint (validatsiya + avtorizatsiya MAJBURIY — xavfsizlik)
Best: actions.ts + Zod + session tekshir + revalidate + useActionState/useOptimisticServer Actions xavfsizligi va best practices — Server Actions'ni professional va xavfsiz ishlatish. Xavfsizlik (eng muhim — ko'p chalkashtiriladi): Server Action server'da ishlaydi, lekin u aslida ochiq endpoint (HTTP endpoint) — ya'ni har kim uni chaqira oladi (brauzer devtools, to'g'ridan so'rov — xuddi API endpoint kabi). Shuning uchun har Server Action'da: (1) validatsiya (Zod — kirish ma'lumotini tekshir — 2.9); (2) avtorizatsiya — kim chaqiryapti tekshir:
const session = await getServerSession(); if (!session) throw new Error("Ruxsat yo'q")(autentifikatsiya — kirganmi?) va egalik tekshiruvi (if (post.authorId !== session.user.id) throw...— bu post shu foydalanuvchiniki emasmi? — boshqa odamning postini o'chira olmasin). Validatsiya/avtorizatsiyasiz Server Action — xavfli (har kim har narsani qila oladi — masalan boshqa odamning post'ini o'chirish). Best practices: (1)"use server"ni alohida faylda (actions.ts— tartibli, qayta ishlatsa bo'ladi); (2) validatsiya (Zod) + avtorizatsiya (session) — har Server Action'da; (3)revalidatePath/revalidateTagmutation'dan keyin 2.8-bob; (4)useActionState(xato/pending — 2.6),useOptimistic(darrov UI — 2.10); (5) Server Component'da fetch parallel (Promise.all— waterfall'dan saqlanish — 2.2). Ikki nuqta: (1) Server Action — ochiq endpoint (validatsiya + avtorizatsiya majburiy — server'da bo'lgani uni avtomatik xavfsiz qilmaydi); (2) best:actions.ts+ Zod + session tekshir + revalidate +useActionState/useOptimistic. Bu — Server Actions'ni to'g'ri, xavfsiz, professional ishlatishning yakuni (14-QISM xavfsizlik bilan chuqurroq). Server Actions kuchli, lekin xavfsizlik dasturchi zimmasida (har action himoyalangan bo'lishi shart).
3. Sintaksis — tez ma'lumotnoma
FETCH 2.1-bob: const data = await fetch(url).then(r => r.json()) (Server Component)
DB 2.1-bob: const items = await db.model.findMany() (ORM — server'da)
PARALLEL 2.2-bob: const [a, b] = await Promise.all([fetch(...), fetch(...)])
KESH (2.3a): fetch(url, { cache: "no-store" | "force-cache" })
ISR (2.3a): fetch(url, { next: { revalidate: 60, tags: ["x"] } })
STREAMING (2.3b): <Suspense fallback={<Skeleton/>}><Slow/></Suspense> | loading.tsx
XATO (2.3c): error.tsx ("use client", reset) | Action: try/catch + return {error}
N+1 (2.3d): db.post.findMany({ include: { author: true } }) (JOIN — 1 so'rov)
SERVER ACTION 2.4-bob:"use server"; export async function act(formData) {...}
FORMA 2.5-bob: <form action={act}> (progressive enhancement)
HOLAT 2.6-bob: const [state, formAction, isPending] = useActionState(act, init)
HODISA 2.7-bob: onClick={() => act(arg)} | action={act.bind(null, arg)}
REVALIDATE 2.8-bob: revalidatePath("/x") | revalidateTag("x") | redirect("/y")
VALIDATSIYA 2.9-bob: const r = Schema.safeParse(...); if(!r.success) return {error}
OPTIMISTIC 2.10-bob: const [opt, addOpt] = useOptimistic(data, reducer)
XAVFSIZLIK 2.11-bob: session tekshir + Zod (har Server Action — ochiq endpoint)4. Batafsil kod namunalari
Har misol: Maqsad + izohli kod + "Bu kod nima qiladi".
Misol 1 — Parallel data fetching (waterfall'dan saqlanish — 2.2)
Maqsad: Bir sahifada bir necha bog'liq bo'lmagan ma'lumotni parallel olish (waterfall'dan saqlanish) — sahifani 2-3 baravar tezroq qilish. Bu data fetching performance'ining asosiy darsi.
// app/dashboard/page.tsx — Server Component (3 ta bog'liq bo'lmagan ma'lumot)
export default async function DashboardPage() {
// NOTO'G'RI (waterfall — ketma-ket — 3s):
// const user = await getUser(); // 1s
// const stats = await getStats(); // +1s
// const orders = await getOrders(); // +1s JAMI 3s
// TO'G'RI (parallel — birga — 1s):
const [user, stats, orders] = await Promise.all([
getUser(), // ┐
getStats(), // ├─ uchalasi BIR VAQTDA boshlanadi
getOrders(), // ┘
]);
// eng sekin so'rov qancha bo'lsa — shuncha (1s — parallel)
return (
<div>
<h1>Salom, {user.name}</h1>
<Stats data={stats} />
<Orders data={orders} />
</div>
);
}Bu kod nima qiladi: Bu — data fetching performance'ining eng muhim darsi (waterfall'dan saqlanish — 2.2). Dashboard'da uch xil ma'lumot kerak: user, stats, orders — ular bir-biriga bog'liq emas (har biri mustaqil olinadi). Noto'g'ri yo'l (waterfall): har birini ketma-ket await qilish — getUser() (1 soniya kutadi), keyin getStats() (yana 1 soniya), keyin getOrders() (yana 1 soniya) — jami 3 soniya (garchi ular bog'liq bo'lmasa ham, biri ikkinchisini kutdi). To'g'ri yo'l (parallel): Promise.all([getUser(), getStats(), getOrders()]) — uchalasi bir vaqtda boshlanadi (parallel), va eng sekin so'rov qancha bo'lsa — shuncha kutiladi (~1 soniya, 3 emas). Natijada: sahifa 3 baravar tezroq yuklanadi (3s 1s). Bu — Next.js (va umuman async) data fetching'ning eng keng performance xatosi: bog'liq bo'lmagan so'rovlarni ketma-ket qilish (waterfall). Qoida: bog'liq bo'lmagan so'rovlar Promise.all (parallel); faqat biri ikkinchisiga bog'liq bo'lganda (masalan getPosts(user.id) — user.id kerak) — ketma-ket majbur 2.2-bob. Server Component'da ko'p so'rov bo'lsa, har doim "bular parallel bo'la oladimi?" deb o'ylang (waterfall sahifani sekinlashtiradi).
Misol 2 — Server Action forma (asosiy — 2.5)
Maqsad: Server Action bilan forma yaratish — backend API'siz ma'lumot saqlash. Bu Server Actions'ning eng tabiiy va asosiy ishlatilishi.
// app/todos/actions.ts — Server Action (alohida fayl — best practice 2.11)
"use server";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
export async function addTodo(formData: FormData) {
const text = formData.get("text") as string; // input name="text"
if (!text?.trim()) return; // bo'sh bo'lsa qaytar
await db.todo.create({ data: { text } }); // server'da DB'ga yozadi
revalidatePath("/todos"); // ro'yxatni yangila (2.8)
}
// app/todos/page.tsx — Server Component (forma + ro'yxat)
import { addTodo } from "./actions";
import { db } from "@/lib/db";
export default async function TodosPage() {
const todos = await db.todo.findMany(); // server'da ma'lumot (2.1)
return (
<div>
{/* Forma — Server Action to'g'ridan (API endpoint YO'Q!): */}
<form action={addTodo}>
<input name="text" placeholder="Yangi vazifa" required />
<button type="submit">Qo'shish</button>
</form>
<ul>{todos.map((t) => <li key={t.id}>{t.text}</li>)}</ul>
</div>
);
}Bu kod nima qiladi: Bu — Server Action bilan to'liq forma oqimi (Next.js'ning eng tabiiy mutation usuli). Server Action (actions.ts — "use server"): addTodo(formData) — forma ma'lumotini (FormData) oladi, formData.get("text") (input'ning name="text" qiymati), DB'ga yozadi (db.todo.create), keyin revalidatePath("/todos") (ro'yxatni yangilaydi — 2.8). Sahifa (page.tsx — Server Component): todosni server'da oladi 2.1-bob, va <form action={addTodo}> — Server Action'ni to'g'ridan forma action'iga beradi. Endi diqqat: bu yerda hech qanday backend API yo'q — app/api/todos/route.ts yo'q, fetch("/api/todos", { method: "POST" }) yo'q, JSON serializatsiya yo'q. Foydalanuvchi formani to'ldirib "Qo'shish" bossa: Next.js avtomatik Server Action'ni chaqiradi (server'da addTodo ishlaydi — DB'ga yozadi), revalidatePath ro'yxatni yangilaydi, va yangi todo ko'rinadi — barchasi bir funksiya bilan. Eski usulda (Express/NestJS backend — 7, 8-QISM) bu uchun: route yozish, controller, client'da fetch, loading boshqaruvi — o'nlab qator kod kerak edi. Server Action buni inqilobiy soddalashtiradi. Eslatma: Server Action alohida faylda (actions.ts — best practice — 2.11, qayta ishlatsa bo'ladi). Bu — Next.js full-stack'ning yuragi (frontend + backend bir joyda).
Misol 3 — useActionState (xato va pending — 2.6, 2.9)
Maqsad: Forma'da validatsiya xatolari va yuklanish holatini ko'rsatish — useActionState + Zod bilan. Bu professional forma UX'ining standart naqshi.
// app/posts/actions.ts
"use server";
import { z } from "zod";
const PostSchema = z.object({
title: z.string().min(3, "Sarlavha kamida 3 harf bo'lsin"),
});
export async function createPost(prevState: any, formData: FormData) {
const result = PostSchema.safeParse({ title: formData.get("title") });
if (!result.success) {
// Xato'ni forma'ga qaytar (useActionState oladi):
return { error: result.error.flatten().fieldErrors.title?.[0] };
}
await db.post.create({ data: result.data });
revalidatePath("/posts");
return { success: true, error: null };
}
// app/posts/PostForm.tsx — Client (holat boshqaruvi)
"use client";
import { useActionState } from "react";
import { createPost } from "./actions";
export default function PostForm() {
// [holat, o'ralgan action, pending]:
const [state, formAction, isPending] = useActionState(createPost, { error: null });
return (
<form action={formAction}>
<input name="title" />
{state?.error && <p className="error">{state.error}</p>} {/* server validatsiya xatosi */}
<button disabled={isPending}>{isPending ? "Saqlanmoqda..." : "Saqlash"}</button>
</form>
);
}Bu kod nima qiladi: Bu — professional forma oqimi (validatsiya xatosi + yuklanish holati). Server Action (createPost): useActionState bilan ishlash uchun ikki argument oladi — prevState (oldingi holat) va formData. Ichida: Zod bilan tekshiradi (PostSchema.safeParse — 2.9); agar xato — return { error: ... } (xatoni forma'ga qaytaradi — DB'ga yozmaydi); agar to'g'ri — DB'ga yozadi, revalidatePath, return { success: true }. Client komponent (PostForm): useActionState(createPost, { error: null }) — Server Action'ni o'rab, uch narsani beradi: state (Server Action qaytargan natija — xato/muvaffaqiyat), formAction (formaga beriladigan o'ralgan action), isPending (yuborilmoqdami). Forma: <form action={formAction}>, xato bo'lsa {state.error && <p>...} (server validatsiya xatosi ko'rsatiladi — masalan "Sarlavha kamida 3 harf"), tugma disabled={isPending} (yuborilayotganda o'chiq — ikki marta bosishning oldini olish) va matn o'zgaradi ("Saqlanmoqda..."). Natija: foydalanuvchi qisqa sarlavha kiritsa — xato ko'radi (forma yuborilmaydi); to'g'ri kiritsa — saqlanadi, tugma "Saqlanmoqda..." ko'rsatadi, keyin yangilanadi. Bu — Server Action + validatsiya + UX'ning to'liq, professional birikmasi (xato ko'rsatish, yuklanish, ikki marta yuborishdan himoya). useActionState — Server Actions bilan forma holatini boshqarishning standart hooki (React 19).
Misol 4 — Server Action tugmadan (o'chirish — 2.7, 2.11)
Maqsad: Server Action'ni tugma hodisasidan chaqirish (forma'siz) + avtorizatsiya tekshiruvi — xavfsiz o'chirish amali. Bu ro'yxat elementini o'chirish kabi keng holat.
// app/posts/actions.ts
"use server";
import { getServerSession } from "@/lib/auth";
import { revalidatePath } from "next/cache";
export async function deletePost(postId: string) {
// AVTORIZATSIYA — kim o'chiryapti? (Server Action ochiq endpoint — 2.11):
const session = await getServerSession();
if (!session) throw new Error("Tizimga kiring");
const post = await db.post.findUnique({ where: { id: postId } });
if (post?.authorId !== session.user.id) throw new Error("Bu post sizniki emas"); // egalik
await db.post.delete({ where: { id: postId } }); // faqat o'z postini o'chiradi
revalidatePath("/posts");
}
// app/posts/DeleteButton.tsx — Client
"use client";
import { deletePost } from "./actions";
export default function DeleteButton({ postId }: { postId: string }) {
return (
<button
onClick={async () => {
if (confirm("O'chirishni tasdiqlaysizmi?")) {
await deletePost(postId); // Server Action — to'g'ridan (forma'siz, hodisada)
}
}}
>
O'chirish
</button>
);
}Bu kod nima qiladi: Bu — Server Action'ni tugma hodisasidan chaqirish (forma'siz — 2.7) va xavfsizlik (avtorizatsiya — 2.11). Server Action (deletePost): postIdni argument sifatida oladi (forma emas — to'g'ridan qiymat). Eng muhim — xavfsizlik tekshiruvi: (1) autentifikatsiya — getServerSession() (kim chaqiryapti? — kirganmi?), kirmagan bo'lsa xato; (2) egalik — post.authorId !== session.user.id (bu post shu foydalanuvchiniki emasmi?), boshqaniki bo'lsa xato. Bu majburiy — chunki Server Action ochiq endpoint (har kim chaqira oladi — 2.11), shuning uchun "bu odam bu postni o'chira oladimi?" deb tekshirish shart (aks holda har kim har kimning postini o'chira olardi — jiddiy xavfsizlik teshigi). Faqat tekshiruvdan o'tsa — DB'dan o'chiradi, revalidatePath. Client komponent (DeleteButton): <button onClick={async () => { if (confirm(...)) await deletePost(postId) }}> — tugma bosilganda (tasdiqlab) Server Action'ni to'g'ridan chaqiradi (forma yo'q — hodisa — 2.7). confirm — tasodifan o'chirishdan himoya (UX). Demak: Server Action forma'siz, tugma hodisasidan chaqiriladi (ro'yxat elementini o'chirish, like, status o'zgartirish kabi holatlar uchun), va har Server Action xavfsizlik tekshiruvi bilan (validatsiya + avtorizatsiya). Bu — xavfsiz mutation'ning amaliy ko'rinishi (server'da bo'lgani yetarli emas — kim chaqirayotganini tekshir).
Misol 5 — useOptimistic (darrov like — 2.10)
Maqsad: Like tugmasini darrov (optimistic) yangilash — useOptimistic bilan. Foydalanuvchi server javobini kutmasdan darrov natijani ko'radi (zamonaviy UX).
// app/posts/LikeButton.tsx — Client
"use client";
import { useOptimistic } from "react";
import { toggleLike } from "./actions";
export default function LikeButton({ postId, likes, liked }: { postId: string; likes: number; liked: boolean }) {
// Optimistic holat — darrov o'zgartirilgandek ko'rsat:
const [optimistic, setOptimistic] = useOptimistic(
{ likes, liked },
(state) => ({ likes: state.liked ? state.likes - 1 : state.likes + 1, liked: !state.liked })
);
async function handleLike() {
setOptimistic(null); // DARROV UI (server kutMASDAN — tez his)
await toggleLike(postId); // keyin server'da (tasdiqlash — xato bo'lsa React orqaga)
}
return (
<form action={handleLike}>
<button type="submit">
{optimistic.liked ? "" : ""} {optimistic.likes} {/* darrov o'zgaradi */}
</button>
</form>
);
}Bu kod nima qiladi: Bu — useOptimistic bilan darrov, silliq UX 2.10-bob. Muammo: foydalanuvchi like tugmasini bossa, Server Action (toggleLike) server'da ishlaydi (DB'ga yozadi — bir oz vaqt) — agar javobni kutsak, yurak/son darrov o'zgarmaydi (sekin, "lag" his). Yechim — useOptimistic: const [optimistic, setOptimistic] = useOptimistic({ likes, liked }, (state) => ({ likes: ..., liked: !state.liked })) — optimistic holat yaratadi (boshlang'ich { likes, liked } + o'zgarish funksiyasi — like bosilsa son va holatni darrov o'zgartiradi). handleLikeda: setOptimistic(null) (UI'ni darrov yangilaydi — yurak to'ladi, son oshadi — server kutmasdan — foydalanuvchi darrov ko'radi), keyin await toggleLike(postId) (server'da haqiqatan yozadi). Agar server muvaffaqiyatli — optimistic holat qoladi (real bo'ladi); agar xato (masalan tarmoq uzildi) — React avtomatik optimistic'ni orqaga qaytaradi (yurak avvalgi holatiga — son kamayadi). Natija: like bosish darrov his etiladi (yurak to'ladi, son oshadi — 0ms kutish), garchi server fonda ishlab tursa ham. Bu — zamonaviy ilova tajribasining belgisi (Instagram, Twitter like — darrov javob). useOptimisticsiz, foydalanuvchi har like'da server javobini kutardi (sekin, eski his). Bu — Server Actions + optimistic UI'ning eng silliq birikmasi (12.4: 2.10 TanStack Query optimistic'ning Next.js native versiyasi).
Misol 6 — redirect action'dan keyin (yangi post — 2.8)
Maqsad: Yangi post yaratilgach, o'sha post sahifasiga server'da yo'naltirish — redirect action ichida. Bu yaratish ko'rish oqimining standart yakuni.
// app/posts/actions.ts
"use server";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export async function createPost(formData: FormData) {
const post = await db.post.create({
data: {
title: formData.get("title") as string,
content: formData.get("content") as string,
},
});
revalidatePath("/posts"); // ro'yxatni yangila (yangi post ko'rinsin)
redirect(`/posts/${post.id}`); // yangi post sahifasiga YO'NALTIR (server'da)
}Bu kod nima qiladi: Bu — yaratish amalidan keyin yo'naltirish 2.8-bob. createPost Server Action: forma ma'lumotini olib, DB'ga yangi post yozadi (db.post.create — natijada post obyekti, post.id bilan). Keyin ikki qadam: (1) revalidatePath("/posts") — postlar ro'yxati sahifasini yangilaydi (yangi post ro'yxatda ko'rinsin — 2.8); (2) redirect(\/posts/${post.id}`)` — foydalanuvchini yangi yaratilgan post sahifasiga yo'naltiradi (next/navigation'dan — server'da yo'naltirish — 13.2: 2.6). Demak oqim: foydalanuvchi yangi post formasini to'ldiradi "Saqlash" bosadi Server Action DB'ga yozadi ro'yxat yangilanadi foydalanuvchi avtomatik yangi post sahifasiga o'tadi (/posts/123 — o'zi yaratgan post'ni ko'radi). Bu — "yaratish ko'rish" oqimining tabiiy yakuni (foydalanuvchi yaratganini darrov ko'radi). redirect Server Action ichida ishlaydi (server'da — komponent render emas), va u returndan keyin kod ishlamaydi (redirect oqimni to'xtatadi — 13.2: Misol 8). Eslatma: redirectni try/catch ichida ishlatma (u maxsus xato "tashlaydi" — Next.js uni ushlaydi; try/catch buni buzishi mumkin) — try/catchdan tashqarida chaqir. Bu — mutation'dan keyin foydalanuvchini to'g'ri joyga olib borishning usuli (yangi element, dashboard, muvaffaqiyat sahifasi).
Misol 7 — Sequential fetch (bog'liq ma'lumot — 2.2)
Maqsad: Bog'liq ma'lumotlar uchun ketma-ket (sequential) fetch — biri ikkinchisiga bog'liq bo'lganda (parallel mumkin emas). Bu waterfall'ning oqlangan holati.
// app/profile/[username]/page.tsx — Server Component
export default async function ProfilePage({ params }: { params: Promise<{ username: string }> }) {
const { username } = await params;
// 1. Avval foydalanuvchi (username bo'yicha):
const user = await db.user.findUnique({ where: { username } });
if (!user) notFound(); // topilmasa 404 (13.2)
// 2. Keyin uning postlari — user.id KERAK (bog'liq — sequential MAJBUR):
const posts = await db.post.findMany({ where: { authorId: user.id } });
// 3. Bu ikki bog'liq bo'lmagan narsani PARALLEL olsa bo'ladi (user.id bor):
const [followers, likes] = await Promise.all([
db.follow.count({ where: { followingId: user.id } }),
db.like.count({ where: { userId: user.id } }),
]);
return (
<div>
<h1>{user.name} (@{user.username})</h1>
<p>{followers} obunachi · {likes} like</p>
<PostList posts={posts} />
</div>
);
}Bu kod nima qiladi: Bu — bog'liq va bog'liq bo'lmagan fetch'larni to'g'ri aralashtirish 2.2-bob. Profil sahifasida ma'lumotlar o'rtasida bog'liqlik bor: (1) avval **user**ni olamiz (username bo'yicha — URL'dan); (2) keyin uning **posts**ini — bu yerda user.id kerak (where: { authorId: user.id }), shuning uchun postsni userdan oldin ola olmaymiz (sequential majbur — posts user.idga bog'liq — waterfall, lekin oqlangan). (3) Keyin followers va likes — bular ikkalasi user.idga bog'liq, lekin bir-biriga bog'liq emas, shuning uchun ularni parallel (Promise.all) olamiz (bir vaqtda — tez). Demak: user (posts, keyin parallel followers+likes). Bu Misol 1'dagi (hammasi parallel) holatdan farq qiladi — bu yerda bog'liqlik bor (posts/followers/likes hammasi user.idga muhtoj), shuning uchun avval user (sequential), keyin qolgani parallel. Qoida: bog'liqlik zanjirini ko'r — bog'liq bo'lganlar ketma-ket (majbur), bog'liq bo'lmaganlar parallel (Promise.all). notFound() 13.2-bob — user topilmasa 404 sahifa. Bu — real data fetching naqshi (ko'p sahifada ma'lumotlar qisman bog'liq) — bog'liqni sequential, mustaqilni parallel qilib, optimal tezlik. Misol 1 (to'liq parallel) va bu misol (aralash) — data fetching strategiyasining ikki uchi.
Misol 8 — Server Action TypeScript bilan to'liq (2.9, 2.11)
Maqsad: To'liq tipli, validatsiyali, xavfsiz Server Action — Zod + TypeScript + avtorizatsiya + xato boshqaruvi. Bu production-darajadagi Server Action'ning to'liq namunasi.
// app/comments/actions.ts
"use server";
import { z } from "zod";
import { getServerSession } from "@/lib/auth";
import { revalidatePath } from "next/cache";
// Natija tipi (useActionState uchun):
type ActionState = { error?: string; success?: boolean };
const CommentSchema = z.object({
postId: z.string().min(1),
text: z.string().min(1, "Izoh bo'sh bo'lmasin").max(500, "Izoh juda uzun"),
});
export async function addComment(prevState: ActionState, formData: FormData): Promise<ActionState> {
// 1. Avtorizatsiya 2.11-bob:
const session = await getServerSession();
if (!session) return { error: "Izoh qoldirish uchun tizimga kiring" };
// 2. Validatsiya (Zod — 2.9):
const result = CommentSchema.safeParse({
postId: formData.get("postId"),
text: formData.get("text"),
});
if (!result.success) {
return { error: result.error.issues[0].message }; // birinchi xato
}
// 3. Mutation (faqat valid + autentifikatsiyalgan):
try {
await db.comment.create({
data: { ...result.data, authorId: session.user.id },
});
revalidatePath(`/posts/${result.data.postId}`);
return { success: true };
} catch {
return { error: "Server xatosi — qayta urinib ko'ring" }; // xato boshqaruvi
}
}Bu kod nima qiladi: Bu — production-darajadagi, to'liq Server Action (xavfsizlik + validatsiya + tip + xato boshqaruvi — barcha best practice bir joyda — 2.9, 2.11). To'rt qatlam: (1) Tip — ActionState (Server Action natijasi tipi — useActionState bilan ishlash uchun — TypeScript xavfsizligi, 11.14); (2) Avtorizatsiya — getServerSession() (kirganmi? — kirmagan bo'lsa xato qaytar, DB'ga yetib bormaydi — 2.11); (3) Validatsiya — Zod (CommentSchema.safeParse — izoh bo'sh emasmi, juda uzun emasmi — server'da ishonchli tekshiruv — 2.9, xato bo'lsa birinchi xabarni qaytar); (4) Mutation + xato boshqaruvi — try/catch ichida DB'ga yozish (authorId: session.user.id — izoh egasi autentifikatsiyalgan foydalanuvchi, forma'dan emas — xavfsiz; agar authorIdni forma'dan olsak, foydalanuvchi boshqa odam nomidan izoh qoldira olardi — xavfsizlik teshigi), revalidatePath (izoh ko'rinsin), va catch bilan server xatosini ushlash (DB ishlamasa — foydalanuvchiga tushunarli xabar). Har qadam xatoda return { error } (forma'ga qaytadi — useActionState ko'rsatadi — 2.6). Demak bu Server Action: tizimga kirmaganni to'xtatadi, noto'g'ri ma'lumotni rad etadi, faqat valid + autentifikatsiyalgan izoh'ni (to'g'ri egalik bilan) DB'ga yozadi, va server xatosini nafis boshqaradi. Bu — har production Server Action'ning strukturasi (avtorizatsiya validatsiya mutation revalidate xato boshqaruvi). Server'da bo'lgani yetarli emas — har qatlam himoya kerak (14-QISM xavfsizlik bilan chuqurroq).
Misol 9 — DB so'rovi cache() bilan dedup (2.3)
Maqsad: DB so'rovini bir render'da dedup qilish (bir xil so'rov bir marta) — React cache() bilan. Bu props drilling'siz, toza arxitektura beradi.
// app/lib/data.ts — DB so'rovini cache() bilan o'rab (dedup — 2.3):
import { cache } from "react";
import { db } from "@/lib/db";
// cache() — bir render'da bir xil chaqiruv BIR MARTA (dedup):
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// Endi getUser(id)ni bir necha joyda chaqirsa bo'ladi — bir marta DB'ga boradi:
// app/layout.tsx (Server):
// const user = await getUser(userId); ┐
// app/page.tsx (Server): ├─ bir xil id 1 ta DB so'rov (cache dedup)
// const user = await getUser(userId); ┘
// app/components/Avatar.tsx (Server):
// const user = await getUser(userId); yana — baribir 1 ta DB so'rovBu kod nima qiladi: Bu — DB so'rovini dedup qilish (request memoization — 2.3 — fetch uchun avtomatik, DB uchun cache() bilan qo'lda). Muammo: bir render'da bir xil DB so'rovi bir necha joyda kerak bo'lishi mumkin (layout.tsxda user, page.tsxda user, Avatar.tsxda user — har biri getUser(id) chaqiradi). Bir xil so'rov 3 marta DB'ga borsa — isrof. fetch uchun Next.js avtomatik dedup qiladi 2.3-bob, lekin DB so'rovi uchun React'ning cache() funksiyasini ishlatasiz: export const getUser = cache(async (id) => db.user.findUnique({ where: { id } })) — cache() bilan o'ralgan funksiya bir render davomida bir xil argument (id) bilan chaqirilsa — bir marta bajariladi (qolgani keshdan — xotirada). Demak getUser(userId)ni layout, page, Avatar — uchalasi chaqirsa ham — DB'ga faqat 1 marta boradi (cache dedup). Bu nima beradi: props drilling kerak emas (12.1: 2.1) — userni layout'dan page'ga, Avatar'ga props orqali uzatish shart emas; har komponent o'ziga kerak joyda getUser(userId) chaqiraversin (cache takrorni yo'qotadi). Natijada toza arxitektura: har komponent o'z ma'lumotini mustaqil oladi (boshqa komponentga bog'liq emas), lekin DB isrof bo'lmaydi (dedup). cache() — React'ning Server Components uchun memoizatsiyasi (useMemo'ning — 11.6 — server versiyasi, lekin so'rovlar uchun). Bu — Server Components arxitekturasining (har komponent o'z ma'lumotini, dedup bilan) toza naqshi.
Misol 10 — To'liq CRUD Server Actions (2.4-2.9)
Maqsad: Bir mavzu uchun to'liq CRUD (Create, Read, Update, Delete)ni Server Actions bilan ko'rsatish — backend API'siz to'liq ma'lumot boshqaruvi. Bu Server Actions'ning to'liq qamrovini ko'rsatadi.
// app/notes/actions.ts — to'liq CRUD (barcha mutation bir faylda)
"use server";
import { revalidatePath } from "next/cache";
import { getServerSession } from "@/lib/auth";
async function requireAuth() {
const session = await getServerSession();
if (!session) throw new Error("Tizimga kiring");
return session; // qayta ishlatiladigan avtorizatsiya yordamchisi
}
// CREATE:
export async function createNote(formData: FormData) {
const session = await requireAuth();
await db.note.create({ data: { text: formData.get("text") as string, userId: session.user.id } });
revalidatePath("/notes");
}
// UPDATE:
export async function updateNote(id: string, formData: FormData) {
const session = await requireAuth();
const note = await db.note.findUnique({ where: { id } });
if (note?.userId !== session.user.id) throw new Error("Ruxsat yo'q"); // egalik
await db.note.update({ where: { id }, data: { text: formData.get("text") as string } });
revalidatePath("/notes");
}
// DELETE:
export async function deleteNote(id: string) {
const session = await requireAuth();
const note = await db.note.findUnique({ where: { id } });
if (note?.userId !== session.user.id) throw new Error("Ruxsat yo'q");
await db.note.delete({ where: { id } });
revalidatePath("/notes");
}
// READ — Server Component'da to'g'ridan (Server Action emas):
// const notes = await db.note.findMany({ where: { userId } }); // 2.1Bu kod nima qiladi: Bu — to'liq CRUD (Create, Read, Update, Delete) Server Actions bilan (backend API'siz to'liq ma'lumot boshqaruvi — Server Actions'ning qamrovi). To'rt amal: (1) CREATE (createNote) — yangi note yaratadi (autentifikatsiya tekshir, userId session'dan — xavfsiz, revalidate); (2) UPDATE (updateNote) — note'ni yangilaydi (autentifikatsiya + egalik tekshir — note.userId !== session.user.id — faqat o'z note'ini, revalidate); (3) DELETE (deleteNote) — note'ni o'chiradi (xuddi shunday egalik tekshir, revalidate); (4) READ — bu Server Action emas — Server Component'da to'g'ridan db.note.findMany (2.1 — read uchun Server Action kerak emas, faqat write/mutation uchun). Muhim naqsh: requireAuth yordamchi funksiya (autentifikatsiya tekshiruvini qayta ishlatish — har action'da takrorlamasdan — DRY — 11.7 custom hook g'oyasi, lekin server'da). Har mutation: avtorizatsiya (requireAuth) egalik tekshir (update/delete — boshqaniki emasligi) DB amali revalidatePath (UI yangilansin). Demak butun CRUD — bir actions.ts faylda (tartibli — 2.11), backend API (route.ts, Express) yo'q — Server Actions hammasini qoplaydi. Bu Express/NestJS backend (7, 8-QISM)ning to'liq CRUD'iga teng, lekin: API endpointlar yo'q, client fetch yo'q, JSON serializatsiya yo'q — to'g'ridan funksiyalar. Server Actions — full-stack CRUD'ni Next.js ichida (alohida backend kerak bo'lmasligi mumkin — kichik-o'rta loyihada). Bu — Next.js'ning "full-stack framework" da'vosining amaliy isboti (frontend + backend + DB — bir joyda, toza).
5. To'g'ri va noto'g'ri holatlar
1) Bog'liq bo'lmagan fetch
ketma-ket await (waterfall — sekin — 2.2)
Promise.all (parallel — tez — Misol 1)2) Mutation'dan keyin
revalidate yo'q (DB o'zgardi, ekran eski — 2.8)
revalidatePath/Tag (yangi ma'lumot ko'rinsin — Misol 2)3) Server Action xavfsizligi
validatsiya/avtorizatsiya yo'q (har kim har narsa — 2.11)
Zod + session tekshir (har Server Action — Misol 4, 8)4) Egalik (authorId)
authorId forma'dan (foydalanuvchi boshqa nomidan — xavfli)
authorId session'dan (server — ishonchli — Misol 8)5) redirect
redirect try/catch ichida (buziladi — 2.8)
try/catch'dan tashqarida (Misol 6)6) Read uchun Server Action
ma'lumot OLISH uchun Server Action (keraksiz)
Server Component'da to'g'ridan await (Server Action — faqat mutation — 2.1)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Server Action o'zgartirdi, lekin ekran yangilanmadi
Sababi: revalidatePath/Tag yo'q (kesh eski — 2.8). Yechimi: mutation'dan keyin revalidatePath("/yo'l") (Misol 2).
Xato 2 — functions cannot be passed to Client Components (Server Action)
Sababi: Server Action noto'g'ri import yoki "use server" yo'q. Yechimi: "use server" faylda; to'g'ri import 2.4-bob.
Xato 3 — Sahifa sekin (waterfall)
Sababi: bog'liq bo'lmagan fetch ketma-ket 2.2-bob. Yechimi: Promise.all (parallel — Misol 1).
Xato 4 — redirect ishlamaydi (xato beradi)
Sababi: redirect try/catch ichida 2.8-bob. Yechimi: try/catch'dan tashqarida (Misol 6).
Xato 5 — Server Action xavfsiz emas (har kim chaqiradi)
Sababi: validatsiya/avtorizatsiya yo'q 2.11-bob. Yechimi: Zod + session har Server Action'da (Misol 8).
Xato 6 — useActionState xato ko'rsatmaydi
Sababi: Server Action prevState argumentini olmaydi yoki return yo'q 2.6-bob. Yechimi: (prevState, formData) + return { error } (Misol 3).
Xato 7 — FormData qiymati undefined
Sababi: input'da name yo'q (formData.get(name) — 2.5). Yechimi: har input'ga name (formData.get shu name'ni oladi).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Server Components 13.3-bob: data fetching — Server Component'ning asosiy ishi.
- Rendering 13.4-bob: fetch kesh (Data Cache) — render strategiyani belgilaydi; revalidate — ISR (2.3a).
- Kesh nazariyasi (07-QISM): Data Cache va Request Memoization qatlamlari (2.3, 2.3a).
- Suspense/error boundary 11.13-bob: streaming (Suspense) va error.tsx (2.3b, 2.3c).
- DB dizayn (6-QISM): N+1 muammosi va JOIN/include (2.3d).
- Route handlers 13.6-bob: Server Actions vs API routes (qachon qaysi).
- Forms 11.10-bob: Server Actions + RHF/Zod (client UX + server xavfsizlik).
- Auth 13.9-bob: Server Action'da session (avtorizatsiya).
- TanStack Query 12.4-bob: useOptimistic — optimistic'ning native versiyasi.
- DB/ORM 6.12-bob: Server Action'da Prisma/Drizzle (to'g'ridan DB).
- Xavfsizlik (14): Server Action validatsiya/avtorizatsiya (OWASP).
8. Eng yaxshi amaliyotlar (best practices)
- Parallel fetch (Promise.all — waterfall'dan saqlanish — 2.2, Misol 1).
- Server Action mutation uchun (read — Server Component await — 2.1).
- "use server" alohida faylda (actions.ts — tartibli — 2.11).
- Validatsiya (Zod) har Server Action'da (server — ishonchli — 2.9).
- Avtorizatsiya (session) har Server Action'da (ochiq endpoint — 2.11).
- authorId session'dan (forma'dan emas — xavfsiz — Misol 8).
- revalidatePath/Tag mutation'dan keyin (UI yangilansin — 2.8).
- useActionState (xato/pending) (forma UX — 2.6).
- useOptimistic (darrov UI) (silliq — 2.10).
- cache() DB dedup'ga (props drilling'siz — 2.3, Misol 9).
- fetch kesh opsiyasini ongli tanlash (no-store/force-cache/revalidate — 2.3a).
- Sekin qismni Suspense'ga o'rash (streaming — sahifa tez ko'rinsin — 2.3b).
- error.tsx bilan xato chegarasi (ilova qulamasin — 2.3c).
- N+1'dan saqlanish (JOIN/include — bir so'rov — 2.3d).
9. Amaliy loyiha: "Full-Stack Izohlar (Server Actions)"
Server Actions bilan to'liq CRUD — backend API'siz — mustahkamlash.
Maqsad
Post + izoh tizimi: Server Component'da o'qish, Server Actions bilan yozish (CRUD), validatsiya, optimistic.
Talablar (requirements)
- Read: Server Component'da post + izohlar (parallel — Misol 1, 7).
- Create: izoh qo'shish (Server Action + forma — Misol 2).
- Validatsiya: Zod (izoh bo'sh emas — server'da — Misol 3, 8).
- Avtorizatsiya: session tekshir (kirgan — Misol 4, 8).
- Egalik: o'z izohini tahrirlash/o'chirish (authorId — Misol 8, 10).
- useActionState: xato + pending (Misol 3).
- useOptimistic: izoh darrov ko'rinsin (Misol 5).
- revalidate: mutation'dan keyin (Misol 2, 6).
- redirect: post yaratilgach post'ga (Misol 6).
- cache(): user so'rovi dedup (Misol 9).
Maslahatlar (hint)
- Read — Server Component await; mutation — Server Action (Xato 6 — read uchun action emas).
- Parallel fetch (Promise.all — Xato 3).
- Validatsiya + avtorizatsiya har Server Action (Xato 5).
- authorId session'dan (forma'dan emas — Misol 8).
- revalidate'ni unutma (Xato 1).
- redirect try/catch'dan tashqarida (Xato 4).
"Tayyor" mezonlari (acceptance criteria)
- Read (Server Component, parallel).
- Create/Update/Delete (Server Actions).
- Validatsiya (Zod, server).
- Avtorizatsiya + egalik.
- useActionState (xato/pending).
- useOptimistic (darrov).
- revalidate (mutation'dan keyin).
- Backend API yo'q (faqat Server Actions).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda Next.js'da ma'lumot olish va o'zgartirishni chuqur o'rgandik:
- Server Component'da fetch/DB 2.1-bob; parallel vs waterfall (Promise.all — 2.2); request memoization (dedup — 2.3).
- fetch kesh opsiyalari va Data Cache/ISR (2.3a); streaming va Suspense (2.3b); xato boshqaruvi (error.tsx) (2.3c); N+1 va ma'lumot qayerda olinadi (2.3d).
- Server Actions ("use server" — 2.4); forma (progressive enhancement — 2.5); useActionState/useFormStatus 2.6-bob; hodisa 2.7-bob.
- revalidation/redirect 2.8-bob; validatsiya (Zod — 2.9); useOptimistic 2.10-bob; xavfsizlik 2.11-bob.
Endi siz Next.js'da full-stack ishlay olasiz: ma'lumotni server'da olish (tez, xavfsiz, useEffect'siz) va Server Actions bilan o'zgartirish (backend API'siz, validatsiyali, xavfsiz, optimistic). Bu — Next.js'ning "full-stack framework" kuchining yuragi.
Keyingi bob — 13.6-bob: Route Handlers va Middleware. Server Actions'ni bildik; endi Next.js'ning API qatlamini (Route Handlers — route.ts) va Middlewareni ko'ramiz. Route Handlers (REST API endpoint — GET/POST/PUT/DELETE — tashqi mijoz, webhook, mobil app uchun), Server Actions vs Route Handlers (qachon qaysi), Middleware (so'rovni ushlab — auth, redirect, A/B, til — sahifaga yetishdan oldin), CORS, headers, cookies, va edge runtime. Bu — Next.js backend imkoniyatlarining to'liq qismi.
Foydalanilgan rasmiy/ishonchli manbalar
- Next.js rasmiy hujjati (nextjs.org/docs) — "Data Fetching, Caching, and Revalidating", "Server Actions and Mutations", "Forms and Mutations"
- Next.js rasmiy hujjati — "Caching in Next.js" (Data Cache va Request Memoization qatlamlari),
fetchopsiyalari (cache,next.revalidate,next.tags) - Next.js rasmiy hujjati — "Loading UI and Streaming" (
loading.tsx, Suspense), "Error Handling" (error.tsx) - React rasmiy hujjati (react.dev) —
useActionState,useOptimistic,useFormStatus,cache,<Suspense>, "use server", "use client" - Next.js rasmiy hujjati —
revalidatePath/revalidateTag,redirect,notFound, parallel/sequential data fetching, request memoization - Zod hujjati (zod.dev) — schema validatsiya (Server Action'da server tomonda)
- Prisma hujjati (prisma.io/docs) —
includebilan bog'liq ma'lumot (N+1'dan saqlanish — 2.3d)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!