Files
tasteby/docs/design/329-admin-split/README.md
joungmin 7b2753b9fd docs(design): #329 admin/page.tsx 분리 + 유틸 통일 설계서 (Architect)
5개 패널을 _panels/<Panel>.tsx로 추출 + localStorage→getAdminToken + SSE→consumeSseStream.
내부 로직 변경 없음(점진적 접근).

설계서: docs/design/329-admin-split/README.md (Approved)
Refs: #329 (Architect)
2026-06-15 15:48:49 +09:00

4.9 KiB

설계서: admin/page.tsx 분리 + 토큰/SSE 유틸 통일 (#329)

상태: Approved 작성: [AI] Architect · 최종수정: 2026-06-15 추적성 — Redmine: #329 · 부모: #304 (현행화 frontend-admin, 09-Done) · 구현 파일: frontend/src/app/admin/page.tsx, frontend/src/app/admin/_panels/*.tsx (신규), frontend/src/lib/admin-utils.ts · 테스트: 수동 (각 탭 진입 + 기존 시나리오 회귀)

1. 목적 (Why)

admin/page.tsx가 2817 LOC 단일 파일. 5개 패널이 같은 파일 안에 함께 있어 (a) 빌드 변경 시 무관한 패널까지 재빌드/재배포, (b) 코드 리뷰 시 충돌 가능성↑, (c) 로직 격리 어려움. 또한 localStorage 직접 호출 + SSE 파싱 코드 중복이 남아있어 #304 후속에서 한 번 더 정리.

2. 범위 (Scope)

  • 포함
    • 5개 패널을 app/admin/_panels/<Panel>.tsx로 추출 (Next.js underscore prefix로 라우팅 제외).
    • page.tsx는 탭 라우팅 + 헤더 + 패널 import만.
    • 남은 localStorage.getItem("tasteby_token") 호출 → getAdminToken()/authHeaders() 교체.
    • SSE 파싱 중복(약 5~6곳) → consumeSseStream() 활용.
    • 공유 타입(AdminUser/UserFavorite/UserReview/UserMemo/VideoSortKey)을 _panels 파일 내부 또는 api.ts에 옮김(짧은 타입만).
  • 제외
    • 패널 내부 로직 변경 (state/effect 그대로).
    • catch{/ignore/} 일괄 로깅 (별도 후속).
    • ad-hoc 타입 캐스팅 정리 (별도 후속).
    • 디자인 시스템 색상 통일.

3. 인수조건

  • 5개 파일 신규: _panels/{Channels,Videos,Restaurants,Users,Daemon}Panel.tsx.
  • page.tsx < 200 LOC (이전 2817).
  • localStorage 직접 호출이 admin 페이지 내에 0건.
  • SSE reader 직접 호출(response.body?.getReader())이 0건.
  • 모든 탭 진입 + 기존 시나리오 회귀 없음(빌드 통과 + 수동 smoke).
  • dev 빌드 + 운영 배포 성공.

4. 컨텍스트 & 제약

  • Next.js 16 (App Router). _ prefix 디렉토리는 route 제외.
  • "use client" 지시문 각 패널 파일 상단에 필요.
  • React Server Components 미사용 (모두 클라이언트 컴포넌트).
  • useAuth() 훅이 auth-context에 있음. 부모 AdminPage가 isAdmin 판정 후 prop으로 패널에 전달.
  • 패널 간 상태 공유 없음 (각각 독립).

5. 아키텍처 개요

app/admin/
  page.tsx                     ← 탭 라우팅 + 헤더 + CacheFlushButton + 패널 import
  _panels/
    ChannelsPanel.tsx          ← 214 LOC
    VideosPanel.tsx            ← 1272 LOC (가장 큼)
    RestaurantsPanel.tsx       ← 667 LOC
    UsersPanel.tsx             ← 332 LOC
    DaemonPanel.tsx            ← 223 LOC

lib/admin-utils.ts (이미 존재)
  ├─ getAdminToken()
  ├─ authHeaders()
  └─ consumeSseStream()

6. 함수 명세

단위 책임 비고
page.tsx (재작성) 탭 라우팅 + 패널 import < 200 LOC
_panels/*.tsx (신규 5개) 각 패널 로직 그대로 옮김 "use client"
localStorage 호출 (~10곳) getAdminToken()/authHeaders()로 통일 의미 동일
SSE getReader (~5곳) consumeSseStream(resp, onEvent)로 통일 의미 동일

7. 흐름

  1. _panels/ 디렉토리 생성.
  2. 각 패널 함수 + 그에 종속된 타입/상수를 <Panel>Panel.tsx로 잘라 옮김.
  3. 각 파일에 "use client" + import 추가.
  4. localStorage.getItem("tasteby_token")getAdminToken() 일괄.
  5. SSE getReader/decoder/buf.split/match 패턴 → consumeSseStream(resp, onEvent) 일괄.
  6. page.tsx 재작성 — 탭 라우팅 + 패널 import.
  7. npm run build.
  8. pm2 restart 또는 deploy.sh --frontend-only.

8. 엣지케이스

  • 순환 import: 패널 간 의존 없음 → 안전.
  • Type 중복: AdminUser 등 패널 내부 타입은 그대로 옮김. 공유 타입은 api.ts에 있음.
  • default export vs named: 각 패널은 named export. page.tsx에서 import { ChannelsPanel } from "./_panels/ChannelsPanel".
  • 빌드 크기: 동일(코드 splitting은 별도 작업).

9. 테스트

  • 빌드: npm run build 통과.
  • 수동:
    • /admin 접근 → 5탭 모두 진입 가능.
    • 채널 추가, 영상 강제 추출(SSE), 식당 검색/수정, 유저 권한 토글, 데몬 설정 변경 — 모두 정상.
  • 자동: 별도 후속(테스트 인프라 #343).

10. 리스크 & 대안

  • 선택: 5개 파일 추출 + 내부 로직 그대로.
  • 대안 A: 추출 + 내부 리팩터링 동시 — 회귀 위험↑, 별도 후속이 안전.
  • 대안 B: Atomic Design (atoms/molecules/organisms) — 큰 재구조화. 미루기.
  • 트레이드오프: 외형은 동일, 유지보수성/충돌 가능성만 개선. 점진적 접근.

11. 미해결 질문

  • 공통 panel layout(헤더/리스트/페이징) 추상화 — 후속.
  • VideosPanel 1272 LOC 내부 분할(상태 머신 + SSE 흐름 분리) — 별도 후속.