Server Component da fetch — caching, revalidate, no-store

~20 daq · Next.js · Ma'lumot Olish

Muammodan boshlaylik

Siz bir sahifada ikkita komponent yozdingiz. Ikkalasi ham bir xil API dan foydalanuvchi ma'lumotini oladi:

// Header.tsx
const user = await fetch('https://api.example.com/user/1').then(r => r.json())

// Sidebar.tsx
const user = await fetch('https://api.example.com/user/1').then(r => r.json())

Bir xil URL ga ikki marta so'rov ketadimi? Serverga yukmi oshadi?

Next.js da — yo'q. Bir xil so'rov avtomatik deduplikatsiya qilinadi. Bitta so'rov ketadi, natija ikkalasiga beriladi.

Bu — Next.js ning fetch ni qanday kengaytirganining bir misoli.


Next.js fetch ni kengaytirgan

Brauzer fetch — oddiy HTTP so'rov. Next.js Server Component dagi fetch ni kengaytirgan — kesh, revalidatsiya, deduplikatsiya qo'shilgan.

Oddiy fetch:
  fetch('/api/data') → HTTP so'rov → javob

Next.js fetch:
  fetch('/api/data') → keshda bormi? → bor → keshdan qaytaradi
                                     → yo'q → HTTP so'rov → keshga saqlaydi

3 ta kesh rejimi

1. force-cache — keshda saqlash (default)

const data = await fetch('https://api.example.com/posts', {
  cache: 'force-cache'  // default — yozmasangiz ham shu
}).then(r => r.json())

Birinchi so'rovda ma'lumot olinadi va keshga saqlanadi. Keyingi so'rovlarda — keshdan qaytariladi. Server ga qayta so'rov ketmaydi.

Bu — Static Rendering ga mos. Build vaqtida bir marta olinadi.

2. no-store — keshlamaslik

const data = await fetch('https://api.example.com/prices', {
  cache: 'no-store'
}).then(r => r.json())

Har so'rovda yangi ma'lumot olinadi. Keshlanmaydi.

Bu — Dynamic Rendering ga mos. Har foydalanuvchi uchun yangi ma'lumot.

3. revalidate — vaqt asosida yangilash

const data = await fetch('https://api.example.com/news', {
  next: { revalidate: 3600 }  // 1 soatda bir yangilanadi
}).then(r => r.json())

Ma'lumot keshlanadi, lekin belgilangan vaqtdan keyin yangilanadi.

Bu — ISR ga mos.


Kesh qanday ishlaydi — vizual

Birinchi so'rov:
  fetch('/api/posts') → keshda yo'q → API ga so'rov → keshga saqlaydi → qaytaradi

Ikkinchi so'rov (force-cache):
  fetch('/api/posts') → keshda bor → API ga so'rov ketmaydi → keshdan qaytaradi

Ikkinchi so'rov (no-store):
  fetch('/api/posts') → keshni tekshirmaydi → API ga so'rov → qaytaradi

Ikkinchi so'rov (revalidate: 3600, 1 soat o'tmagan):
  fetch('/api/posts') → keshda bor, muddati o'tmagan → keshdan qaytaradi

Ikkinchi so'rov (revalidate: 3600, 1 soat o'tgan):
  fetch('/api/posts') → keshda bor, muddati o'tgan → API ga so'rov → keshni yangilaydi

Deduplikatsiya — bir xil so'rov bir marta

Bir render davomida bir xil URL ga bir necha marta fetch qilinsa — Next.js faqat bitta so'rov yuboradi:

// app/page.tsx
export default async function Page() {
  return (
    <div>
      <Header />      {/* ichida fetch('/api/user') */}
      <Sidebar />     {/* ichida fetch('/api/user') */}
      <MainContent /> {/* ichida fetch('/api/user') */}
    </div>
  )
}

Uchta komponent bir xil URL ga so'rov qiladi — lekin server ga bitta so'rov ketadi. Natija uchtalasiga beriladi.

Bu faqat GET so'rovlari va bir render davomida ishlaydi.


Teglar bilan kesh boshqarish

revalidateTag bilan ishlash uchun fetch ga teg qo'shiladi:

// Teg bilan fetch
const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
}).then(r => r.json())

const post = await fetch(`https://api.example.com/posts/${id}`, {
  next: {
    tags: ['posts', `post-${id}`],
    revalidate: 3600
  }
}).then(r => r.json())

Yangi post qo'shilganda:

import { revalidateTag } from 'next/cache'

revalidateTag('posts')  // 'posts' tegli barcha fetch lar yangilanadi

fetch ishlatilmasa — unstable_cache

Ba'zan fetch emas, to'g'ridan-to'g'ri DB so'rovi ishlatiladi. Bunday holda unstable_cache (Next.js 14) yoki cacheTag / cacheLife (Next.js 15) ishlatiladi:

// Next.js 14
import { unstable_cache } from 'next/cache'

const getCachedPosts = unstable_cache(
  async () => {
    return await db.select().from(posts)  // DB so'rovi
  },
  ['posts-cache'],        // kesh kaliti
  {
    revalidate: 3600,     // 1 soatda bir
    tags: ['posts'],      // revalidateTag uchun
  }
)

export default async function Page() {
  const posts = await getCachedPosts()
  return <PostList posts={posts} />
}

Amaliy misol — blog sahifasi

// app/blog/page.tsx — ISR, 1 soatda bir yangilanadi
export default async function BlogPage() {
  const posts = await fetch('https://api.example.com/posts', {
    next: {
      revalidate: 3600,
      tags: ['posts']
    }
  }).then(r => r.json())

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <a href={`/blog/${post.slug}`}>{post.title}</a>
        </li>
      ))}
    </ul>
  )
}
// app/blog/[slug]/page.tsx — ISR, 1 soatda bir
export default async function BlogPost({ params }) {
  const { slug } = await params

  const post = await fetch(`https://api.example.com/posts/${slug}`, {
    next: {
      revalidate: 3600,
      tags: ['posts', `post-${slug}`]
    }
  }).then(r => r.json())

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}
// app/dashboard/page.tsx — Dynamic, har so'rovda yangi
import { cookies } from 'next/headers'

export default async function DashboardPage() {
  const cookieStore = await cookies()
  const token = cookieStore.get('token')?.value

  const user = await fetch('https://api.example.com/me', {
    cache: 'no-store',
    headers: { Authorization: `Bearer ${token}` }
  }).then(r => r.json())

  return <h1>Salom, {user.name}</h1>
}

Tez-tez uchraydigan xatolar

1. no-store va revalidate birga ishlatish

// ❌ — qarama-qarshi, no-store ustun keladi
const data = await fetch('/api/data', {
  cache: 'no-store',
  next: { revalidate: 60 }  // e'tiborga olinmaydi
})

// ✅ — bittasini tanlang
const data = await fetch('/api/data', { cache: 'no-store' })
// yoki
const data = await fetch('/api/data', { next: { revalidate: 60 } })

2. Client Component da server fetch keshini kutish

// ❌ — Client Component da fetch — Next.js keshi ishlamaydi
'use client'
useEffect(() => {
  fetch('/api/posts')  // bu oddiy brauzer fetch, Next.js keshi yo'q
}, [])

// ✅ — Server Component da fetch
export default async function Page() {
  const posts = await fetch('/api/posts', { next: { revalidate: 60 } })
    .then(r => r.json())
}

Next.js fetch keshi faqat Server Component da ishlaydi.

3. Har bir fetch uchun alohida revalidate o'rniga sahifa darajasida belgilash

// ❌ — har fetch da takrorlanadi
const a = await fetch('/api/a', { next: { revalidate: 3600 } })
const b = await fetch('/api/b', { next: { revalidate: 3600 } })
const c = await fetch('/api/c', { next: { revalidate: 3600 } })

// ✅ — sahifa darajasida bir marta
export const revalidate = 3600

const a = await fetch('/api/a').then(r => r.json())
const b = await fetch('/api/b').then(r => r.json())
const c = await fetch('/api/c').then(r => r.json())

Xulosa

Next.js fetch — oddiy fetch emas, kesh va revalidatsiya qo'shilgan.

cache: 'force-cache'      → keshda saqlash, Static uchun (default)
cache: 'no-store'         → keshlamaslik, Dynamic uchun
next: { revalidate: 60 }  → 60 soniyada bir yangilash, ISR uchun
next: { tags: ['posts'] } → revalidateTag bilan yangilash uchun

Bir xil URL — bir so'rov (deduplikatsiya). DB so'rovlari uchun unstable_cache.


Keyingi dars

18-dars: Parallel va Sequential data fetching — farqi, qachon qaysinisi

Bir sahifada bir nechta ma'lumot olish kerak bo'lganda — ketma-ket yoki parallel, qaysi usul qachon mos.