무색
기술블로그
에세이
연구
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

contact@museck.com

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관·연락처
INCHEON, KR
블로그 시리즈 시스템 구축 — isometric 키 비주얼
museck 만들기
2026. 2. 14.

블로그 시리즈 시스템 구축: PayloadCMS 컬렉션 설계부터 탭 필터링 UI까지

payloadcmsnext.jsreactcms

블로그 글이 20편을 넘어가니까 문제가 생겼다. 시간순으로 쭉 나열된 목록에서 원하는 글을 찾기가 어렵다. '홈랩 시리즈 글만 보고 싶은데'라는 생각이 자꾸 든다. 카테고리 텍스트 필드 하나로는 한계가 분명했다. PayloadCMS에 시리즈 컬렉션을 새로 만들고, 블로그 UI를 탭 필터링 방식으로 리디자인한 과정을 정리한다.

왜 카테고리로는 부족했나

기존에는 category라는 텍스트 필드 하나로 글을 분류했다. voice-ai, app-web-dev, infra 같은 값을 직접 입력하는 방식이다. 문제는 두 가지였다.

  • 타이포 위험. 자유 텍스트라서 voice-ai와 voiceai가 다른 카테고리로 취급된다.
  • 순서 관리 불가. 같은 카테고리 안에서 글의 순서를 지정할 방법이 없다. 시리즈물은 읽는 순서가 중요한데.

필요한 건 '시리즈'라는 독립적인 엔티티였다. 제목, 설명, 이모지, 대표 이미지를 가지고, 포스트와 1:N 관계를 맺는 컬렉션.

Series 컬렉션 설계

PayloadCMS에서 새 컬렉션을 만드는 건 간단하다. 스키마를 정의하면 어드민 UI, API, 타입이 자동으로 생성된다.

핵심 필드는 order다. 시리즈 목록을 보여줄 때 정렬 기준이 된다. emoji는 탭 UI에서 시리즈를 시각적으로 구분하는 용도.

Posts와 Series 연결: relationship 필드

Posts 컬렉션의 category 텍스트 필드를 series relationship 필드로 교체했다. hasMany: true로 설정해서 하나의 글이 여러 시리즈에 속할 수 있게 했다.

여기에 seriesOrder 숫자 필드를 추가했다. 같은 시리즈 안에서 글의 순서를 지정하는 필드다. 시리즈별로 1, 2, 3... 이런 식으로 매긴다.

autoSeriesOrder 훅: 순서 자동 계산

매번 시리즈 순서를 수동으로 입력하는 건 귀찮다. beforeChange 훅으로 자동 계산하도록 만들었다.

시리즈에 속하는 글을 새로 만들 때, 해당 시리즈의 마지막 순서를 조회해서 +1한 값을 자동으로 넣어준다. seriesOrder를 직접 입력하면 그 값을 그대로 사용한다.

BlogFilter: 탭 필터링 UI

블로그 목록 페이지에 시리즈별 탭을 만들었다. '전체' 탭과 각 시리즈 탭이 있고, 탭을 클릭하면 URL의 쿼리 파라미터가 바뀌면서 해당 시리즈의 글만 필터링된다.

전체 탭에서는 시간순(최신 우선), 시리즈 탭에서는 seriesOrder 오름차순으로 정렬한다. 시리즈를 순서대로 읽고 싶은 사용자를 위한 배려다.

홈페이지 리디자인

홈페이지도 시리즈 중심으로 바꿨다. 시리즈 카드가 그리드로 배치되고, 각 카드에는 이모지, 제목, 설명, 글 개수가 표시된다. 카드를 클릭하면 해당 시리즈로 필터링된 블로그 목록으로 이동한다.

최신 글과 추천 글(isFeatured: true) 섹션도 추가해서 시리즈에 속하지 않는 글도 자연스럽게 노출되도록 했다.

삽질 기록

Lexical upload 노드의 이중 구조

Lexical 에디터의 upload 노드가 때에 따라 { value: { url: '...' } } 객체로 저장되기도 하고, 문자열 ID로 저장되기도 한다. 렌더링 코드에서 두 경우를 모두 처리해야 했다. 타입 체크 없이 .url에 접근하면 런타임 에러가 터진다.

featuredImage의 sizes.card vs 원본

카드 목록에서는 sizes.card 크롭 이미지를 쓰지만, 글 본문에서는 원본 이미지를 써야 한다. 처음에 sizes.card를 본문에도 적용해서 잘린 이미지가 나왔다. 용도에 따라 이미지 사이즈를 구분해서 써야 한다.

데이터 마이그레이션

기존 포스트의 category 텍스트 값을 새 series relationship으로 매핑하는 작업은 REST API로 일괄 처리했다. 20편 정도라서 스크립트를 짜는 게 수동 작업보다 빨랐다.

교훈

  • 텍스트 필드에서 relationship으로의 전환은 빠를수록 좋다. 데이터가 쌓일수록 마이그레이션 비용이 늘어난다.
  • PayloadCMS의 beforeChange 훅은 자동화의 핵심이다. 순서 자동 계산, 슬러그 생성 같은 반복 작업을 훅에 맡기면 실수가 줄어든다.
  • 클라이언트 필터링은 searchParams로. URL에 상태를 반영하면 공유 가능한 링크가 되고, 뒤로 가기도 자연스럽게 동작한다.

마무리

카테고리 텍스트 필드 하나를 시리즈 컬렉션으로 바꿨을 뿐인데, 블로그의 탐색 경험이 확 달라졌다. 스키마 설계가 UI를 결정한다는 걸 다시 한번 느꼈다. 데이터 모델이 잘 잡혀 있으면 UI는 자연스럽게 따라온다.

자주 묻는 질문

PayloadCMS에서 시리즈(Series)와 포스트(Posts)를 어떻게 연결하나요?
Posts 컬렉션에 relationship 필드를 추가하고 relationTo: 'series', hasMany: true로 설정합니다. 하나의 글이 여러 시리즈에 속할 수 있습니다.
시리즈 내 글 순서를 자동으로 관리하려면 어떻게 하나요?
beforeChange 훅에서 해당 시리즈의 마지막 seriesOrder를 조회하고 +1한 값을 자동으로 할당합니다. 수동 입력하면 그 값을 우선 사용합니다.
블로그 탭 필터링에서 URL 상태를 관리하는 이유는 무엇인가요?
searchParams로 URL에 필터 상태를 반영하면 링크 공유가 가능하고 브라우저 뒤로 가기가 자연스럽게 동작합니다. useState 대신 URL이 진실의 원천이 됩니다.
museck 만들기(11/22)
Prev

CI에서 터지기 전에 잡자: husky + lint-staged 설정기

Next

Next.js App Router SEO 구현: robots.ts부터 JSON-LD까지 하루 만에