Chúng ta xây dựng comment section cho blog trong tutorial này️. Tech stack:
- NextJS 13 (App dir)
- NextAuth (Authentication)
- Upstash Redis (lưu comments)
- SWR (Caching và Revalidation comments)
Bắt đầu.
Xử Lý Auth Với NextAuth
Trước hết, không thể cho bất kỳ ai post comment, đúng không? Ai đó có thể chạy script spam comment blog bạn. Xây dựng hệ thống Authentication trước khi cho post comment. Dùng NextAuth.
Cài next-auth trong project.
pnpm install next-auth
Cấu trúc thư mục trong App Directory:
. ├── app
│ ├── api
│ │ └── auth
│ │ └── [...nextauth]
│ │ └── route.ts
│ ├── blog
│ │ ├── page.tsx
│ │ └── [...slug]
│ │ └── page.tsx
│ ├── components
│ │ └── LoginButton.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
Setup Auth API route:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
const handler = NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
callbacks: {
async session({ session, token }) {
if (session && session.user && token.sub) {
session.user.sub = token.sub;
}
return session;
},
},
});
export { handler as GET, handler as POST };
Tạo Github OAuth application mới từ đây lấy GITHUB_CLIENT_ID và GITHUB_CLIENT_SECRET.

NextAuth cho phép Sign in và SignOut từ bất kỳ Client Component nào dùng function signIn và signOut từ next-auth. Trước đó, setup Context Provider wrap toàn bộ ứng dụng.
// app/layout.tsx
"use client";
import "./globals.css";
import { SessionProvider } from "next-auth/react";
export default function RootLayout({ children, }: { children: React.ReactNode; }) {
return (
<html lang="en">
<SessionProvider>
<body>{children}</body>
</SessionProvider>
</html>
);
}
SessionProvider cho phép truy cập session state từ bất kỳ Client component nào.
Truy cập session state dùng hook useSession từ next-auth. Sample Login Button:
"use Client";
import { signIn, signOut, useSession } from "next-auth/react";
export default function LoginButton() {
const { data: session } = useSession();
if (session) {
return (
<div>
Signed in as {session.user?.name} using {session.user?.email}
<br />
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
return (
<div>
Not signed in
<br />
<button onClick={() => signIn()}> Sign in </button>
</div>
);
}
Với này, Auth system ready. Bây giờ bắt đầu server-side routes lưu comment trong Redis database.
Thiết Lập Redis Database
- Vào Upstash tạo Redis Database.
- Chọn region gần user và bật TLS encryption.
- Cài package
@upstash/redis.
pnpm install @upstash/redis

- Copy tokens vào
.env.local.
Viết API endpoints trong cấu trúc thư mục:
. ├── app
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ └── route.ts
│ │ ├── comment
│ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ ├── get
│ │ │ │ └── route.ts
│ │ │ └── post
│ │ │ └── route.ts
│ │ └── lib
│ │ ├── getUser.ts
│ │ └── redis.ts
- Tạo Redis Client Instance
// app/api/lib/redis.ts
import { Redis } from "@upstash/redis";
/* Thử load UPSTASH_REDIS_REST_URL và UPSTASH_REDIS_REST_TOKEN từ environment dùng process.env */
const redis = Redis.fromEnv();
export default redis;
Đó là xong. Redis Client setup xong, chỉ còn setup API routes cho nó trong ứng dụng.
Dùng Redis Lists lưu comments. Chúng act như stack nên comment mới nhất hiển thị top. Chắc chắn có thể implement sorting logic client-side nhưng Redis đã cung cấp data structure này, dùng nó.
Xem toàn bộ code tại đây. Tóm tắt:
-
Tạo comment:
redis.lpush(referer, comment). Push comment vào list có keyreferer -
Lấy tất cả comment
redis.lrange(referer, 0, -1) -
Xóa comment
redis.lrem(referer, 0, comment). Xóa tất cả occurrence củacommenttrong list keyreferer.
API ready. Tích hợp frontend với backend.
Thiết Lập Client Side
Không khó. Chỉ cần hit endpoints vừa tạo dùng fetch. Dùng library swr cho caching và revalidation comments. Tự do dùng React Query hoặc thư viện khác.
Tạo hook useComment() có handler onSubmit() và onDelete() làm request.
"use client";
import { useState } from "react";
import type { Comment } from "@/app/interfaces/interfaces";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((res) => res.json());
const useComment = () => {
const [text, setText] = useState("");
const { data: comments, mutate } = useSWR<Comment[]>(
"/api/comment/get",
fetcher,
{ fallbackData: [], },
);
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
await fetch("/api/comment/post", {
method: "POST",
body: JSON.stringify({ text }),
headers: { "Content-Type": "application/json", },
});
setText("");
await mutate();
} catch (error) {
console.log(error);
}
};
const onDelete = async (comment: Comment) => {
try {
await fetch("/api/comment/delete", {
method: "POST",
body: JSON.stringify({ comment }),
headers: { "Content-Type": "application/json", },
});
await mutate();
} catch (error) {
console.log(error);
}
};
return { text, setText, comments, onSubmit, onDelete };
};
export default useComment;
Comment section xong. Fetch, post, delete comments được. Bước tiếp tạo component Comment box và comments list. Complete sample tại đây.