เราจะสร้าง Comment Section สำหรับ Blog ใน Tutorial นี้ Tech Stack:
- NextJS 13 (App Directory)
- NextAuth (Authentication)
- Upstash Redis (Store Comments)
- SWR (Caching และ Revalidation Comments)
Let's Start
Handling Auth with NextAuth - จัดการ Auth ด้วย NextAuth
ไม่สามารถให้ใครก็ได้ Post Comment ได้ สมควรมี Authentication System ก่อน ใช้ NextAuth
Install next-auth:
pnpm install next-auth
Directory Structure ใน 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 };
สร้าง Github OAuth Application ได้ GITHUB_CLIENT_ID และ GITHUB_CLIENT_SECRET

NextAuth ให้ Sign In/Out จาก Client Component ใช้ signIn signOut จาก next-auth แต่ต้อง Setup Context Provider Wrap App ก่อน
// 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 ให้ Access Session State จาก Client Component ใดๆ
Access Session State 用 useSession Hook จาก next-auth ตัวอย่าง 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> ); }
Auth System Ready ต่อ Server Side Routes Store Comments ใน Redis Database
Setting up Redis Database - ตั้งค่า Redis Database
- ไป Upstash สร้าง Redis Database
- เลือก Region ใกล้ Users เลือก TLS Encryption
- Install
@upstash/redisPackage:pnpm install @upstash/redis

- Copy Tokens ใส่
.env.local
เขียน API Endpoints ใน Directory Structure:
.
├── app
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ └── route.ts
│ │ ├── comment
│ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ ├── get
│ │ │ │ └── route.ts
│ │ │ └── post
│ │ │ └── route.ts
│ │ └── lib
│ │ ├── getUser.ts
│ │ └── redis.ts
- Create Redis Client Instance
// app/api/lib/redis.ts
import { Redis } from "@upstash/redis";
/* Load UPSTASH_REDIS_REST_URL และ UPSTASH_REDIS_REST_TOKEN จาก Environment ด้วย process.env */
const redis = Redis.fromEnv();
export default redis;
Redis Client Ready เหลือ Setup API Routes ใน App
ใช้ Redis Lists เก็บ Comments Act as Stack Recent Comment Top แทนที่จะ Sort Client-side ใช้ Data Structure ที่ Redis มีให้
Code เต็มได้ที่นี่ Gist:
- Create Comment:
redis.lpush(referer, comment)Push Comment ใน List Keyreferer - Get All Comments:
redis.lrange(referer, 0, -1) - Delete Comment:
redis.lrem(referer, 0, comment)Delete All Occurrences ของcommentใน List Keyreferer
API Ready Integrate Frontend กับ Backend
Setting up the Client Side - ตั้งค่า Client Side
ไม่ยาก แค่ Hit Endpoints ที่สร้างด้วย fetch ใช้ Library swr Caching และ Revalidation Comments (React Query ก็ได้)
สร้าง useComment() Hook มี onSubmit() และ onDelete() Handlers ส่ง Requests:
"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 Done Fetch, Post, Delete Comments ได้ Next Steps สร้าง Components Comment Box และ Comments List Sample เต็มที่นี่