Server дээр өгөгдөл татах
Next.js-н App Router-н хамгийн хүчтэй тал бол Server Component дотор шууд өгөгдөл татах чадвар. useEffect дотор fetch дуудах, loading state удирдах шаардлагагүй — async/await ашиглан шууд бичнэ. Энэ хичээлд server дата татах бүх арга замыг судална.
async Server Component
Server Component нь async функц байж болно. Ингэснээр await шууд component дотор ашиглах боломжтой:
// app/courses/page.tsx
import { getAllCourses } from "@/lib/courses";
export default async function CoursesPage() {
// Сервер дээр ажиллана — хөтөч рүү нэмэлт JS очдоггүй
const courses = await getAllCourses();
return (
<main className="max-w-6xl mx-auto px-4 py-8">
<h1 className="text-2xl font-bold text-white mb-6">Сургалтууд</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{courses.map((course) => (
<div
key={course.slug}
className="bg-[#0f172a] border border-[#1e293b] rounded-xl p-5"
>
<h2 className="text-white font-semibold">{course.title}</h2>
<p className="text-slate-400 text-sm mt-1">{course.description}</p>
</div>
))}
</div>
</main>
);
}
Файл системээс унших
content/ директорт хадгалсан markdown, JSON файлуудыг Server Component-с шууд унших боломжтой:
// lib/courses.ts
import { readFile, readdir } from "fs/promises";
import path from "path";
export interface Lesson {
slug: string;
title: string;
order: number;
}
export interface Course {
slug: string;
title: string;
description: string;
color: string;
isFree: boolean;
lessons: Lesson[];
}
export async function getAllCourses(): Promise<Course[]> {
const coursesDir = path.join(process.cwd(), "content/courses");
const slugs = await readdir(coursesDir);
const courses = await Promise.all(
slugs.map(async (slug) => {
const jsonPath = path.join(coursesDir, slug, "course.json");
const raw = await readFile(jsonPath, "utf-8");
return JSON.parse(raw) as Course;
}),
);
return courses.sort((a, b) => a.slug.localeCompare(b.slug));
}
export async function getCourse(slug: string): Promise<Course | null> {
try {
const jsonPath = path.join(
process.cwd(),
"content/courses",
slug,
"course.json",
);
const raw = await readFile(jsonPath, "utf-8");
return JSON.parse(raw) as Course;
} catch {
return null; // Файл олдоогүй
}
}
export async function getLessonContent(
courseSlug: string,
lessonSlug: string,
): Promise<string | null> {
try {
const mdPath = path.join(
process.cwd(),
"content/courses",
courseSlug,
"lessons",
lessonSlug,
"lesson.md",
);
return await readFile(mdPath, "utf-8");
} catch {
return null;
}
}
fetch ашиглан гадаад API-с татах
Next.js-н fetch нь Web стандарт fetch-г өргөтгөж кэшлэх боломж нэмсэн:
// app/page.tsx
interface GithubRepo {
name: string;
stargazers_count: number;
html_url: string;
}
export default async function HomePage() {
// fetch — сервер дээр ажиллана
const res = await fetch("https://api.github.com/users/vercel/repos", {
next: { revalidate: 3600 }, // 1 цагт нэг удаа шинэчлэнэ
});
if (!res.ok) {
throw new Error("GitHub API-с өгөгдөл татахад алдаа гарлаа");
}
const repos: GithubRepo[] = await res.json();
return (
<ul>
{repos.slice(0, 5).map((repo) => (
<li key={repo.name}>
<a href={repo.html_url} className="text-indigo-400">
{repo.name}
</a>
<span className="text-slate-400 ml-2">
⭐ {repo.stargazers_count}
</span>
</li>
))}
</ul>
);
}
Supabase-с шууд татах
Server Component дотор Supabase client-г шууд ашиглаж болно — хамгийн энгийн, аюулгүй арга:
// app/profile/page.tsx
import { createClient } from "@/lib/supabase/server";
import { redirect } from "next/navigation";
export default async function ProfilePage() {
const supabase = await createClient();
// Хэрэглэгчийн мэдээлэл авах
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
redirect("/login");
}
// Профайл ба явц нэгэн зэрэг татах
const [{ data: profile }, { data: progress }] = await Promise.all([
supabase
.from("profiles")
.select("username, xp, streak")
.eq("id", user.id)
.single(),
supabase
.from("lesson_progress")
.select("course_slug, lesson_slug, completed_at")
.eq("user_id", user.id)
.order("completed_at", { ascending: false })
.limit(5),
]);
return (
<main className="max-w-2xl mx-auto px-4 py-8">
<h1 className="text-2xl font-bold text-white">
Сайн уу, {profile?.username}!
</h1>
<p className="text-slate-400 mt-1">
XP: {profile?.xp} · Streak: {profile?.streak} өдөр
</p>
<h2 className="text-white font-semibold mt-8 mb-3">
Сүүлийн үйл ажиллагаа
</h2>
<ul className="space-y-2">
{progress?.map((p) => (
<li
key={`${p.course_slug}-${p.lesson_slug}`}
className="text-slate-400 text-sm"
>
{p.course_slug} / {p.lesson_slug}
</li>
))}
</ul>
</main>
);
}
Promise.all — Зэрэгцээ татах
Хэд хэдэн өгөгдлийг нэг нэгээр татвал удаашрана. Promise.all ашиглан зэрэгцээ татахад нийт хугацаа богиносно:
// ❌ Удаан — дараалал: 200мс + 150мс + 100мс = 450мс
const course = await getCourse(courseSlug);
const lessons = await getLessons(courseSlug);
const progress = await getProgress(userId, courseSlug);
// ✅ Хурдан — зэрэгцэн: max(200, 150, 100) = 200мс
const [course, lessons, progress] = await Promise.all([
getCourse(courseSlug),
getLessons(courseSlug),
getProgress(userId, courseSlug),
]);
Дараагийн хичээлд:
Client дээр өгөгдөл татах аргыг судална. useState + useEffect ашиглах сонгодог арга болон хөтчөөс API route дуудах загварыг жишээгээр ойлгоно.