IlmHamroh
JavaScript Full-stack/23-qism. Next.js18/43-dars22 daqiqa
Mundarija (30)

Next.js Cache Components: "use cache", cacheLife va Partial Prerendering

Qisqacha: Next 16 da Cache Components yoqilgan bo'lsa (cacheComponents: true), hech narsa o'z-o'zidan keshlanmaydi: fetch, fayl o'qish, baza so'rovi — hammasi "har so'rovda" deb hisoblanadi. Prerender (build vaqtidagi chizish) shunday ma'lumotga yetganda to'xtaydi. Keyin sizdan uchta yo'ldan birini so'raydi: <Suspense> bilan oqim, "use cache" bilan keshlash yoki sahifani bloklash. "use cache" — funksiya yoki komponent boshidagi bitta qator: natija argumentlar bo'yicha saqlanadi, cacheLife("hours") esa qancha vaqt saqlanishini aytadi. Natijada sahifa statik qobiq + keshlangan qismlar + kerak bo'lsa dinamik "teshiklar" dan iborat bo'ladi — bu Partial Prerendering (PPR).

Bu darsda

  • Cache Components'ning asosiy qoidasini tushuntirasiz: "aniq aytilmasa — dinamik" va build xatosidagi uchta yo'l.
  • "use cache" ni funksiya, komponent va fayl darajasida yozasiz; kesh kaliti nimadan iboratligini bilasiz.
  • cacheLife profillari (minutes, hours, max …) va stale / revalidate / expire farqini o'quv modelida sinaysiz.
  • Kanon «Men haqimda» sahifasini ◐ dan ○ ga o'tkazgan o'zgarishni va o'lchangan natijasini ko'rasiz: DCL 1 176 → 56 ms.
  • "use cache" ichida cookies() nega taqiqlanganini haqiqiy build xatosi bilan ko'rasiz va private / remote variantlarini taniysiz.

Oldin bilishingiz kerak: Streaming va Suspense, generateStaticParams va segment konfiguratsiyasi, Serverda ma'lumot olish, Kesh strategiyalari.

1. Nega bu kerak?

Streaming darsi oxirida «Men haqimda» sahifasida uchta muammo qolgan edi:

  1. Limit. Har ko'rish = GitHub'ga 2 ta so'rov. Kalitsiz limit — soatiga 60 ta. Soatiga ~30 ko'rishdan keyin hamma zaxira matnni ko'radi.
  2. Tezlik. Sahifa har safar GitHub'ni kutadi: kanon o'lchovida DCL (sahifa HTML'i to'liq o'qilgan payt) — 1 176 ms.
  3. JavaScript'siz. Oqim bilan kelgan blok JS o'chiq bo'lsa abadiy "yuklanmoqda…".

Lekin GitHub profilingiz har soniyada o'zgarmaydi. Kuniga bir-ikki marta yangi commit — xolos. Har ko'ruvchi uchun GitHub'dan qayta so'rash — har mijozga choy damlash uchun suvni qaytadan qaynatishga o'xshaydi. Oddiy choyxonada katta samovar bor: suv bir marta qaynaydi, keyin soatlab undan quyiladi. Vaqti-vaqti bilan yangi suv solinadi.

Kesh (cache) — aynan shu samovar: bir marta olingan natija saqlanadi va keyingi so'rovlarga undan beriladi. Kesh tushunchasini Service Worker va kesh strategiyalari darsida brauzerda ko'rgansiz. Bugun uni serverda, Next.js'ning yangi modelida qo'llaymiz.

2. Cache Components modeli

2.1 Asosiy qoida: aniq aytilmasa — dinamik

create-next-app 16.4 skeleti next.config.ts da allaqachon shunday yozgan (Loyiha yaratish):

ts
const nextConfig: NextConfig = {
  // create-next-app 16.4 sukut bo'yicha yoqadi (N-18, N-06)
  cacheComponents: true,
  partialPrefetching: true,
};

Bu sozlama Next.js'ning keshga munosabatini o'zgartiradi:

Eski model (Next 14) Cache Components (Next 16)
fetch Sukut bo'yicha keshlanardi Keshlanmaydi
Sahifa statik/dinamik Next.js "taxmin" qilardi Siz aniq aytasiz
Keshlash vositasi fetch sozlamalari, revalidate "use cache" + cacheLife
Noaniq ma'lumot Jim dinamik render Build xatosi + yo'l-yo'riq

Oxirgi qator eng muhimi. Next.js build vaqtida har sahifani chizib ko'radi (prerender). Keshlanmagan ma'lumotga yetsa — to'xtaydi va xato beradi. Kanon buni blog postlarini fayldan o'qiganda ko'rgan (xom natijalar/tajriba/11-fs-uncached-xato.txt):

text
Error: Route "/blog": Next.js encountered uncached or runtime data during prerendering.

`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [cache] For uncached data (`fetch`, database calls): cache the access with `"use cache"` (does not apply to `connection()`)
  - [block] Set `export const instant = false` to allow a blocking route

Tarjimasi: "/blog marshrutida prerender paytida keshlanmagan yoki so'rov paytidagi ma'lumot uchradi. Bu ma'lumot <Suspense> dan tashqarida o'qilgani uchun sahifani oldindan chizib bo'lmaydi va yuklanish sekinlashadi". Keyin uchta yo'l:

  • [stream] — <Suspense> bilan o'rang: qolgan qism statik, bu qism oqim bilan (Streaming).
  • [cache] — "use cache" bilan keshlang: natija build vaqtida olinadi va qobiqqa tushadi.
  • [block] — export const instant = false: sahifa bloklanadi, prerender'siz har so'rovda to'liq chiziladi.
flowchart TB
  P["prerender: sahifani chizish"] --> Q{"ma'lumot keshlanganmi?"}
  Q -- "ha: use cache" --> S["statik qobiqqa tushadi"]
  Q -- "yo'q, Suspense ichida" --> D["dinamik teshik: oqim"]
  Q -- "yo'q, Suspense'siz" --> E["build xatosi: 3 yo'l"]

Diagrammadagi uchinchi shox — Next 15 dan farqi. Ilgari Next.js bunday sahifani jimgina "har so'rovda" qilib qo'yardi va siz buni faqat sekinlikdan bilib qolardingiz.

2.2 Partial Prerendering (PPR)

Streaming darsida ◐ /haqimda ni ko'rdingiz — bu Partial Prerendering (qisman oldindan chizish): bitta sahifa ichida statik va dinamik qism birga yashaydi.

flowchart LR
  subgraph Q["Statik qobiq (build vaqtida)"]
    H["h1, matn, menyu"]
    C["use cache qismlari"]
    F["Suspense fallback"]
  end
  F -. "so'rov paytida" .-> D["dinamik qism: oqim"]

Qobiq — CDN'dan yoki diskdan bir zumda beriladigan HTML. Dinamik qism — so'rov paytida server chizadigan va oqim bilan qo'shiladigan bo'lak. Build jadvalidagi belgilar:

Belgi Ma'nosi
○ To'liq statik — dinamik teshik yo'q
◐ Statik qobiq + dinamik teshiklar (PPR)
ƒ To'liq dinamik — prerender yo'q (masalan Route Handler POST)

partialPrefetching: true esa PPR'ning navigatsiyadagi juftligi: <Link> ko'ringanda avval qobiq, keyin to'liq sahifa oldindan yuklanadi. Kanon Server Components holatida buni 16 ta RSC so'rovi sifatida ko'rgan; chuqur o'lchov — Performance darsida.

Tekshirib ko'ring: blog sahifasidagi xatoni <Suspense> bilan "tuzatish" mumkin edi. Nega kanon "use cache" ni tanladi?

Javob

Postlar faqat yangi deploy bilan o'zgaradi. Ularni har so'rovda fayldan o'qish ma'nosiz. <Suspense> bilan post matni oqim bilan kelardi. JS o'chiq bo'lsa, u ko'rinmasdi. "use cache" bilan matn build vaqtida HTML'ga tushadi — sahifa ○ statik.

3. "use cache" direktivasi

3.1 Funksiya darajasida

Direktiva — fayl yoki funksiya boshidagi maxsus satr, kompilyatorga buyruq. "use client" va "use server" ni bilasiz (Client Components). "use cache" — uchinchisi.

Kanon src/malumot/github.ts dagi o'zgarish (holat 16 → 19):

diff
 import "server-only";
+import { cacheLife, cacheTag } from "next/cache";
ts
export async function githubProfil(
  login: string,
): Promise<GithubProfil | null> {
  // "use cache" (N-18): natija keshlanadi — soatiga 60 so'rov
  // limiti endi tashrif soniga bog'liq emas.
  // Kesh kaliti — argument (login). Teg — qo'lda yangilash
  // uchun (N-19, /api/qayta-yangila).
  "use cache";
  cacheTag("github");
  const natija = await profilniOl(login);
  // Muvaffaqiyatsiz natija bir soat keshda qolmasin:
  // zaxira — 1 daqiqa, ma'lumot — 1 soat
  if (natija) cacheLife("hours");
  else cacheLife("minutes");
  return natija;
}

async function profilniOl(
  login: string,
): Promise<GithubProfil | null> {
  // … oldingi darsdagi Promise.all, tekshiruv va try/catch
}

Kanon izohlari qisqa qatorlarga bo'lindi. Eski githubProfil tanasi o'zgarmadi — u faqat profilniOl nomini oldi. Yangi githubProfil — uning keshlangan o'rami.

Qoidalar:

Qoida Izoh
Birinchi qator "use cache" funksiya tanasining eng boshida (izohlardan keyin)
Faqat async Keshlangan funksiya Promise qaytaradi
Faqat serverda Client Component ichida e'lon qilinsa — build xatosi
cacheLife, cacheTag Faqat "use cache" ichida chaqiriladi

Uchinchi qator haqiqiy: kanon Client va server kompozitsiyasi tajribasida blog.ts ni client komponentga import qilganda shu xatoni olgan:

text
Error: It is not allowed to define inline "use cache" annotated functions in Client Components.

3.2 Komponent va fayl darajasida

"use cache" ni uch joyga yozish mumkin:

tsx
// 1) Funksiya — kanon shunday (ma'lumot qatlami)
async function githubProfil(login: string) {
  "use cache";
  // …
}

// 2) Komponent — uning butun chiqishi (JSX) keshlanadi
async function PostlarRoyxati() {
  "use cache";
  const royxat = await postlar();
  return <ul>{/* … */}</ul>;
}

// 3) Fayl — birinchi qatorda: fayldagi HAMMA export keshlanadi
"use cache";
export async function sahifaMatni() { /* … */ }

Kanon birinchisini tanladi: kesh ma'lumot yonida turadi, sahifa va komponentlar oddiyligicha qoladi. Komponent darajasi — ko'p joyda ishlatiladigan og'ir komponent uchun qulay. Fayl darajasi — hamma funksiyasi keshlanadigan modul uchun.

3.3 Kesh kaliti

Kesh har chaqiruvni kalit bo'yicha saqlaydi. Kalit uch qismdan iborat (Next.js hujjati bo'yicha):

  1. Build ID — har build'da yangi. Yangi deploy — yangi kesh.
  2. Funksiya ID — qaysi funksiya.
  3. Argumentlar va funksiya tashqaridan olgan o'zgaruvchilar — seriyalangan holda.

Ya'ni githubProfil("octocat") va githubProfil("aziz") — ikki alohida yozuv. githubProfil("octocat") ni ikkinchi marta chaqirsangiz — o'sha yozuv.

Argumentlar seriyalanadigan bo'lishi shart: satr, son, oddiy obyekt, massiv, Date. Funksiya yoki klass nusxasi kalit bo'la olmaydi. Istisno — children kabi qiymatlarni ichiga qaramasdan "o'tkazib yuborish" mumkin (hujjat buni pass-through deydi): ular kalitga kirmaydi.

4. cacheLife: qancha vaqt?

4.1 Uch vaqt: stale, revalidate, expire

Har kesh yozuvining uchta muddati bor:

Muddat Kimga Ma'nosi
stale Brauzer (client router) Shu vaqt ichida serverdan so'ramay, eslab qolganini ko'rsatadi
revalidate Server Shundan keyingi so'rov eski qiymatni oladi, server fonda yangilaydi
expire Server Shuncha vaqt so'rov bo'lmasa — keyingi so'rov yangi qiymatni kutadi

revalidate va expire farqini nonvoyxona bilan tushuntirish oson. Non ertalab yopildi. Bir soatdan keyin (revalidate) birinchi xaridor kechagi emas, ertalabki — lekin biroz sovigan nonni oladi, nonvoy esa shu payt yangi qozon yopadi. Agar kun bo'yi hech kim kelmasa (expire), ertasi kungi birinchi xaridor eski nonni olmaydi — yangisini kutadi.

Bu naqsh stale-while-revalidate (eskisini ber, fonda yangila) deyiladi. Uni kesh strategiyalari darsida Service Worker uchun ko'rgansiz.

4.2 Tayyor profillar

cacheLife ga profil nomini berasiz. Next.js 16.4 hujjatidagi jadval:

Profil stale revalidate expire
default 5 daqiqa 15 daqiqa hech qachon
seconds 30 soniya 1 soniya 1 daqiqa
minutes 5 daqiqa 1 daqiqa 1 soat
hours 5 daqiqa 1 soat 1 kun
days 5 daqiqa 1 kun 1 hafta
weeks 5 daqiqa 1 hafta 30 kun
max 5 daqiqa 30 kun 1 yil

cacheLife chaqirilmasa — default. Hujjat har "use cache" da profilni aniq yozishni tavsiya qiladi: o'qigan odam muddatni darhol ko'radi.

Raqamlar bilan ham berish mumkin (soniyalarda): cacheLife({ stale: 300, revalidate: 3600, expire: 86400 }). Yoki next.config.ts da o'z profilingizni nom bilan e'lon qilasiz.

Bitta nozik qoida: juda qisqa kesh qobiqqa kirmaydi. Hujjat bo'yicha expire 5 daqiqadan qisqa yoki revalidate: 0 bo'lsa — yozuv prerender'dan chiqariladi va dinamik teshikka aylanadi. stale 30 soniyadan qisqa bo'lsa ham shunday (prefetch qilingan nusxa bosishga ulgurmay eskirardi). Tayyor profillardan faqat seconds shunga tushadi.

4.3 Natija oynasida: kesh modeli

"use cache" natija oynasida ishlamaydi — u Next.js kompilyatorining imkoniyati. Lekin uning qoidasi — "kalit bo'yicha saqlash va uch muddat" — oddiy JS bilan aniq ko'rsatiladi. Bu o'quv modeli, Next.js'ning ichki kodi emas. Vaqt soniyalarda, soat — soxta (time o'zgaruvchisi), shunda 2 soatni bir zumda "o'tkazib" yuboramiz:

js
let time = 0; // soxta soat, soniyalarda
let calls = 0;

function cached(fn, { revalidate, expire }) {
  const store = new Map();
  return async (...args) => {
    const key = JSON.stringify(args);
    const entry = store.get(key);
    const age = entry ? time - entry.at : Infinity;
    if (age < revalidate) {
      console.log(`${time}s HIT ${key}`);
      return entry.value;
    }
    if (age < expire) {
      console.log(`${time}s STALE ${key}: eskisi, fonda yangilanadi`);
      fn(...args).then((v) => store.set(key, { value: v, at: time }));
      return entry.value;
    }
    console.log(`${time}s MISS ${key}: kutamiz`);
    const value = await fn(...args);
    store.set(key, { value, at: time });
    return value;
  };
}

// GitHub o'rnida: har chaqiruv — yangi "versiya"
const githubProfile = cached(
  async (login) => `${login} v${++calls}`,
  { revalidate: 3600, expire: 86400 }, // "hours" profili
);

console.log(await githubProfile("octocat"));
time = 600;
console.log(await githubProfile("octocat"));
console.log(await githubProfile("aziz"));
time = 4000;
console.log(await githubProfile("octocat"));
await null; // fondagi yangilanish tugashi uchun
console.log(await githubProfile("octocat"));
time = 200000;
console.log(await githubProfile("octocat"));
console.log("GitHub'ga so'rovlar:", calls);

Konsolda:

text
0s MISS ["octocat"]: kutamiz
octocat v1
600s HIT ["octocat"]
octocat v1
600s MISS ["aziz"]: kutamiz
aziz v2
4000s STALE ["octocat"]: eskisi, fonda yangilanadi
octocat v1
4000s HIT ["octocat"]
octocat v3
200000s MISS ["octocat"]: kutamiz
octocat v4
GitHub'ga so'rovlar: 4

Qatorma-qator:

Vaqt Nima bo'ldi
0 s Kesh bo'sh — kutib oldik (v1)
600 s 1 soatdan kam — keshdan, GitHub'ga so'rov yo'q
600 s, aziz Boshqa argument — boshqa kalit, alohida yozuv
4 000 s 1 soat o'tdi: eski v1 darhol berildi, fonda v3 olindi
4 000 s, yana Endi keshda v3
200 000 s 1 kundan (expire) oshdi — yangi qiymat kutildi

7 ta chaqiruvga 4 ta so'rov. Haqiqiy saytda farq ancha katta: soatiga minglab ko'rish — GitHub'ga soatiga bir-ikki so'rov.

4.4 Shartli muddat va TypeScript tuzog'i

Kanon kodida cacheLife ikki shoxda: ma'lumot bo'lsa "hours", zaxira (null) bo'lsa "minutes". Nega? GitHub bir lahzaga ishlamay qolsa, zaxira matn bir soat keshda qolib ketmasin — bir daqiqadan keyin yana urinib ko'riladi. Next.js hujjati buni "conditional cache lifetimes" deb ataydi: muhimi — bitta chaqiruvda faqat bitta cacheLife ishlashi.

Qisqaroq yozish istagi tabiiy: cacheLife(natija ? "hours" : "minutes"). Kanon shunday yozib ko'rgan va TypeScript rad etgan. Biz kanon nusxasida takrorladik (npx tsc --noEmit):

text
src/malumot/github.ts(51,13): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'string' is not assignable to parameter of type '{ stale?: number | undefined; revalidate?: number | undefined; expire?: number | undefined; }'.
      Type '"minutes"' has no properties in common with type '{ stale?: number | undefined; revalidate?: number | undefined; expire?: number | undefined; }'.

Tarjimasi: "Bu chaqiruvga birorta overload mos kelmadi. string turidagi argumentni { stale?, revalidate?, expire? } turiga berib bo'lmaydi". Sabab: cacheLife ning har profil nomi uchun alohida overload i bor (har biri aniq literal kutadi: "hours", "minutes"). Ternary ifodaning turi esa "hours" | "minutes" — bitta overloadga ham sig'maydi (Function overload). if/else da har chaqiruv bitta aniq literal oladi — TypeScript rozi.

5. Kanon natijasi: ◐ dan ○ ga

githubProfil keshlangach, GithubBlok dagi await connection() ham olib tashlandi — endi blok build vaqtida bir marta GitHub'ga boradi va natija HTML'ga tushadi:

diff
-import { connection } from "next/server";
 import { githubProfil } from "@/malumot/github";
 …
 export async function GithubBlok() {
-  await connection();
   const profil = await githubProfil(GITHUB_LOGIN);

Build jadvali (xom natijalar/tajriba/19-build.txt):

text
Route (app)                         Revalidate  Expire
├ ○ /haqimda                                1h      1d

◐ /haqimda → ○ /haqimda, va yangi ustunlar: 1h (revalidate) va 1d (expire) — "hours" profili butun sahifaga o'tdi. Sahifa endi to'liq statik, lekin soatda bir fonda yangilanadi.

Kanon brauzer o'lchovi (holat 16 → holat 19, next start, headless Chrome):

/haqimda: DOMContentLoaded
  • oqim (holat 16)GitHub kutiladi1 176 ms
  • use cache (holat 19)HTML tayyor56 ms

Manba: IlmHamroh kanoni, portfolio v2 tekshiruv-16 va tekshiruv-19, Chrome 155, 2026-10-21

TTFB 60 → 9 ms, DCL 1 176 → 56 ms. Javob sarlavhasi ham o'zgardi: avval cache-control: private, no-cache, no-store, endi s-maxage=3600, stale-while-revalidate=82800 — CDN ham sahifani bir soat saqlay oladi. JS hajmi o'zgarmadi (138.4 KB) — kesh butunlay serverda.

Uchala muammo yechildi: GitHub'ga soatiga ~2 so'rov (tashriflar soniga bog'liq emas), sahifa tez, JS o'chiq bo'lsa ham profil HTML'da.

5.1 GitHub ishlamaganda build

Kanon GitHub'ni "o'chirib" (GITHUB_API_URL=http://127.0.0.1:9) build qildi (natijalar/tajriba/19-build-githubsiz.txt):

text
GitHub profili olinmadi: fetch failed
…
├ ○ /haqimda                                1m      1h

Endi cacheLife("minutes") shoxi ishladi va sahifaning muddati ham 1m 1h ga tushdi. Ya'ni zaxira matn bilan build qilingan sayt bir daqiqadan keyin GitHub'ni qayta sinaydi. Shartli muddat butun sahifaga o'tadi — ichki keshning eng qisqa muddati tashqi sahifani ham "qisqartiradi".

Tekshirib ko'ring: blog postlari cacheLife("max") bilan keshlangan. Sahifa jadvalda 30d 1y ko'rsatadi. Post faylini tahrirlab, serverni qayta ishga tushirmasangiz, o'zgarish qachon ko'rinadi?

Javob

Hech qachon — yangi build'gacha. Postlar src/blog/ da, ular build vaqtida o'qilgan va kod bilan birga deploy qilingan. Kesh 30 kunda "yangilansa" ham, server o'sha eski fayllarni qayta o'qiydi. Kanon buni ongli qildi: postlar faqat deploy bilan o'zgaradi. Agar postlar CMS yoki bazada bo'lganda, max profili ostida ular 30 kungacha yangilanmay qolishi mumkin edi — u holda qo'lda yangilash kerak (Cache Components II).

6. "use cache" chegaralari

6.1 Biz topdik: cookies() keshlangan funksiya ichida

Keshlangan natija hamma foydalanuvchiga bitta. Agar funksiya ichida foydalanuvchiga xos narsa (cookie, sarlavha) o'qilsa, birinchi foydalanuvchining natijasi qolganlarga ham berilib ketardi. Next.js buni taqiqlaydi. Biz kanon nusxasida githubProfil ichiga cookies() ni qo'shib build qildik:

tsx
  "use cache";
  cacheTag("github");
  const tema = (await cookies()).get("tema")?.value; // ❌
text
Error: Route "/haqimda": `cookies()` can't be read inside `"use cache"`. Read it outside the cached function and pass what you need as an argument.
Learn more: https://nextjs.org/docs/messages/next-request-in-use-cache
    at k (src\malumot\github.ts:50:23)

Tarjimasi: "cookies() ni "use cache" ichida o'qib bo'lmaydi. Uni keshlangan funksiyadan tashqarida o'qing va keraklisini argument sifatida bering". Xuddi shu qoida headers() va searchParams uchun ham. Taqiq chaqiruvlar zanjiri bo'ylab ishlaydi: keshlangan funksiya chaqirgan yordamchi funksiya cookies() ni o'qisa ham — xato.

Hujjat bitta muhim ogohlantirish beradi: sahifa dinamik bo'lsa, bu xato build'da emas, next start da, so'rov paytida chiqadi. Biznikida /haqimda statik edi — shuning uchun build ushladi.

To'g'ri naqsh — xabar matnining o'zi aytgandek:

tsx
// ✅ cookie tashqarida o'qiladi, qiymat — argument (kalitga kiradi)
async function Sahifa() {
  const til = (await cookies()).get("til")?.value ?? "uz";
  const matn = await tarjimalar(til);
  // …
}

async function tarjimalar(til: string) {
  "use cache";
  cacheLife("days");
  // … til bo'yicha o'qish
}

Endi tarjimalar("uz") va tarjimalar("en") — ikki alohida kesh yozuvi, hech kimning ma'lumoti aralashmaydi.

6.2 "use cache: private" va "use cache: remote"

"use cache" ning ikki varianti bor. Kanon ularni ishlatmadi — quyidagisi Next.js 16.4 hujjati bo'yicha:

Direktiva Qayerda saqlanadi Qachon
"use cache" Server xotirasi (LRU) + build qobig'i Hamma uchun bir xil ma'lumot (kanon)
"use cache: private" Faqat bitta so'rov ichida va brauzer xotirasida cookies() ni ichida o'qish shart bo'lsa
"use cache: remote" Tashqi kesh (Redis, KV) Bir nechta server nusxasi bitta keshni bo'lishishi kerak

LRU (Least Recently Used — eng uzoq ishlatilmagan birinchi chiqariladi) — xotira to'lganda eski yozuvlarni o'chirish qoidasi (LRU kesh g'oyasi). Muhim narsa: sukut bo'yicha kesh bitta server jarayonining xotirasida. Kanon kabi bitta next start uchun bu yetarli. Serverless platformada (Vercel funksiyalari) har so'rov boshqa nusxaga tushishi mumkin — u yerda xotira keshi tez-tez bo'sh bo'ladi. Shunday paytda remote kerak bo'ladi, lekin u tarmoq kechikishi va platforma to'lovi bilan keladi.

Yangi deploy esa har qanday keshni tozalaydi: kalitda build ID bor (next.config da deploymentId berilsa — u).

7. cacheTag — keshga yorliq

Kanon kodida yana bir qator bor edi: cacheTag("github"). Teg (tag) — kesh yozuviga yopishtirilgan yorliq. Keyin "github yorlig'i bor hamma yozuvni yangila" deyish mumkin — bir soat kutmasdan. Masalan, GitHub'ga yangi loyiha qo'yib, portfolio'da darhol ko'rishni xohlaysiz.

Teg bo'yicha yangilash (revalidateTag, updateTag), yo'l bo'yicha yangilash va kanondagi /api/qayta-yangila — keyingi darsda.

8. Portfolio qadami: GitHub profili keshda

Bu dars holat 19 ning ikkinchi qismi. Commit — Cache Components II da. Hali commit qilmang.

Fayl O'zgarish
src/malumot/github.ts githubProfil → "use cache" + cacheTag("github") + shartli cacheLife; eski tana — profilniOl
haqimda/_komponentlar/GithubBlok.tsx await connection() olib tashlandi

Tekshiruv: npm run build jadvalida ○ /haqimda 1h 1d. npm run tip — 0 xato (if/else bilan). Keyin GITHUB_API_URL=http://127.0.0.1:9 npm run build — ○ /haqimda 1m 1h.

<Suspense> ni sahifadan olib tashlash shartmi? Yo'q — kanon uni qoldirdi. Build vaqtida kesh to'la bo'lgani uchun fallback hech qachon ko'rinmaydi. Lekin kesh muddati tugab, yangi qiymat kutilayotgan kamdan-kam holatda u yana foydali.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
uncached or runtime data during prerendering Keshlanmagan fetch/fayl, Suspense yo'q "use cache" yoki <Suspense>
TS2769: No overload matches this call cacheLife(shart ? "a" : "b") if/else bilan ikki chaqiruv
`cookies()` can't be read inside "use cache" Keshlangan funksiyada so'rov API'si Tashqarida o'qing, argument qilib bering
…inline "use cache" … in Client Components Client fayldagi keshlangan funksiya Alohida server faylga ko'chiring
Zaxira matn bir soat qolib ketdi Muvaffaqiyatsiz natija ham hours bilan Shartli muddat: null → minutes
Kesh "ishlamayapti", har so'rov yangi Argument har safar boshqa (new Date(), tasodifiy) Kalitga faqat barqaror qiymat
Serverless'da kesh tez-tez bo'sh Xotira keshi nusxalar orasida umumiy emas "use cache: remote" (narxi bilan)

10. Mashqlar

Mashqlar kurs/mashqlar/23/18-use-cache/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): profil tanlang

  • Valyuta kursi (har daqiqada o'zgaradi):
  • Foydalanish shartlari sahifasi (yilda bir marta):
  • Ob-havo (kuniga bir necha marta):
  • Haftalik podkast ro'yxati:
Yechim
  • minutes — 1 daqiqada fonda yangilanadi.
  • max — 30 kunda bir yangilanadi, yil davomida saqlanadi.
  • hours — hujjat ob-havoni aynan shu profilga misol qiladi.
  • weeks — haftada bir yangilanadi.

Birja narxi kabi "har soniyada" ma'lumot uchun seconds bor — lekin u statik qobiqqa kirmaydi (expire 1 daqiqa), dinamik teshik bo'ladi.

2-mashq (o'rta): kesh kaliti

«Kesh modeli» blokidagi cached funksiyasidan foydalanib, exchangeRate(from, to) ni keshlang (revalidate: 60, expire: 3600). Quyidagi chaqiruvlar ketma-ketligida nechta haqiqiy so'rov bo'ladi? Kodni yozib tekshiring: ("USD", "UZS"), ("USD", "UZS"), ("EUR", "UZS"), ("UZS", "USD"), vaqt 30 s, ("USD", "UZS").

Yechim
js
let time = 0;
let calls = 0;

function cached(fn, { revalidate, expire }) {
  const store = new Map();
  return async (...args) => {
    const key = JSON.stringify(args);
    const entry = store.get(key);
    const age = entry ? time - entry.at : Infinity;
    if (age < revalidate) return entry.value;
    if (age < expire) {
      fn(...args).then((v) => store.set(key, { value: v, at: time }));
      return entry.value;
    }
    const value = await fn(...args);
    store.set(key, { value, at: time });
    return value;
  };
}

const exchangeRate = cached(
  async (from, to) => {
    calls++;
    return `${from}→${to}`;
  },
  { revalidate: 60, expire: 3600 },
);

await exchangeRate("USD", "UZS");
await exchangeRate("USD", "UZS");
await exchangeRate("EUR", "UZS");
await exchangeRate("UZS", "USD");
time = 30;
await exchangeRate("USD", "UZS");
console.log("so'rovlar:", calls);

Konsolda:

text
so'rovlar: 3

["USD","UZS"], ["EUR","UZS"] va ["UZS","USD"] — uchta alohida kalit: argumentlar tartibi ham kalitning qismi. 30 s da USD→UZS hali yangi (60 s dan kam) — keshdan.

3-mashq (qiyin): shartli muddat

cached modelini o'zgartiring: fn qiymat bilan birga muddatni ham qaytarsin — { value, life: { revalidate, expire } } — va kesh yozuvi shu muddat bilan saqlansin (kanondagi if (natija) cacheLife("hours") else cacheLife("minutes") g'oyasi). Soxta githubProfile birinchi chaqiruvda null (GitHub ishlamayapti), keyin "octocat: 8 repo" qaytarsin. null uchun 60 s / 3600 s, qiymat uchun 3600 s / 86400 s. Vaqtlar: 0 s, 30 s, 90 s, 120 s — har birida natijani chiqaring.

Yechim
js
let time = 0;
let calls = 0;

function cached(fn) {
  const store = new Map();
  return async (...args) => {
    const key = JSON.stringify(args);
    const entry = store.get(key);
    const age = entry ? time - entry.at : Infinity;
    if (entry && age < entry.life.revalidate) return entry.value;
    if (entry && age < entry.life.expire) {
      fn(...args).then((r) => store.set(key, { ...r, at: time }));
      return entry.value;
    }
    const r = await fn(...args);
    store.set(key, { ...r, at: time });
    return r.value;
  };
}

const MINUTES = { revalidate: 60, expire: 3600 };
const HOURS = { revalidate: 3600, expire: 86400 };

const githubProfile = cached(async (login) => {
  calls++;
  const value = calls === 1 ? null : `${login}: 8 repo`;
  return { value, life: value ? HOURS : MINUTES };
});

for (const t of [0, 30, 90, 120]) {
  time = t;
  console.log(`${t}s`, await githubProfile("octocat"));
  await null;
}
console.log("so'rovlar:", calls);

Konsolda:

text
0s null
30s null
90s null
120s octocat: 8 repo
so'rovlar: 2

0 s — GitHub ishlamadi, null 1 daqiqalik muddat bilan saqlandi. 30 s — hali yangi. 90 s — 60 s o'tdi: eski null berildi, fonda qayta urinildi va endi haqiqiy qiymat HOURS bilan saqlandi. 120 s — yangi qiymat. null ham HOURS bilan saqlanganda, foydalanuvchilar bir soat zaxira matnni ko'rardi.

4-mashq: Portfolio qadami

github.ts ga "use cache", cacheTag("github") va shartli cacheLife ni qo'shing, GithubBlok dan connection() ni olib tashlang. npm run build — ○ /haqimda 1h 1d chiqishi kerak. Keyin cacheLife(natija ? "hours" : "minutes") deb yozib npm run tip ni ishga tushiring — TS2769 ni o'zingiz ko'ring va if/else ga qaytaring. Commit hali yo'q.

Yechim

Namuna — «"use cache" direktivasi» va «Kanon natijasi» bo'limlaridagi kod. next start bilan /haqimda ni ikki marta oching va terminalga qarang: GitHub'ga so'rov loglari endi har ochilishda chiqmaydi. DevTools → Network → haqimda javobining sarlavhalarida cache-control: s-maxage=3600, stale-while-revalidate=82800 va x-nextjs-cache: HIT ni toping.

11. Real ishda

  • Cache Components — Next 16 ning eng katta o'zgarishi. Yangi loyihalarda sukut bo'yicha yoqilgan, eski loyihalar unga bosqichma-bosqich o'tmoqda (Versiyalarni yangilash).
  • Keshni to'g'ri qo'yish — senior darajadagi ko'nikma: nima hamma uchun bir xil (keshlanadi), nima foydalanuvchiga xos (keshlanmaydi yoki argument bilan), qancha vaqt eskirgan ma'lumot qabul qilinadi.
  • Tashqi API limitlari (GitHub, to'lov tizimi kurslari, ob-havo) — kesh bilan hal qilinadigan eng ko'p uchraydigan muammo.
  • Intervyuda: "PPR nima?", "stale-while-revalidate qanday ishlaydi?", "Nega cookies() ni keshlangan funksiyada o'qib bo'lmaydi?".

Xulosa

  • Cache Components'da hamma narsa sukut bo'yicha dinamik; prerender keshlanmagan ma'lumotda to'xtaydi va uch yo'l taklif qiladi: oqim, kesh, blok.
  • "use cache" — funksiya, komponent yoki fayl boshida; kalit = build ID + funksiya + argumentlar.
  • cacheLife profillari stale / revalidate / expire ni beradi; eskisi darhol beriladi, yangisi fonda olinadi.
  • Kanon: GitHub profili keshlandi — ◐ → ○ /haqimda 1h 1d, DCL 1 176 → 56 ms, GitHub'ga soatiga ~2 so'rov.
  • Keshlangan funksiyada cookies()/headers() taqiqlangan — tashqarida o'qib, argument qiling.
  • PPR = statik qobiq + keshlangan qismlar + dinamik teshiklar; ○, ◐, ƒ belgilari shuni ko'rsatadi.

Keyingi dars: Cache Components II: revalidatsiya va yangilash — keshni bir soat kutmasdan, teg bo'yicha yangilaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js Cache Components: "use cache", cacheLife va Partial Prerendering — IlmHamroh