Next.js кэш систем
Next.js-н хамгийн хүчтэй, мөн хамгийн ойлгоход хэцүү сэдвүүдийн нэг бол кэш систем. Зөв ашиглавал апп маш хурдан ажиллана — нэг хүсэлтийн үр дүнг хадгалж, дараагийн хүсэлтэд дахин тооцоолохгүйгээр шууд буцаана. Энэ хичээлд кэшний үндсэн ойлголтуудыг практик жишээгээр ойлгоно.
Кэш гэж юу вэ?
Сурагч /courses хуудас нээх бүрт сервер файлуудыг унших, JSON задлан шинжлэх, HTML үүсгэх ажиллагааг давтан хийх шаардлагагүй. Нэг удаа үүсгэсэн үр дүнг кэш-д хадгалж, дараагийн хүсэлтэд тэрийг л буцаана.
Эхний хүсэлт: хэрэглэгч → сервер → файл унших → HTML үүсгэх → кэш хадгалах → хэрэглэгч
Дараагийн хүсэлт: хэрэглэгч → кэш → хэрэглэгч (сервер хүртэл очдоггүй!)
fetch кэш
Next.js-н fetch нь анхнаасаа кэшлэдэг. cache болон next.revalidate сонголтоор зан үйлийг тохируулна:
// lib/api.ts
// 1. Анхны кэш — нэг удаа татаж хадгална (build дээр)
const staticData = await fetch("https://api.example.com/courses", {
cache: "force-cache", // Анхны утга, заримдаа дурдахгүй ч ижил
});
// 2. Кэшлэхгүй — дүр бүрт шинэ өгөгдөл татна
const freshData = await fetch("https://api.example.com/notifications", {
cache: "no-store",
});
// 3. Тогтмол хугацааны дараа шинэчлэнэ (ISR)
const revalidatedData = await fetch("https://api.example.com/leaderboard", {
next: { revalidate: 60 }, // 60 секундэд нэг удаа шинэчлэнэ
});
| Сонголт | Зан үйл | Хэзээ ашиглах |
| ------------------------- | ------------------------- | --------------------------- |
| cache: 'force-cache' | Build дээр нэг удаа татна | Статик агуулга |
| cache: 'no-store' | Дүр бүрт татна | Бодит цагийн өгөгдөл |
| next: { revalidate: N } | N секундэд нэг удаа | Тогтмол шинэчлэгдэх өгөгдөл |
Route segment кэш — revalidate экспорт
fetch-д биш харин хуудас бүхэлдээ хэр олон шинэчлэгдэхийг тохируулахад revalidate константыг экспортолно:
// app/courses/page.tsx
import { getAllCourses } from "@/lib/courses";
// Бүх энэ хуудасны fetch дуудлагууд 3600 секундэд нэг удаа шинэчлэгдэнэ
export const revalidate = 3600; // 1 цаг
export default async function CoursesPage() {
const courses = await getAllCourses(); // Файл системээс унших — кэшлэгдэнэ
return (
<main>
{courses.map((c) => (
<div key={c.slug}>{c.title}</div>
))}
</main>
);
}
// app/leaderboard/page.tsx
// Энэ хуудас кэшлэгдэхгүй — дүр бүрт шинэ өгөгдөл татна
export const dynamic = "force-dynamic";
export default async function LeaderboardPage() {
// ...
}
unstable_cache — Функц кэшлэх
fetch биш харин өөрийн функцийг кэшлэхэд unstable_cache ашиглана. Файл системийн функц, Supabase query зэрэгт тохиромжтой:
// lib/courses.ts
import { unstable_cache } from "next/cache";
import { readFile, readdir } from "fs/promises";
import path from "path";
export const getAllCourses = unstable_cache(
async () => {
const coursesDir = path.join(process.cwd(), "content/courses");
const slugs = await readdir(coursesDir);
return Promise.all(
slugs.map(async (slug) => {
const raw = await readFile(
path.join(coursesDir, slug, "course.json"),
"utf-8",
);
return JSON.parse(raw);
}),
);
},
["all-courses"], // Кэшний түлхүүр
{ revalidate: 3600 }, // 1 цагт нэг удаа шинэчлэнэ
);
// Supabase query кэшлэх
import { createClient } from "@/lib/supabase/server";
export const getTopUsers = unstable_cache(
async () => {
const supabase = await createClient();
const { data } = await supabase
.from("profiles")
.select("username, xp")
.order("xp", { ascending: false })
.limit(10);
return data ?? [];
},
["top-users"],
{ revalidate: 300 }, // 5 минутад нэг удаа
);
revalidatePath — Кэш гараар цэвэрлэх
Хэрэглэгч шинэ хичээл дуусгасны дараа /profile хуудасны кэш шалтгаалсан кэшийг цэвэрлэж болно. Server Action болон API route-д ашигладаг:
// app/api/progress/route.ts
import { revalidatePath } from "next/cache";
import { createClient } from "@/lib/supabase/server";
export async function POST(request: Request) {
const { courseSlug, lessonSlug } = await request.json();
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return Response.json({ error: "Нэвтрэх шаардлагатай" }, { status: 401 });
}
await supabase.from("lesson_progress").upsert({
user_id: user.id,
course_slug: courseSlug,
lesson_slug: lessonSlug,
});
await supabase.rpc("increment_xp", { user_id: user.id, amount: 10 });
// Профайл хуудасны кэш цэвэрлэнэ — дараагийн зочлоход шинэ өгөгдөл татна
revalidatePath("/profile");
return Response.json({ success: true });
}
Кэш тохиргоог сонгох нь
Агуулга хэзээ өөрчлөгддөг вэ?
│
├── Хэзээ ч өөрчлөгддөггүй (статик текст, зураг)
│ └── cache: 'force-cache' эсвэл revalidate: false
│
├── Тогтмол хугацааны дараа өөрчлөгддөг (хичээлийн жагсаалт)
│ └── next: { revalidate: 3600 }
│
├── Хэрэглэгч үйлдэл хийсний дараа өөрчлөгддөг (профайл, явц)
│ └── revalidatePath() ашиглах
│
└── Дүр бүрт өөр байх ёстой (хэрэглэгчийн тусгай өгөгдөл)
└── cache: 'no-store' эсвэл dynamic: 'force-dynamic'
Дараагийн хичээлд:
ISR (Incremental Static Regeneration) ба revalidation-г нарийвчлан судална. revalidatePath, revalidateTag ашиглан кэшийг нарийн удирдах аргыг ойлгоно.