Next.js / Loading UI ба Suspense

Loading UI ба Suspense

Server Component-д өгөгдөл татахад тодорхой хугацаа шаарддаг. Тэр хугацаанд хэрэглэгч хоосон дэлгэц харвал муу туршлага болно. Next.js-д loading.tsx файл болон React-н Suspense ашиглан уншиж байгаа үеийн UI-г хялбархан харуулж болно.

loading.tsx — Автомат loading UI

page.tsx-тай нэг директорт loading.tsx нэмэхэд Next.js тухайн хуудас ачаалагдаж байх үед автоматаар харуулдаг:

код
app/
└── courses/
    ├── page.tsx         ← Өгөгдөл татдаг хуудас
    └── loading.tsx      ← Татаж байх үеийн UI
tsx
// app/courses/loading.tsx
export default function CoursesLoading() {
  return (
    <div className="max-w-6xl mx-auto px-4 py-8">
      <div className="h-8 w-48 bg-[#1e293b] rounded animate-pulse mb-6" />
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
        {Array.from({ length: 6 }).map((_, i) => (
          <div
            key={i}
            className="h-40 bg-[#0f172a] border border-[#1e293b] rounded-xl animate-pulse"
          />
        ))}
      </div>
    </div>
  );
}

animate-pulse Tailwind класс нь элементийг мигаруулж "skeleton" эффект үүсгэдэг. Хэрэглэгч хуудас руу шилжих мөчид энэ skeleton харагдаж, өгөгдөл ирмэгц бодит агуулгаар солигдоно.

loading.tsx хэрхэн ажилладаг вэ?

Next.js loading.tsx-г автоматаар Suspense boundary болгон ашигладаг. Дотроосоо ийм харагдана:

tsx
// Next.js дотооддоо ингэж хийдэг — өөрөө бичих шаардлагагүй
<Suspense fallback={<CoursesLoading />}>
  <CoursesPage />
</Suspense>

page.tsxasync функц өгөгдөл татаж дуустал loading.tsx харагддаг. Маш энгийн, маш хүчтэй!

React Suspense шууд ашиглах

Нэг хуудасны зарим хэсэг удаан, зарим нь хурдан ачаалдаг тохиолдолд <Suspense> шууд ашиглана. Ингэснээр хурдан хэсгүүд нь нэн даруй харагдаж, удаан хэсэг нь ачаалагдаж байна:

tsx
// app/courses/[courseSlug]/page.tsx
import { Suspense } from "react";
import LessonList from "@/components/lesson/LessonList";
import CourseStats from "@/components/CourseStats";

interface Props {
  params: Promise<{ courseSlug: string }>;
}

export default async function CoursePage({ params }: Props) {
  const { courseSlug } = await params;

  return (
    <main className="p-6">
      {/* Энэ хэсэг шууд харагдана */}
      <h1 className="text-2xl font-bold text-white">{courseSlug} курс</h1>

      {/* CourseStats удаан татагддаг — дангаар нь Suspense-д оруулна */}
      <Suspense fallback={<StatsLoading />}>
        <CourseStats courseSlug={courseSlug} />
      </Suspense>

      {/* LessonList тусдаа ачаалагдана */}
      <Suspense fallback={<LessonsLoading />}>
        <LessonList courseSlug={courseSlug} />
      </Suspense>
    </main>
  );
}

function StatsLoading() {
  return (
    <div className="flex gap-4 my-4">
      {[1, 2, 3].map((i) => (
        <div
          key={i}
          className="h-16 w-24 bg-[#1e293b] rounded-lg animate-pulse"
        />
      ))}
    </div>
  );
}

function LessonsLoading() {
  return (
    <div className="space-y-2 mt-4">
      {Array.from({ length: 5 }).map((_, i) => (
        <div
          key={i}
          className="h-12 bg-[#0f172a] border border-[#1e293b] rounded-lg animate-pulse"
        />
      ))}
    </div>
  );
}

Ийм байдлаар хуудасны хэсгүүд нь зэрэгцэн ачаалагдана — нийт хүлээх хугацаа богиносно.

Skeleton component дахин ашиглах

Skeleton UI-г дахин ашиглах боломжтой component болгож бичих нь зөв загвар:

tsx
// components/ui/LoadingSpinner.tsx
export default function LoadingSpinner() {
  return (
    <div className="flex items-center justify-center py-12">
      <div className="w-8 h-8 border-2 border-[#1e293b] border-t-indigo-400 rounded-full animate-spin" />
    </div>
  );
}
tsx
// components/ui/SkeletonCard.tsx
export default function SkeletonCard() {
  return (
    <div className="bg-[#0f172a] border border-[#1e293b] rounded-xl p-5 animate-pulse">
      <div className="h-4 w-16 bg-[#1e293b] rounded mb-3" />
      <div className="h-6 w-3/4 bg-[#1e293b] rounded mb-2" />
      <div className="h-4 w-full bg-[#1e293b] rounded" />
      <div className="h-4 w-2/3 bg-[#1e293b] rounded mt-1" />
    </div>
  );
}
tsx
// app/courses/loading.tsx — Дахин ашиглаж болно
import SkeletonCard from "@/components/ui/SkeletonCard";

export default function CoursesLoading() {
  return (
    <div className="max-w-6xl mx-auto px-4 py-8 grid grid-cols-1 md:grid-cols-3 gap-4">
      {Array.from({ length: 9 }).map((_, i) => (
        <SkeletonCard key={i} />
      ))}
    </div>
  );
}

loading.tsx давхарлалт

Layout шиг loading.tsx мөн давхарлагдана. /courses/javascript хаягаар ороход дараах дарааллаар хайна:

код
app/courses/[courseSlug]/loading.tsx  ← Эхлээд энд хайна
app/courses/loading.tsx               ← Байхгүй бол энд
app/loading.tsx                       ← Байхгүй бол root-д

Хурдан ачаалагддаг хуудасд loading.tsx нэмэх шаардлагагүй — зөвхөн өгөгдөл татдаг удаан хуудсуудад л нэм.

Дараагийн хичээлд:

Error UI судална. error.tsx файл ашиглан сервер дээр гарсан алдааг хэрэглэгчид ойлгомжтой байдлаар харуулж, дахин оролдох боломж олгох аргыг ойлгоно.