Next.js freymvorki haqida
Next.js, Vercel tomonidan ishlab chiqilgan mashhur React freymvork’i bo’lib, server tomonidan render qilinadigan (SSR) va statik veb-saytlar yaratishda keng imkoniyatlar va soddalikni taqdim etishi bilan tanilgan. Bu “sozlamalar o’rniga konvensiya” (convention over configuration) tamoyiliga amal qiladi, ya’ni loyihani boshlash uchun zarur bo’lgan ortiqcha sozlamalar va qarorlar qilishni kamaytiradi. Next.js 13 chiqarilishi bilan muhim yangiliklardan biri Next.js App Router funksiyasining qo’shilishi bo’ldi.
Asosiy Next.js ilovasi
Yangi Next.js ilovasini yaratishni tushunish uchun, quyidagi buyruqni bajarish orqali boshlaymiz:
npm create next-app@14
Bu bizga bir nechta savollar beradi, lekin yakunda asosiy Next.js loyihasiga ega bo’lamiz. Atrofga nazar solib, ichidagi tarkibni ko’rib chiqamiz. Loyihani ko’rib, app kategoriyasida page.tsx, layout.tsx, error.tsx va loading.tsx fayllari borligini sezamiz.
Bir narsani darhol payqashimiz mumkinki, Next.js Remix singari server konfiguratsiyalarini oshkor etmaydi, aksincha, ko’plab murakkabliklarni yashiradi va bu orqali dasturchilarni o’z ilovalarini yaxshiroq yaratishga e’tibor qaratishga yo’naltiradi. Bu turli xil freymvork’larning bir xil muammolarni turli yondashuvlar va falsafalar orqali hal qilishini ko’rsatuvchi yaxshi misol hisoblanadi.
Endi Next.js’ni uch asosiy xususiyat — server rendering, routing va data fetching nuqtayi nazaridan ko’rib chiqamiz.
Server rendering
Next.js server tomonidan render qilish imkoniyatini taqdim etish bilan birga, server-first tamoyiliga ham amal qiladi. Next.js’dagi har bir sahifa va komponent server komponenti hisoblanadi. Server komponentlariga keyingi bobda batafsilroq to’xtalamiz, lekin hozircha ularni faqat serverda render qilinadigan komponentlar deb tushunish kifoya. Bu tushunish darajasi hozircha yaxshi, chunki asosiy e’tibor server komponentlariga emas, Next.js’ga qaratilgan.
Next.js’da bu kontekst nimani anglatadi? Aslini olganda, biz yozgan kodimizning barcha qismi faqat serverda bajarilishini hisobga olishimiz kerak. Buni o’zgartirish uchun faqat kerakli route yoki komponent ustiga "use client"
direktivasini qo’shish kerak bo’ladi. Bu direktiva bo’lmasa, barcha kod server kodi deb qabul qilinadi.
Biroq, Next.js ham static-first yondashuvini qo’llaydi: qurish(build) vaqtida barcha server komponentlari imkon qadar statik kontentga aylantiriladi va keyin serverga joylanadi (deploy). Bu server-first va static-first kombinatsiyasi Next.js’ni juda kuchli qiladi va ishlash samaradorligini sezilarli darajada ustun bo’lishini ta’minlaydi, chunki statik kontent eng tez yetib boradigan kontent turi hisoblanadi - unda runtime yoki server tomoni jarayoni talab qilinmaydi, faqat HTML tekstini o’z ichiga oladi. Statikdan keyingi bosqich esa serverda render qilingan kontent bo’lib, uni optimizatsiya qilish va keshlash mumkin, lekin baribir serverdan foydalangan holda render qilishni talab qiladi. Oxirgi bosqich esa sahifaning interaktiv qismlarini hidratsiya qilish orqali klient tomoni orqali render qilinadi.
Bu yondashuv bilan Next.js foydalanuvchilarga kichikroq JavaScript to’plam paketlarini yuborishga imkon beradi, bunda kontentning asosiy qismi statik va serverda render qilingan bir markup’larning aralashmasidan iborat bo’ladi. Faqat sahifalar emas, balki komponentlar ham serverda render qilinishi imkoniyati mavjudligi Next.js’ning kuchli tomonlaridan biri hisoblanadi, bu esa ma’lumotlarni olish va render qilish uchun kuchli imkoniyatlar yaratadi.
Keyingi bosqichlarda ushbu imkoniyatlardan qanday foydalanish mumkinligini ko’rib chiqishdan oldin, Next.js qanday qilib route’larni boshqarishini o’rganamiz.
Routing
Yangi Next.js loyihamizda biz app katalogini ko’ramiz, unda layout.tsx va page.tsx mavjud. Next.js quyidagi pattern’ga amal qiladi: foydalanuvchilar brauzerlarida ko’radigan yo’l - ya’ni sahifa URL’i - katalog nomiga mos keladi, bunda app ildiz yo’li (/) bilan tenglashtiriladi, va uning ostidagi har bir katalog subyo’lga aylanadi.
Buni yaxshiroq tushunish uchun cheese nomli katalog yaratib, unga page.tsx faylini qo’shamiz. Agar ./app ostidagi katalogda page.tsx fayli bo’lsa, u katalog route’iga aylanadi. Keling, ./app/cheese/page.tsx’ga kontent qo’shamiz:
export default function CheesePage() {
return <h1>This might sound cheesy, but I think you're really grate!</h1>;
}
Endi, agar ishlab chiqish serverini ishga tushirsak va /cheese yo’liga o’tsak, qiziq sarlavha bilan sahifani ko’ramiz. Shuni ta’kidlash joizki, Next.js ham Remix singari umumiy layout’lar tushunchasiga ega. Masalan, ./app/layout.tsx faylida layout komponentini belgilab, uni har bir sahifada render qilishimiz mumkin. Keyin, ./app/cheese/layout.tsx fayli esa /cheese route’iga tegishli har bir sahifada render qilinadi. Layout’lar odatda bir nechta sahifalar orasida umumiy bo’lgan qismlarni, masalan, header yoki footer’lar kabi, ko’p ishlatiladigan elementlarni ifodalaydi.
Demak, Next.js route’larni mana shunday boshqaradi. Bu Remix va fayl tizimi asosidagi route’lashning o’zimiz yaratgan usulimizga o’xshaydi, ammo kichik farqlari bor: bitta fayl emas, balki butun katalog sahifa bo’ladi va sahifaning o’zi doim page.tsx deb atalishi kerak. Bundan tashqari, uslub juda o’xshash.
Endi esa ma’lumotlarni olish haqida gaplashamiz.
Ma’lumotlarni olish (data fetching)
Next.js’da har bir komponent server komponent bo’lgani uchun, har bir komponent asinxron bo’lib, ma’lumotlarni await
orqali olish imkoniyatiga ega. Oldingi Remix misolimizdagi kabi pishloqlarni Next.js’da olishni sinab ko’ramiz:
export default async function CheesePage() {
const cheeses = await fetch("https://api.com/get-cheeses").then((r) => r.json());
return (
<div>
<ul>
<h1>This might sound cheesy, but I think you're really grate!</h1>
{cheeses.map((cheese) => (
<li key={cheese.id}>{cheese.name}</li>
))}
</ul>
</div>
);
}
Buni ko’rib hayratlangan bo’lsangiz ajab emas. Ha, React muhandislari yillar davomida orzu qilgan sintaksis bu – ma’lumotlarni olish va ulardan foydalanishning o’ziga xos va tabiiy usuli. Bu CheesePage
server komponent bo’lgani uchun buning iloji bor: u klient tomonida yuklanmaydi, balki serverda render qilinadi. Bu esa bizga ma’lumotlarni await
orqali olish va ularni to’g’ridan-to’g’ri sahifada ko’rsatish imkonini beradi.
Barcha komponentlar server komponent bo’lganligi sababli, ma’lumotlarni sahifa darajasida olish o’rniga, agar istasak, komponent darajasida ham olish imkoniyatiga egamiz. Misol uchun, bu sahifani kichikroq komponentlarga ajratib, CheeseList
nomli qayta foydalanish mumkin bo’lgan komponent sifatida yaratib, uni bu sahifada ishlatishimiz va boshqa joylarda ham ishlatishimiz mumkin.
Sahifaning ko’rinishi
Bizning sahifamiz quyidagicha ko’rinishga ega bo’ladi:
import { CheeseList } from "./CheeseList";
export default function CheesePage() {
return (
<div>
<h1>This might sound cheesy, but I think you're really grate!</h1>
<CheeseList />
</div>
);
}
CheeseList
komponenti esa quyidagicha bo’ladi:
export async function CheeseList() {
const cheeses = await fetch("https://api.com/get-cheeses").then((r) => r.json());
return (
<ul>
{cheeses.map((cheese) => (
<li key={cheese.id}>{cheese.name}</li>
))}
</ul>
);
}
Bu yondashuvning haqiqiy kuchi shundaki, biz ma’lumotlarni sahifa emas, balki komponent darajasida olishimiz va ularni sahifaga render qilishimiz mumkin. Endi sahifa darajasida loader
, getData
, getServerSideProps
, getStaticProps
kabi funksiyalarni eksport qilishning hojati yo’q. Shunchaki ma’lumotlarni komponent darajasida olamiz va sahifaga render qilamiz.
Ma’lumotlar qanday ishlatiladi
Next.js bu ma’lumotlardan bizning sahifamizni dastlab statik holatda yuklash uchun foydalanadi va keyingi yuklashlarda serverda render qiladi. Shuningdek, Next.js turli keshlash va deduplikatsiya (takrorlanishni bartaraf qilish) mexanizmlariga ega bo’lib, bu ma’lumotlar yaxlitligi va sahifa samaradorligini ta’minlaydi.
Oxirgi qadam sifatida, Next.js qanday qilib ma’lumotlar o’zgarishini (mutatsiyalarini) boshqarishini ko’rib chiqamiz.
Ma’lumotlarni mutatsiya qilish (mutating data)
Next.js serverda ishlaydigan server actions (server harakatlari) tushunchasiga ega. Bu funksiyalar serverda ishlaydi va ular forma yuborilganda, foydalanuvchi tugmani bosganda yoki sahifaga o’tganda chaqiriladi. Bu funksiyalar klient to’plam paketiga kiritilmagan va faqat serverda ishlaydi.
Server harakatlari (server actions)
Keling, Remix misolidagidek, ro’yxatga pishloq qo’shishni ko’rib chiqaylik. Buning uchun sahifamizga forma qo’shamiz:
import { CheeseList } from "./CheeseList";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export default function CheesePage() {
return (
<div>
<h1>This might sound cheesy, but I think you're really grate!</h1>
<CheeseList />
<form
action={async (formData) => {
"use server";
await fetch("https://api.com/add-cheese", {
method: "POST",
body: JSON.stringify({
name: formData.get("cheese"),
}),
});
revalidatePath("/cheese");
return redirect("/cheese");
}}
method="post"
>
<input type="text" name="cheese" />
<button type="submit">Pishloq qo'shish</button>
</form>
</div>
);
}
Bu yerda Remi’xga o’xshash standart HTML shakldan foydalanmoqdamiz, faqat bu safar action
atributi funksiya sifatida ishlamoqda. Bu funksiya server action bo’lib, forma yuborilganda chaqiriladi. Bu funksiya klient to’plam paketi(client bundle)ga kiritilmagan va uning o’rniga serverda ishlaydi. Bu yuqoridagi "use server"
direktivasi orqali belgilanadi.
Bu funksiyani istalgan joyga ko’chirishimiz mumkin, jumladan uni server komponenti tanasiga ham joylashimiz mumkin, quyidagi kabi:
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export default function CheesePage() {
async function addCheese(formData) {
"use server";
await fetch("https://api.com/add-cheese", {
method: "POST",
body: JSON.stringify({
name: formData.get("cheese"),
}),
});
revalidatePath("/cheese");
return redirect("/cheese");
}
return (
<div>
<h1>This might sound cheesy, but I think you're really grate!</h1>
<CheeseList />
<form action={addCheese} method="post">
<input type="text" name="cheese" />
<button type="submit">Pishloq qo'shish</button>
</form>
</div>
);
}
yoki hattoki alohida modulga ham ko’chirishimiz mumkin, quyidagi kabi:
import { addCheeseAction } from "./addCheeseAction";
export default function CheesePage() {
return (
<div>
<h1>This might sound cheesy, but I think you're really grate!</h1>
<CheeseList />
<form action={addCheese} method="post">
<input type="text" name="cheese" />
<button type="submit">Pishloq qo'shish</button>
</form>
</div>
);
}
Bu holda, addCheeseAction
o’z faylida quyidagi tarzda ko’rinadi:
"use server";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export async function addCheeseAction(formData) {
await fetch("https://api.com/add-cheese", {
method: "POST",
body: JSON.stringify({
name: formData.get("cheese"),
}),
});
revalidatePath("/cheese");
return redirect("/cheese");
}
Interaktivlik muammosi
Bu yerda o’ziga xos bir muammo bor, chunki barcha komponentlari klient komponentlari bo’lgan Remix’dan farqli o’laroq, server komponentlari umuman interaktivlikni qo’llab-quvvatlamaydi, chunki ular klient to’plam paketiga kiritilmaydi va brauzer tomonidan yuklanmaydi; shuning uchun onClick
ishlov beruvchilar uni hech qachon foydalanuvchilarga yetkaza olmaydi. Ushbu muammoni hal qilish uchun, Next.js klient komponentlari tushunchasini yaratgan, bu komponentlar klient to’plam paketiga kiritiladi va brauzer tomonidan yuklanadi. Bu komponentlar server komponentlari emas va shu sababli asinxron bo’lishi yoki server harakatlariga ega bo’la olmaydi.
Klient komponenti sifatida ishlatish
Pishloq qo’shishni kiritamiz, lekin bu safar server va klient komponentlarini aralashtirgan holda. Bu, shuningdek, formani yuborishga javoban darhol spinner yoki shunga o’xshash narsalar bilan javob qaytarishga yordam beradi. Buning uchun biz yangi komponent, ./app/AddCheeseForm.tsxni yaratamiz:
"use client";
import { addCheeseAction } from "./addCheeseAction";
export function AddCheeseForm() {
return (
<form action={addCheeseAction} method="post">
<input type="text" name="cheese" />
<button type="submit">Pishloq qo'shish</button>
</form>
);
}
useFormStatus hook
Endi bu klient komponentiga aylanganligi sababli, biz interaktiv narsalarni bajara olamiz — masalan, forma holatidagi o’zgarishlarga javob berish. AddCheeseForm
ni bunga mos ravishda yangilaymiz:
"use client";
import { addCheeseAction } from "./addCheeseAction";
import { useFormStatus } from "react-dom";
export function AddCheeseForm() {
const { pending } = useFormStatus();
return (
<form action={addCheeseAction} method="post">
<input disabled={pending} type="text" name="cheese" />
<button type="submit" disabled={pending}>
{pending ? "Yuklanmoqda..." : "Pishloq qo'shish"}
</button>
</form>
);
}
AddCheeseForm
bizning klient komponentimiz bo’lgani uchun, biz useFormStatus
dan foydalanib, forma statusini olishimiz mumkin. Bu React tomonidan taqdim etilgan hook hisoblanadi. Ushbu hook pending
xususiyatiga ega bo’lgan obyektni qaytaradi, bu forma yuborilayotganida true
, va yuborilmayotganida false
qiymatini qaytaradi. Biz buni formani yuborilayotganda o’chirish va yuklanish indikatorini ko’rsatish uchun ishlatishimiz mumkin.
Endi biz ushbu formani o’z sahifamizda, server komponenti sifatida ishlatishimiz mumkin, quyidagicha:
import { CheeseList } from "./CheeseList";
import { AddCheeseForm } from "./AddCheeseForm";
export default function CheesePage() {
return (
<div>
<h1>This might sound cheesy, but I think you're really grate!</h1>
<CheeseList />
<AddCheeseForm />
</div>
);
}
Server va klient komponentlarini birga ishlatish
Natijada biz server va klient komponentlarini aralashtirdik. CheesePage
va CheeseList
server komponentlari, va AddCheeseForm
klient komponentidir. Ikkala komponent ham qayta foydalanishga yaroqli va dasturimizning boshqa joylarida ham ishlatilishi mumkin. Klient va server komponentlari bilan bog’liq ba’zi qoida va mulohazalar mavjud, lekin biz bularni keyingi bobda o’rganamiz.
Hozircha, agar keng ko’lamda qarasak, Next.js Remix va fayl tizimiga asoslangan route’lash, ma’lumotlarni olish va ma’lumotlarni mutatsiya qilishni o’zimiz implementatsiya qilish kabi muammolarni hal qilishini ko’ramiz. Bu biroz boshqacha usulda amalga oshiriladi, ammo asosiy mexanizmlar bir-biriga o’xshashdir.
Ideal holda, ushbu ikkala freymvorkni o’rganish orqali biz nima uchun freymvorklarga murojaat qilishimiz kerakligini, ularning qanday muammolarni hal qilishini va bizning foydamiz uchun qanday yechim berishini tushunishimiz mumkin.
Kelin, freymvorkni qanday tanlash haqida gapirish bilan bo’limni yakunlaymiz.