DEV Community

MATKARIM MATKARIMOV
MATKARIM MATKARIMOV

Posted on

Next.js - Cache Components (Partial Prerendering) To'liq Qo'llanma

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!                  │
└──────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Muhim: Suspense chegarasini 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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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 cache bilan 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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,
  });
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Muhim farq:

  • use cache ichida Math.random()bir marta hisoblanadi, keshlanadi (hamma uchun bir xil)
  • connection() dan keyin Math.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!
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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            │
└──────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode
// 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>;
}
Enter fullscreen mode Exit fullscreen mode

revalidate = 3600cacheLife('hours')

// Oldin
export const revalidate = 3600;

// Keyin
import { cacheLife } from 'next/cache';

export default async function Page() {
  'use cache';
  cacheLife('hours');
  return <div>...</div>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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)