Next.js / Эцсийн төсөл

Эцсийн төсөл

Та Next.js курсын 39 хичээлийг дуусгалаа — App Router, Server Component, data fetching, Supabase, deploy, security, performance бүгдийг сурлаа. Одоо тэр мэдлэгийг нэг бүрэн апп болгох цаг болсон. Эцсийн төсөлд хувийн блог платформ хийнэ — нийтлэл бичих, уншигчид сэтгэгдэл үлдээх, зохиогч нэвтрэх боломжтой fullstack апп. Энэ бол жинхэнэ ажлын байранд шаарддаг чадварыг дадлагажуулах хамгийн сайн арга юм.

Төслийн тойм

Бүтээх аппын онцлог:

| Feature | Технологи | | ------------------ | ---------------------------------- | | Нийтлэл харах | Server Component, Markdown | | Нийтлэл хайх | URL searchParams, Server Component | | Сэтгэгдэл үлдээх | Server Action, Supabase | | Зохиогч нэвтрэх | Supabase Auth | | Шинэ нийтлэл нэмэх | Server Action, form | | Deploy | Vercel, custom domain |

Файлын бүтэц:

код
app/
├── page.tsx                    ← Нийтлэлийн жагсаалт
├── posts/
│   └── [slug]/
│       └── page.tsx            ← Нэг нийтлэл + сэтгэгдэл
├── dashboard/
│   ├── page.tsx                ← Зохиогчийн хяналтын самбар
│   └── new/
│       └── page.tsx            ← Шинэ нийтлэл үүсгэх
├── (auth)/
│   ├── login/page.tsx
│   └── register/page.tsx
└── api/
    └── comments/route.ts

Supabase schema

sql
create table posts (
  id uuid default gen_random_uuid() primary key,
  author_id uuid references profiles(id) on delete cascade,
  slug text unique not null,
  title text not null,
  content text not null,
  published boolean default false,
  created_at timestamptz default now()
);

create table comments (
  id uuid default gen_random_uuid() primary key,
  post_id uuid references posts(id) on delete cascade,
  user_id uuid references profiles(id) on delete cascade,
  body text not null,
  created_at timestamptz default now()
);

alter table posts    enable row level security;
alter table comments enable row level security;

-- Хэн ч нийтлэгдсэн нийтлэл унших боломжтой
create policy "Нийтлэгдсэн нийтлэл нийтэд харагдана"
  on posts for select
  using (published = true);

-- Зохиогч өөрийн бүх нийтлэл харна
create policy "Зохиогч өөрийн нийтлэл харна"
  on posts for select
  using (auth.uid() = author_id);

-- Зохиогч нийтлэл үүсгэнэ
create policy "Зохиогч нийтлэл нэмнэ"
  on posts for insert
  with check (auth.uid() = author_id);

-- Нэвтэрсэн хэрэглэгч сэтгэгдэл нэмнэ
create policy "Нэвтэрсэн хэрэглэгч сэтгэгдэл нэмнэ"
  on comments for insert
  with check (auth.uid() = user_id);

create policy "Сэтгэгдэл нийтэд харагдана"
  on comments for select
  using (true);

Нийтлэлийн жагсаалт хуудас

tsx
// app/page.tsx
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import Link from "next/link";

interface Post {
  id: string;
  slug: string;
  title: string;
  created_at: string;
  profiles: { username: string };
}

export default async function HomePage() {
  const cookieStore = await cookies();
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: { getAll: () => cookieStore.getAll() } },
  );

  const { data: posts } = await supabase
    .from("posts")
    .select("id, slug, title, created_at, profiles(username)")
    .eq("published", true)
    .order("created_at", { ascending: false })
    .returns<Post[]>();

  return (
    <main className="max-w-2xl mx-auto px-4 py-12">
      <h1 className="text-3xl font-bold text-[#f1f5f9] mb-8">Нийтлэлүүд</h1>
      <ul className="space-y-6">
        {posts?.map((post) => (
          <li key={post.id} className="border border-[#1e293b] rounded-xl p-6">
            <Link
              href={`/posts/${post.slug}`}
              className="text-xl font-semibold text-[#f1f5f9] hover:text-[#818cf8]"
            >
              {post.title}
            </Link>
            <p className="text-[#94a3b8] text-sm mt-2">
              {post.profiles.username} ·{" "}
              {new Date(post.created_at).toLocaleDateString("mn-MN")}
            </p>
          </li>
        ))}
      </ul>
    </main>
  );
}

Сэтгэгдэл нэмэх Server Action

typescript
// app/actions/comments.ts
"use server";

import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { revalidatePath } from "next/cache";

interface AddCommentInput {
  postId: string;
  postSlug: string;
  body: string;
}

interface ActionResult {
  success: boolean;
  error?: string;
}

export async function addComment(
  input: AddCommentInput,
): Promise<ActionResult> {
  const { postId, postSlug, body } = input;

  if (!body.trim() || body.length > 1000) {
    return { success: false, error: "Сэтгэгдэл хоосон эсвэл хэт урт байна" };
  }

  const cookieStore = await cookies();
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: { getAll: () => cookieStore.getAll() } },
  );

  const {
    data: { session },
  } = await supabase.auth.getSession();
  if (!session) return { success: false, error: "Нэвтрэх шаардлагатай" };

  const { error } = await supabase.from("comments").insert({
    post_id: postId,
    user_id: session.user.id,
    body: body.trim(),
  });

  if (error) return { success: false, error: "Сэтгэгдэл нэмэхэд алдаа гарлаа" };

  revalidatePath(`/posts/${postSlug}`);
  return { success: true };
}

Эцсийн шатлал ба deploy

Төслөө дуусгахдаа дараах дарааллыг дагана:

bash
# 1. Орон нутагт бүрэн ажиллахыг шалгана
npm run dev

# 2. TypeScript алдаа шалгана
npx tsc --noEmit

# 3. Build амжилттай болохыг шалгана
npm run build

# 4. Git-д commit хийнэ
git add .
git commit -m "feat: final project complete"
git push origin main

# 5. Vercel автоматаар deploy хийнэ
# Dashboard-д build log-г хянана

Амжилттай deploy болсны дараа:

  • Custom domain тохируулна
  • Environment variable production орчинд зөв байгааг шалгана
  • Analytics болон Speed Insights идэвхжүүлнэ
  • Нэг найздаа холбоосыг явуулж туршина

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

Та Next.js курсыг бүрэн дуусгалаа! App Router, Server Component, Supabase, deploy, security, performance — бүгдийг эзэмшлээ. Дараагийн алхам: React Native курсаар гар утасны апп хөгжүүлэх эсвэл TypeScript курсаар type системийг гүнзгийрүүлэн судлах. Кодлосоор байгаарай — туршлага хуримтлагдах тусам бүх зүйл илүү хялбар болдог.