Next.js / Core Web Vitals ба оновчлол

Core Web Vitals ба оновчлол

Core Web Vitals бол Google-ийн вэб хуудасны гүйцэтгэлийг хэмждэг стандарт үзүүлэлт юм. Эдгээр үзүүлэлт нь хайлтын байр суурьт шууд нөлөөлдөг — гүйцэтгэл муутай сайт Google-д доор харагдана. Next.js нь Core Web Vitals-г сайжруулахад зориулсан олон built-in хэрэгсэлтэй тул та тэдгээрийг зөв хэрэглэж сурах хэрэгтэй.

Core Web Vitals гэж юу вэ?

| Үзүүлэлт | Нэр | Зорилт | | -------- | ------------------------- | ------------ | | LCP | Largest Contentful Paint | < 2.5 секунд | | INP | Interaction to Next Paint | < 200ms | | CLS | Cumulative Layout Shift | < 0.1 |

LCP — Хуудасны хамгийн том элемент (ихэвчлэн banner зураг эсвэл h1) хэдий хурдан харагдах вэ.

INP — Хэрэглэгч товшиход хуудас хэр хурдан хариу үйлдэл үзүүлэх вэ.

CLS — Хуудасны элементүүд ачааллах явцад хэр хөдөлдөг вэ (текст гэнэт доошоо унах зэрэг).

next/image ашиглан зураг оновчлох

Зураг бол LCP-н хамгийн гол шалтгаан. next/image нь автоматаар зургийг оновчилдог:

tsx
// components/lesson/LessonHero.tsx
import Image from "next/image";

interface LessonHeroProps {
  src: string;
  alt: string;
}

export default function LessonHero({ src, alt }: LessonHeroProps) {
  return (
    <div className="relative w-full h-64">
      <Image
        src={src}
        alt={alt}
        fill
        priority // LCP зураг бол priority нэм
        sizes="(max-width: 768px) 100vw, 50vw"
        className="object-cover rounded-xl"
      />
    </div>
  );
}

Зургийн хэмжээг урьдчилан тодорхойлж CLS-г бууруулна:

tsx
// Хэмжээ тодорхой байвал width/height заана
<Image
  src="/course-banner.png"
  alt="JavaScript курс"
  width={800}
  height={400}
  loading="lazy" // LCP биш зурагт lazy ашиглана
/>

next/font ашиглан фонт оновчлох

Google Fonts шууд ашиглахад layout shift үүсдэг. next/font/google энийг засдаг:

typescript
// app/layout.tsx
import { GeistSans, GeistMono } from 'next/font/google';

const geistSans = GeistSans({
  subsets: ['latin'],
  variable: '--font-geist-sans',
  display: 'swap',       // фонт ачаалах үед fallback харуулна
});

const geistMono = GeistMono({
  subsets: ['latin'],
  variable: '--font-geist-mono',
  display: 'swap',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="mn" className={`${geistSans.variable} ${geistMono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

Suspense болон streaming ашиглах

Удаан өгөгдөл татах хэсгийг Suspense-д орооход хуудас хурдан ачаалагддаг:

tsx
// app/courses/page.tsx
import { Suspense } from "react";
import CourseList from "@/components/CourseList";
import LoadingSpinner from "@/components/ui/LoadingSpinner";

export default function CoursesPage() {
  return (
    <main>
      <h1 className="text-2xl font-bold mb-6">Сургалтууд</h1>
      <Suspense fallback={<LoadingSpinner />}>
        <CourseList />
      </Suspense>
    </main>
  );
}
tsx
// app/courses/[courseSlug]/page.tsx
import { Suspense } from "react";

export default async function CoursePage({
  params,
}: {
  params: Promise<{ courseSlug: string }>;
}) {
  const { courseSlug } = await params;

  return (
    <div>
      <Suspense fallback={<p className="text-[#94a3b8]">Ачааллаж байна...</p>}>
        <LessonProgress courseSlug={courseSlug} />
      </Suspense>
    </div>
  );
}

Bundle хэмжээ хянах

Initial JS bundle 200KB-аас бага байх ёстой. next build ажиллуулахад bundle хэмжээ харагдана:

bash
npm run build

Том dependency-г dynamic import-оор lazy load хийнэ:

typescript
// components/lesson/LessonPanel.tsx
'use client';

import dynamic from 'next/dynamic';

// react-markdown нь том library — dynamic import хийнэ
const ReactMarkdown = dynamic(() => import('react-markdown'), {
  loading: () => <p className="text-[#94a3b8]">Ачааллаж байна...</p>,
});

export default function LessonPanel({ content }: { content: string }) {
  return <ReactMarkdown>{content}</ReactMarkdown>;
}

Metadata оновчлох

SEO болон LCP-д зориулж metadata тохируулна:

typescript
// app/courses/[courseSlug]/page.tsx
import { Metadata } from "next";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ courseSlug: string }>;
}): Promise<Metadata> {
  const { courseSlug } = await params;
  const course = await getCourse(courseSlug);

  return {
    title: `${course?.title} | Ulaanbaatar.app`,
    description: course?.description,
    openGraph: {
      title: course?.title,
      description: course?.description,
    },
  };
}

Vercel Analytics болон Speed Insights нэмж хуудасны гүйцэтгэлийг бодит цагаар хянаж болно — хоёулаа үнэгүй.

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

Next.js аппын аюулгүй байдлыг хангах аргуудыг сурна. XSS, CSRF, environment variable-н аюулгүй байдал, Supabase Row Level Security зэрэг чухал сэдвийг авч үзнэ.