이 튜토리얼️에서는 블로그에 대한 댓글 섹션을 구축할 것입니다. 우리가 될 기술 스택은 다음과 같습니다:
- NextJS 13(앱 디렉토리)
- NextAuth(인증용)
- Upstash Redis(댓글 저장용)
- SWR(댓글 캐싱 및 재검증용)
시작해 보겠습니다.
NextAuth로 인증 처리
첫째, 누구나 댓글을 게시하도록 할 수는 없습니다. 그렇죠? 누군가가 스크립트를 실행하여 귀하의 블로그에 댓글을 스팸으로 보낼 수도 있습니다. 사람들이 댓글을 게시할 수 있도록 하기 전에 먼저 인증 시스템을 구축해 보겠습니다. NextAuth를 사용하겠습니다.
next-auth 설치 프로젝트에 참여하세요.
pnpm install next-auth 이는 앱 디렉토리 내부의 디렉토리 구조입니다
.
├── app
│ ├── api
│ │ └── auth
│ │ └── [...nextauth]
│ │ └── route.ts
│ ├── blog
│ │ ├── page.tsx
│ │ └── [...slug]
│ │ └── page.tsx
│ ├── components
│ │ └── LoginButton.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
인증 API 경로를 설정하세요:
// 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 애플리케이션을 생성하여 GITHUB_CLIENT_ID를 가져옵니다. 그리고 GITHUB_CLIENT_SECRET .

이제 NextAuth를 사용하면 signIn를 사용하여 모든 클라이언트 구성 요소에서 로그인 및 로그아웃할 수 있습니다. 및 signOut next-auth의 함수 . 하지만 그 전에 전체 애플리케이션을 래핑하는 컨텍스트 제공자를 설정해야 합니다.
// 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 이를 통해 애플리케이션의 모든 클라이언트 구성 요소에서 세션 상태에 액세스할 수 있습니다.
useSession를 사용하여 세션 상태에 액세스할 수 있습니다. next-auth의 후크 . 다음은 로그인 버튼의 샘플입니다:
"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>
);
} 이를 통해 인증 시스템이 마련되었습니다. 이제 Redis 데이터베이스에 주석을 저장하기 위한 서버 측 경로를 시작하겠습니다.
Redis 데이터베이스 설정
- Upstash로 이동하여 Redis 데이터베이스를 생성하세요.
- 사용자와 가까운 지역을 선택하고 TLS 암호화를 선택하세요.
@upstash/redis설치 패키지.
pnpm install @upstash/redis

- 이 토큰을
.env.local에 복사하세요. .
이제 이 디렉토리 구조에 API 엔드포인트를 작성해 보겠습니다.
.
├── app
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ └── route.ts
│ │ ├── comment
│ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ ├── get
│ │ │ │ └── route.ts
│ │ │ └── post
│ │ │ └── route.ts
│ │ └── lib
│ │ ├── getUser.ts
│ │ └── redis.ts
- Redis 클라이언트 인스턴스 생성
// app/api/lib/redis.ts
import { Redis } from "@upstash/redis";
/*
This tries to load UPSTASH_REDIS_REST_URL
and UPSTASH_REDIS_REST_TOKEN from your environment
using process.env
*/
const redis = Redis.fromEnv();
export default redis; 그게 다야. 이제 Redis 클라이언트가 설정되었으며 남은 것은 애플리케이션에서 이에 대한 API 경로를 설정하는 것뿐입니다.
우리는 설명을 저장하기 위해 Redis 목록을 사용할 것입니다. 스택 역할을 할 수 있으므로 가장 최근 댓글이 맨 위에 표시됩니다. 물론 클라이언트측에서 정렬 논리를 구현할 수도 있지만 Redis가 이미 이 데이터 구조를 제공하고 있다면 이를 사용해 보겠습니다.
여기에서 전체 코드를 볼 수 있습니다. 요점은 다음과 같습니다:
-
댓글 작성:
redis.lpush(referer, comment). 그러면referer키가 있는 목록에 주석이 푸시됩니다. -
모든 댓글 가져오기
redis.lrange(referer, 0, -1) -
redis.lrem(referer, 0, comment)댓글 삭제 .comment가 모두 삭제됩니다.referer키가 있는 목록에서 .
이제 API가 준비되었습니다. 프런트엔드와 백엔드를 통합해 보겠습니다.
클라이언트 측 설정
그다지 어렵지 않습니다. fetch를 사용하여 방금 생성한 엔드포인트에 접속하면 됩니다. .우리는 swr라는 라이브러리를 사용할 것입니다. 댓글 캐싱 및 재검증을 위해. 하지만 React Query와 같은 다른 라이브러리를 사용해도 됩니다.
우리는 useComment()을 만듭니다 onSubmit()을 갖는 후크 및 onDelete() 요청을 처리하는 핸들러입니다.
"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; 이제 댓글 섹션이 완료되었습니다. 댓글을 가져오고, 게시하고, 삭제할 수 있습니다. 다음 단계는 설명 상자 및 설명 목록에 대한 구성 요소를 만드는 것입니다. 다음은 전체 샘플입니다