5개 패널을 _panels/<Panel>.tsx로 추출 + localStorage→getAdminToken + SSE→consumeSseStream. 내부 로직 변경 없음(점진적 접근). 설계서: docs/design/329-admin-split/README.md (Approved) Refs: #329 (Architect)
4.9 KiB
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에 옮김(짧은 타입만).
- 5개 패널을
- 제외
- 패널 내부 로직 변경 (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. 흐름
_panels/디렉토리 생성.- 각 패널 함수 + 그에 종속된 타입/상수를
<Panel>Panel.tsx로 잘라 옮김. - 각 파일에
"use client"+ import 추가. localStorage.getItem("tasteby_token")→getAdminToken()일괄.- SSE
getReader/decoder/buf.split/match패턴 →consumeSseStream(resp, onEvent)일괄. page.tsx재작성 — 탭 라우팅 + 패널 import.npm run build.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 흐름 분리) — 별도 후속.