
Kim JeongHyeonFrontend Focused Fullstack Developer

포스트를 시리즈로 묶어 이전/다음 글 네비게이션을 제공하고, 닉네임+비밀번호 기반의 게스트 댓글 시스템을 구축합니다.
카테고리와 시리즈는 역할이 다릅니다:
카테고리: 단일 분류 (Tech, Design, Essay 등)
시리즈: 순서가 있는 컬렉션 (이 글도 "Next.js 블로그 만들기" 시리즈의 3번째 글입니다)
하나의 포스트는 하나의 카테고리와 하나의 시리즈에 동시에 소속될 수 있습니다.
CREATE TABLE series (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
description TEXT DEFAULT '',
published BOOLEAN DEFAULT false,
title_en TEXT DEFAULT '',
description_en TEXT DEFAULT ''
);
-- posts에 시리즈 연결
ALTER TABLE posts ADD COLUMN series_id UUID REFERENCES series(id) ON DELETE SET NULL;
ALTER TABLE posts ADD COLUMN series_order INTEGER DEFAULT 0;
ON DELETE SET NULL을 사용하면 시리즈를 삭제해도 포스트는 남고, series_id만 NULL로 바뀝니다.
포스트 상세 페이지에서 시리즈 소속인 경우, 시리즈 박스를 표시합니다:
// 시리즈 데이터 fetch
useEffect(() => {
if (!post.series_id) return;
fetch(`/api/series/${post.series_id}`)
.then(res => res.json())
.then(data => setSeriesData(data));
}, [post.series_id]);
시리즈 박스에는 세 가지 요소가 있습니다:
접기/펼치기 가능한 전체 목록 — 현재 글이 하이라이트됨
이전 글 링크 — series_order가 현재보다 작은 글 중 가장 큰 것
다음 글 링크 — series_order가 현재보다 큰 글 중 가장 작은 것
const currentOrder = seriesPosts.findIndex(p => p.slug === post.slug);
const prevPost = currentOrder > 0 ? seriesPosts[currentOrder - 1] : null;
const nextPost = currentOrder < seriesPosts.length - 1
? seriesPosts[currentOrder + 1] : null;
포트폴리오 블로그이므로 OAuth 로그인을 요구하는 건 과합니다. 닉네임 + 비밀번호 방식으로 누구나 댓글을 남길 수 있게 했습니다.
작성: 닉네임과 비밀번호 입력 → 비밀번호는 bcrypt 해시로 저장
삭제: 비밀번호 입력 → bcrypt.compare로 검증 → 일치 시 삭제
대댓글: parent_id 기반 1-depth 대댓글 지원
CREATE TABLE comments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
parent_id UUID REFERENCES comments(id) ON DELETE CASCADE,
nickname TEXT NOT NULL,
password_hash TEXT NOT NULL,
content TEXT NOT NULL,
is_admin BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
API에서는 flat list로 반환하고, 클라이언트에서 parent_id 기준으로 트리 구조를 만듭니다:
const grouped = comments.reduce((acc, comment) => {
if (!comment.parent_id) {
// 최상위 댓글
acc.push({ ...comment, replies: [] });
} else {
// 대댓글 → 부모 찾아서 replies에 추가
const parent = acc.find(c => c.id === comment.parent_id);
if (parent) parent.replies.push(comment);
}
return acc;
}, []);
좋아요도 로그인 없이 동작해야 합니다. IP 주소를 기준으로 중복을 방지합니다:
CREATE TABLE likes (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
target_type TEXT NOT NULL, -- 'post' | 'work'
target_id TEXT NOT NULL,
ip TEXT NOT NULL,
UNIQUE (target_type, target_id, ip)
);
UNIQUE 제약으로 DB 레벨에서 중복을 차단하고, API에서는 이미 좋아요가 있으면 삭제(토글) 합니다.
3편에 걸쳐 Next.js + Supabase 블로그의 핵심 기능을 모두 다뤘습니다:
1편: 프로젝트 설계, Supabase 세팅, 테이블 스키마
2편: Markdown/Rich Text 에디터, 이미지 편집, 코드 하이라이팅
3편: 시리즈 네비게이션, 게스트 댓글, IP 기반 좋아요
이 시리즈가 여러분의 블로그 구축에 도움이 되었으면 좋겠습니다. 전체 소스 코드는 GitHub에서 확인할 수 있습니다.