Next.js 15+ da Cache Components - bitta sahifada statik, keshlangan va dinamik kontentni birlashtirish, use cache, cacheLife, cacheTag va revalidatsiya
Next.js App Router - Cache Components (Partial Prerendering)
Bu Next.js ning eng yangi va eng kuchli xususiyatlaridan biri. Bitta sahifada statik, keshlangan va dinamik kontentni birga ishlatish imkonini beradi.
Muammo: Avvalgi yondashuv
Oddiy server-rendered ilovalarda sahifa ikki xil bo'lishi mumkin edi:
Variant 1: STATIK sahifa - Tez, lekin eskirgan. Build vaqtida yaratiladi, yangi ma'lumot ko'rsatmaydi.
Variant 2: DINAMIK sahifa - Yangi ma'lumot, lekin sekin. Har safar serverda renderlanadi, foydalanuvchi kutadi.
Tanlash kerak edi - TEZLIK yoki YANGILIK? Ikkalasi birga bo'lmasdi.
Yechim: Cache Components
Cache Components bilan ikkalasini birga olasiz:
┌──────────────────────────────────────────┐
│ Header, Nav, Footer → STATIK (darhol) │
│ Blog postlar, katalog → KESHLANGAN │
│ Foydalanuvchi ma'lumot → DINAMIK (stream) │
│ │
│ Hammasi BITTA sahifada! │
└──────────────────────────────────────────┘
Brauzer darhol statik shell (qobiq) oladi, keyin dinamik qismlar tayyor bo'lganda UI yangilanadi.
Yoqish
Bu opt-in xususiyat. next.config.ts da yoqish kerak:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;
3 xil kontent turi
Cache Components yoqilganda sahifadagi har bir komponent 3 xil bo'lishi mumkin:
| Tur | Qanday belgilanadi | Static shell da? | Misol |
|---|---|---|---|
| Statik | Avtomatik | Ha - darhol | Header, nav, footer |
| Keshlangan |
'use cache' + cacheLife()
|
Ha - darhol | Blog postlar, katalog |
| Dinamik |
<Suspense> bilan o'raladi |
Yo'q - stream bo'ladi | cookies, shaxsiy ma'lumot |
1. Avtomatik statik kontent
Sinxron operatsiyalar, import lar va oddiy hisob-kitoblar avtomatik statik shell ga qo'shiladi. Hech qanday maxsus belgi kerak emas:
// app/page.tsx
import fs from 'node:fs';
export default async function Page() {
// Sinxron fayl o'qish
const content = fs.readFileSync('./config.json', 'utf-8');
// Module import
const constants = await import('./constants.json');
// Oddiy hisob-kitob
const processed = JSON.parse(content).items.map(
item => item.value * 2
);
return (
<div>
<h1>{constants.appName}</h1>
<ul>
{processed.map((value, i) => (
<li key={i}>{value}</li>
))}
</ul>
</div>
);
}
// Butun sahifa STATIK - darhol ko'rsatiladi
Next.js o'zi aniqlaydi - "bu komponent prerendering da tugaydi" va uni statik shell ga qo'shadi.
Tekshirish: Build natijasini ko'ring yoki brauzerda "View Page Source" qiling - statik kontent HTML da ko'rinadi.
2. Dinamik kontent - <Suspense> bilan
Network so'rovlar, DB querylar, asinxron operatsiyalar prerendering da bajarilmaydi. Ularni <Suspense> bilan o'rab, fallback UI berish kerak:
// app/page.tsx
import { Suspense } from 'react';
async function LatestPosts() {
// Network so'rov - prerendering da bajarilmaydi
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default function Page() {
return (
<>
{/* STATIK - darhol ko'rsatiladi */}
<h1>Blog</h1>
<nav>Navigatsiya</nav>
{/* DINAMIK - fallback darhol, kontent keyin stream bo'ladi */}
<Suspense fallback={<p>Postlar yuklanmoqda...</p>}>
<LatestPosts />
</Suspense>
</>
);
}
Jarayon:
1. Foydalanuvchi sahifani ochadi
2. Darhol ko'radi: "Blog", Navigatsiya, "Postlar yuklanmoqda..."
3. Server API dan ma'lumot oladi (fonda)
4. Tayyor bo'lganda: "Postlar yuklanmoqda..." → haqiqiy postlar ro'yxati
Muhim:
Suspensechegarasini iloji boricha komponentga yaqin qo'ying. Shunda chegaradan tashqaridagi kontent statik shell da qoladi.
Bir nechta dinamik bo'limlar bo'lsa, ular parallel renderlanadi:
export default function Page() {
return (
<>
<h1>Dashboard</h1>
{/* Bu ikkalasi PARALLEL yuklanadi - bir-birini kutmaydi */}
<Suspense fallback={<p>Statistika...</p>}>
<Stats />
</Suspense>
<Suspense fallback={<p>Grafik...</p>}>
<Chart />
</Suspense>
</>
);
}
3. Runtime ma'lumotlar - cookies, headers, searchParams
Bu ma'lumotlar faqat so'rov vaqtida mavjud bo'ladi. Har bir foydalanuvchi uchun boshqacha:
import { cookies, headers } from 'next/headers';
import { Suspense } from 'react';
async function UserGreeting() {
// Runtime data - faqat so'rov vaqtida mavjud
const cookieStore = await cookies();
const username = cookieStore.get('username')?.value;
const headerStore = await headers();
const lang = headerStore.get('accept-language');
return <p>Salom, {username}! Tilingiz: {lang}</p>;
}
export default function Page() {
return (
<>
<h1>Dashboard</h1> {/* STATIK */}
{/* Runtime data - Suspense MAJBURIY */}
<Suspense fallback={<p>Yuklanmoqda...</p>}>
<UserGreeting />
</Suspense>
</>
);
}
Runtime API lari:
| API | Ma'lumot |
|---|---|
cookies() |
Foydalanuvchi cookie lari |
headers() |
So'rov headerlari |
searchParams |
URL query parametrlari |
params |
Dinamik route parametrlari |
Muhim: Runtime data ni
use cachebilan keshlab bo'lmaydi, chunki har bir foydalanuvchi uchun boshqacha.
4. use cache - Dinamik ma'lumotni keshlash
Agar dinamik ma'lumot tez-tez o'zgarmasa (blog postlar, mahsulot katalogi), uni use cache bilan keshlab statik shell ga qo'shish mumkin:
// app/page.tsx
import { cacheLife } from 'next/cache';
export default async function Page() {
'use cache'; // Bu sahifani keshla
cacheLife('hours'); // 1 soat davomida keshdan beraver
// Bu fetch bir marta bajariladi, natija keshlanadi
const users = await db.query('SELECT * FROM users');
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
cacheLife profillari
| Profil | Ma'nosi |
|---|---|
'seconds' |
Bir necha soniya |
'minutes' |
Bir necha daqiqa |
'hours' |
Soatlar |
'days' |
Kunlar |
'weeks' |
Haftalar |
'max' |
Imkon qadar uzoq |
Maxsus konfiguratsiya
import { cacheLife } from 'next/cache';
export default async function Page() {
'use cache';
cacheLife({
stale: 3600, // 1 soat - eskirgan deb hisoblanguncha
revalidate: 7200, // 2 soat - qayta tekshirilguncha
expire: 86400, // 1 kun - butunlay o'chirilguncha
});
const data = await fetchData();
return <div>{data}</div>;
}
use cache ni funksiya yoki komponent darajasida qo'llash
Faqat sahifa emas, alohida funksiya yoki komponentga ham qo'llash mumkin:
// Komponent darajasida
async function ProductCatalog() {
'use cache';
cacheLife('days');
const products = await fetchProducts();
return <div>{/* ... */}</div>;
}
// Funksiya darajasida
async function getPopularPosts() {
'use cache';
cacheLife('hours');
return await db.posts.findMany({
orderBy: { views: 'desc' },
take: 10,
});
}
5. Runtime data + use cache birga ishlatish
Runtime data va use cache ni bir scope da ishlatib bo'lmaydi. Lekin qiymatni chiqarib, cached funksiyaga argument sifatida uzatish mumkin:
// app/profile/page.tsx
import { cookies } from 'next/headers';
import { Suspense } from 'react';
export default function Page() {
return (
<Suspense fallback={<div>Yuklanmoqda...</div>}>
<ProfileContent />
</Suspense>
);
}
// 1-qadam: Runtime data o'qiladi (keshlanmaydi)
async function ProfileContent() {
const session = (await cookies()).get('session')?.value;
// Session qiymatini cached komponentga uzatadi
return <CachedContent sessionId={session!} />;
}
// 2-qadam: KESHLANADI - sessionId cache key bo'ladi
async function CachedContent({
sessionId,
}: {
sessionId: string;
}) {
'use cache';
// Har xil sessionId uchun alohida kesh yozuvi
const data = await fetchUserData(sessionId);
return <div>{data.name} profili</div>;
}
Jarayon:
So'rov keldi → cookies o'qildi → session="abc123"
│
Cache da "abc123" bormi?
├── Ha → keshdan javob (tez!)
└── Yo'q → fetchUserData("abc123")
→ natija keshlanadi
→ keyingi safar keshdan
Argumentlar avtomatik ravishda cache key ga aylanadi - turli inputlar uchun alohida kesh yozuvlari yaratiladi.
6. Tag va Revalidatsiya - keshni yangilash
Keshlangan ma'lumotni tag bilan belgilab, keyin yangilash mumkin. Ikki xil usul bor:
updateTag - darhol yangilash
Kesh shu zahoti o'chiriladi va yangi ma'lumot olinadi. Savat, like, comment uchun ideal:
// app/actions.ts
import { cacheTag, updateTag } from 'next/cache';
// Keshlangan funksiya
export async function getCart() {
'use cache';
cacheTag('cart'); // "cart" tegi bilan belgilash
return await fetchCart();
}
// Server Action - savatni yangilash
export async function addToCart(itemId: string) {
'use server';
await db.cart.add(itemId);
updateTag('cart'); // "cart" keshini DARHOL yangilash
}
revalidateTag - fonda yangilash (stale-while-revalidate)
Eski kesh ko'rsatiladi, fonda yangi ma'lumot yuklanadi. Blog, katalog uchun ideal:
import { cacheTag, revalidateTag } from 'next/cache';
export async function getPosts() {
'use cache';
cacheTag('posts');
return await fetchPosts();
}
export async function createPost(post: FormData) {
'use server';
await db.posts.create(post);
revalidateTag('posts');
// Foydalanuvchi eski postlarni ko'radi
// Fonda yangi postlar yuklanadi
// Keyingi so'rovda yangi postlar ko'rsatiladi
}
Ikki usulning farqi
| Metod | Xatti-harakat | Qachon ishlatish |
|---|---|---|
updateTag('cart') |
Kesh darhol o'chiriladi, yangi ma'lumot shu so'rovda olinadi | Savat, like, comment - darhol ko'rinishi kerak |
revalidateTag('posts') |
Eski kesh ko'rsatiladi, fonda yangilanadi | Blog, katalog - biroz kechikish qabul qilinadi |
7. Noaniq operatsiyalar - connection()
Math.random(), Date.now(), crypto.randomUUID() kabi operatsiyalar har safar boshqa natija beradi. Ularni so'rov vaqtida ishlashini ta'minlash uchun connection() ishlatiladi:
import { connection } from 'next/server';
import { Suspense } from 'react';
async function UniqueContent() {
await connection(); // "Bu komponent so'rov vaqtida ishlashi kerak"
const uuid = crypto.randomUUID();
const now = new Date().toISOString();
return (
<div>
<p>Unikal ID: {uuid}</p>
<p>Vaqt: {now}</p>
</div>
);
}
export default function Page() {
return (
<Suspense fallback={<p>Yuklanmoqda...</p>}>
<UniqueContent />
</Suspense>
);
}
Muhim farq:
-
use cacheichidaMath.random()→ bir marta hisoblanadi, keshlanadi (hamma uchun bir xil) -
connection()dan keyinMath.random()→ har safar yangi qiymat
8. React Activity - navigatsiya state saqlanishi
cacheComponents yoqilganda, Next.js React <Activity> komponentini ishlatadi:
/dashboard dan /settings ga o'tdingiz:
Dashboard UNMOUNT bo'lmaydi!
Activity mode = "hidden" bo'ladi
State saqlanadi (form inputlar, scroll pozitsiya)
/dashboard ga qaytganingizda:
Dashboard avvalgi holatida qayta ko'rinadi
Form ma'lumotlari saqlanib qolgan!
Bu degani foydalanuvchi formani to'ldirib yurgan, boshqa sahifaga o'tib qaytsa - hamma narsa saqlanib qoladi.
Next.js yaqinda ko'rilgan bir nechta routeni
"hidden"holatda saqlaydi. Eski routelar DOM dan o'chiriladi.
9. To'liq misol - hammasi birga
// app/blog/page.tsx
import { Suspense } from 'react';
import { cookies } from 'next/headers';
import { cacheLife } from 'next/cache';
import Link from 'next/link';
export default function BlogPage() {
return (
<>
{/* 1. STATIK - avtomatik, darhol ko'rsatiladi */}
<header>
<h1>Bizning Blog</h1>
<nav>
<Link href="/">Bosh sahifa</Link>
<Link href="/about">Biz haqimizda</Link>
</nav>
</header>
{/* 2. KESHLANGAN - statik shell da, soatlik yangilanadi */}
<BlogPosts />
{/* 3. DINAMIK - har bir foydalanuvchi uchun alohida */}
<Suspense fallback={<p>Sozlamalar yuklanmoqda...</p>}>
<UserPreferences />
</Suspense>
</>
);
}
// KESHLANGAN - barcha foydalanuvchilar uchun bir xil
async function BlogPosts() {
'use cache';
cacheLife('hours');
const res = await fetch('https://api.vercel.app/blog');
const posts = await res.json();
return (
<section>
<h2>So'nggi postlar</h2>
<ul>
{posts.slice(0, 5).map((post: any) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.author} - {post.date}</p>
</li>
))}
</ul>
</section>
);
}
// DINAMIK - har bir foydalanuvchi uchun shaxsiy
async function UserPreferences() {
const theme =
(await cookies()).get('theme')?.value || 'light';
const favoriteCategory =
(await cookies()).get('category')?.value;
return (
<aside>
<p>Mavzu: {theme}</p>
{favoriteCategory && (
<p>Sevimli kategoriya: {favoriteCategory}</p>
)}
</aside>
);
}
Foydalanuvchi sahifani ochganda:
Darhol ko'rinadi (statik shell):
┌──────────────────────────────────────┐
│ Bizning Blog │ ← STATIK
│ Bosh sahifa | Biz haqimizda │
│──────────────────────────────────────│
│ So'nggi postlar │
│ • Post 1 - Muallif - Sana │ ← KESHLANGAN
│ • Post 2 - Muallif - Sana │
│ • Post 3 - Muallif - Sana │
│──────────────────────────────────────│
│ Sozlamalar yuklanmoqda... │ ← FALLBACK
└──────────────────────────────────────┘
~0.5 soniya keyin:
┌──────────────────────────────────────┐
│ ...yuqoridagi hamma narsa saqlanadi │
│──────────────────────────────────────│
│ Mavzu: dark │ ← DINAMIK (stream)
│ Sevimli kategoriya: React │
└──────────────────────────────────────┘
10. Eski route segment config lardan ko'chish
Cache Components yoqilganda eski sozlamalar kerak emas yoki o'zgaradi:
dynamic = 'force-dynamic' → O'chirish
// Oldin
export const dynamic = 'force-dynamic';
// Keyin - shunchaki o'chiring, default holatda dinamik
export default function Page() {
return <div>...</div>;
}
dynamic = 'force-static' → use cache
// Oldin
export const dynamic = 'force-static';
export default async function Page() {
const data = await fetch('https://api.example.com/data');
return <div>...</div>;
}
// Keyin
import { cacheLife } from 'next/cache';
export default async function Page() {
'use cache';
cacheLife('max');
const data = await fetch('https://api.example.com/data');
return <div>...</div>;
}
revalidate = 3600 → cacheLife('hours')
// Oldin
export const revalidate = 3600;
// Keyin
import { cacheLife } from 'next/cache';
export default async function Page() {
'use cache';
cacheLife('hours');
return <div>...</div>;
}
fetchCache → Kerak emas
// Oldin
export const fetchCache = 'force-cache';
// Keyin - use cache ichidagi fetch lar avtomatik keshlanadi
export default async function Page() {
'use cache';
return <div>...</div>;
}
runtime = 'edge' → Qo'llab-quvvatlanmaydi
Cache Components faqat Node.js runtime da ishlaydi. Edge Runtime qo'llab-quvvatlanmaydi.
Xulosa
| Tushuncha | Qanday ishlaydi | Qachon ishlatish |
|---|---|---|
| Statik | Avtomatik - hech narsa kerak emas | Header, nav, footer, statik matn |
use cache |
'use cache' + cacheLife()
|
DB/API data tez-tez o'zgarmasa |
<Suspense> |
Fallback + stream | Runtime data, shaxsiy ma'lumot |
cacheTag |
Keshni belgilash | Yangilash kerak bo'lgan data |
updateTag |
Darhol yangilash | Savat, like, comment |
revalidateTag |
Fonda yangilash | Blog postlar, katalog |
connection() |
So'rov vaqtiga kechirish | Random, vaqt, UUID |
| Activity | State saqlanishi | Navigatsiyada form, scroll |
Asosiy g'oya
Bitta sahifada statik, keshlangan va dinamik kontent birga yashaydi. Foydalanuvchi darhol statik shell ni ko'radi, dinamik qismlar tayyor bo'lganda stream qilinadi. Bu eng yaxshi foydalanuvchi tajribasini beradi - statik saytning tezligi va dinamik saytning yangiligi birgalikda.
Bu maqola Next.js 15+ rasmiy dokumentatsiyasi asosida yozilgan. Savollar yoki takliflar bo'lsa, kommentariyada yozing!
(https://www.matkarim.uz)
Top comments (0)