CX DM 도메인 위키
Architecture
Stack
- Next.js 16
- React 19
- MDX Remote
- Shiki
- D3.js 7
- Fuse.js
- gray-matter
External integrations
- MDX content (wiki/ directory)
- D3 force-directed graph
- Fuse.js fuzzy search
- Vercel
Highlights
- 5-stage prebuild pipeline
- Precomputed D3 force layout
- 1-hop graph on each article
문제정의
CX DM 팀은 16개의 백오피스/CS 서비스를 운영하며 각 서비스의 엔티티·정책·규칙이 코드와 노션, 채널 DM, 슬랙 스레드, 그리고 사람의 머릿속에 흩어져 있었다. 신규 합류자는 "환불 정책이 어디 적혀 있냐"는 질문 하나를 풀기 위해 3-4명에게 핑을 돌려야 했고, 사라진 답변은 다음 신규자가 다시 물었다. 도메인 자산이 개인 지식으로만 존재했다.
기존에 위키를 시도하지 않은 이유는 분명했다 — 유지비. WYSIWYG 위키는 작성자가 코드 베이스와 분리된 도구를 또 열어야 하고, 도메인이 바뀌면 코드와 문서가 따로 늙어간다. 운영 중인 정책 16개의 초기 인제스트 비용도 만만치 않았다.
| 한계 | 수치 / 영향 |
|---|---|
| 흩어진 지식 출처 | 노션·슬랙·코드 주석·DM 4종 이상 |
| 신규자 온보딩 핑-퐁 | 평균 3-4명에게 질문 후 통합 |
| 정책 변경 추적 | 코드 PR과 문서 동기화 없음 |
| 그래프 탐색 부재 | "이 엔티티를 누가 쓰나" 추적 불가 |
목표는 세 가지로 좁혔다.
- 하루 안에 라이브 — 초기 비용을 압축하지 않으면 시작 자체가 미뤄진다. 인제스트와 웹앱을 같은 날 끝낸다.
- docs-as-code — 위키 콘텐츠가 코드와 같은 레포에 살고, PR로 변경되며, 빌드 산출물이 정적 아티팩트인 구조.
- 그래프 탐색을 1급 시민으로 — 엔티티·개념·규칙의 연결 관계를 force-directed 그래프로 시각화해 "주변 지식" 발견이 가능해야 한다.
구현
빌드 파이프라인 = 단일 진실 (Prebuild 5-stage)
콘텐츠 소스는 wiki/*.md 한 곳이고, 클라이언트가 읽는 것은 빌드 산출물 JSON 3종이다. 런타임에 파일 시스템을 만지지 않는다. 그래서 그래프 레이아웃을 빌드 타임에 한 번만 계산하고, 클라이언트는 좌표를 그대로 렌더한다.
| Stage | 도구 | 산출물 |
|---|---|---|
| 1. Frontmatter 파싱 | gray-matter | 페이지 메타 + 본문 |
| 2. Wikilink 해석 | 자체 파서 | edge list |
| 3. 그래프 레이아웃 | D3 forceSimulation | graph.json (좌표 포함) |
| 4. 검색 인덱스 | Fuse.js 입력 형태 | search-index.json |
| 5. 페이지 직렬화 | MDX 입력 형태 | pages.json |
AI-assisted 인제스트 — 16개 서비스, 단일 세션
services.config.yml에 16개 서비스의 이름·URL·담당자를 미리 정의해두고, 그걸 시드로 한 번에 마크다운 스캐폴드 16개를 생성했다. 그 다음 각 서비스의 엔티티·개념·규칙을 동일한 frontmatter 스키마(category, entities, concepts, rules)로 인제스트했다. 결과는 엔티티 112개, 개념 90개, 규칙 64개, 단일 커밋 11K LOC.
이 단계의 핵심은 "사람이 하루에 못 끝낼 분량"을 AI 보조로 압축한 게 아니라, 스키마를 먼저 못 박은 것이었다. frontmatter 키를 표준화했기에 빌드 파이프라인이 모든 페이지를 동일하게 다룰 수 있었고, 이후 추가되는 페이지도 동일한 lint 룰을 통과한다.
D3 force-directed 그래프를 1급 시민으로
/graph 페이지에 전체 그래프를 띄우고, 각 문서 페이지에는 1-hop 이웃만 추린 미니 그래프를 사이드바에 박았다. 좌표는 빌드 타임 precompute이므로 클라이언트는 sim을 돌리지 않고 바로 렌더하다가, 인터랙션이 들어오면 그때만 부분 시뮬을 가동한다.
모바일에서는 force 그래프가 가독성이 떨어져서 카테고리별 트리 뷰로 자동 폴백한다. 같은 graph.json을 다른 시각화로 풀어내는 형태다.
⌘K Command Palette + lazy 검색 인덱스
search-index.json은 페이지 진입 시 즉시 로드하지 않고, 사용자가 ⌘K를 처음 누를 때 fetch한다. Fuse.js로 제목·요약·태그를 fuzzy 매칭하고, 결과를 그대로 라우터에 push.
Codex 코드 리뷰 1회전
Day 1 마지막 커밋이 Codex 리뷰 피드백 반영이었다 — TOC 활성 항목 추적, 그래프 필터의 메모이즈, hook 의존성 배열, dynamic import로 번들 분할까지. 셀프 리뷰만으로는 잡지 못했을 신선한 시야였다.
출시
| 시기 | 주요 변경 |
|---|---|
| 2026-04-10 (Day 1) | 스캐폴딩 → 콘텐츠 인제스트 → Next.js 셋업 → 5-stage 빌드 → 페이지·그래프·⌘K → Codex 리뷰 반영, 같은 날 라이브 |
| 2026-04-13 (+3일) | Vercel 스타일 벤또 리디자인 + 디자인 토큰 + 반응형 + 테스트 추가 (PR #1328) |
Day 1은 24커밋, 34K LOC, 17:00경 첫 푸시부터 23:00 Codex 피드백 반영까지 단일 세션. 사흘 후 주말에 시각적으로 더 다듬으며 디자인 토큰과 테스트를 함께 도입해 후속 변경에서 회귀를 잡을 안전망을 깔았다.
결과학습
| 항목 | 결과 |
|---|---|
| 첫 라이브까지 | 당일 (Day 1, 단일 세션) |
| 인제스트 규모 | 엔티티 112 · 개념 90 · 규칙 64 |
| 서비스 페이지 | 16개 (services.config.yml 기반) |
| 단일 PR 변경량 | +34,278 / −446 / 431 files |
| 빌드 산출물 | pages.json · graph.json · search-index.json |
| 후속 리디자인 | Day 4, 디자인 토큰 + 테스트 + Vercel 벤또 |
기술적·운영적으로 가져간 학습은 네 가지다.
- 스키마를 먼저 박으면 인제스트가 병렬화된다. frontmatter 키(
category/entities/concepts/rules)를 못 박은 덕에 16개 서비스를 같은 lint 룰로 한 번에 흡수할 수 있었다. 자유형 마크다운이었다면 빌드 파이프라인이 성립하지 않았다. - 레이아웃은 빌드 타임에 끝낸다. D3 force는 매번 돌리면 클라이언트 부하가 크다. 좌표를
graph.json에 굳혀 두면 첫 페인트가 즉시고, 인터랙션 시에만 부분 시뮬로 충분하다. - 단일 산출물, 다중 시각화. 같은
graph.json을 데스크탑은 force 그래프로, 모바일은 카테고리 트리로 풀어냈다. 데이터를 한 형태로 굳히면 시각화는 사후 결정으로 미룰 수 있다. - Day 1에 외부 리뷰를 한 번 받는다. Codex 리뷰 한 차례가 hook 의존성, 번들 분할, 메모이즈 같은 셀프 리뷰가 놓치는 잡티들을 잡아냈다. 개인 프로젝트라도 마지막에 외부 시야 한 번을 끼우면 출시 품질이 단을 한 계단 올라간다.
Next
- 콘텐츠 lint를 CI로 — 현재 스키마 검증은 빌드 실패로만 잡힌다. PR 단계에서
entities/concepts/rules누락을 경고로 띄우는 lint 룰을 CI에 정착시킨다. - wikilink 자동 제안 — 작성 중 동일 엔티티명이 다른 페이지에 있으면
[[slug]]후보를 추천. 그래프 밀도를 사람이 의식하지 않고도 채우는 방향. - 변경 로그 자동화 — 페이지 frontmatter의
updated_at과 git blame을 묶어 "이 정책은 누가 언제 바꿨나"를 페이지 하단에 자동 노출.