SWR 데이터 페칭 표준화

날짜
영향도높음

배경

AlgoSu 프론트엔드는 모든 데이터 페칭을 useState + useEffect + useCallback 보일러플레이트로 수동 관리하고 있었다. 동일한 3-state 패턴(data/isLoading/error)이 problems, submissions, settings, dashboard 등 전 페이지에 반복되어 유지보수 비용이 높고, 캐싱/재검증/중복 요청 방지 등 기본 기능이 부재했다.

MEMORY.md "후속 처리 필요" 섹션 3항목(SWR 도입, Redis 통계 캐시, problem.tags JSON 전환) 중 최우선 순위로 선정되어 단일 스프린트로 편성.

목표

항목내용상태
Phase ASWR 인프라 구축 (fetcher, Provider, 4개 훅, test-utils)✅ 완료
Phase B3개 페이지 마이그레이션 (problems/submissions/settings)✅ 완료
Phase CNotificationBell 폴링 → refreshInterval 전환✅ 완료
Phase D신규 훅 테스트 + 기존 페이지 테스트 수정✅ 완료

결정 사항

D1. SWR 선택 (vs TanStack Query / React Query)

배경: 프론트 데이터 페칭 라이브러리 선택. 두 후보 비교 필요.

선택지:

  • (A) TanStack Query (React Query) — 고급 캐싱/동기화, 번들 ~40KB, 풍부한 DevTools
  • (B) SWR — 경량 ~8KB, 단순 API, httpOnly Cookie 친화 ← 선택

선택: (B) — AlgoSu의 기술 스택과 정확히 맞음:

  • httpOnly Cookie 인증: credentials: 'include' 자동 처리, Authorization 헤더 수동 주입 불필요
  • 기존 fetch wrapper (fetchApi)와 최소 변경으로 통합 — key를 API path 그대로 사용
  • SSE + 폴링 실시간 기능과 충돌 없음
  • 번들 크기 이점 (~8KB)
  • 현 데이터 페칭 복잡도(단순 CRUD)가 낮아 React Query의 고급 기능 필요 없음

결과: [email protected] 도입. lib/swr.ts에 공통 fetcher + cacheKeys 팩토리, SWRProvider로 글로벌 설정.


D2. 캐시 키 = API path (별도 키 체계 없음)

배경: SWR은 key로 인덱싱된 캐시를 가지며, key 네이밍 규칙이 필요.

선택지:

  • (A) 도메인별 튜플 키 (예: ['problems', 'all', studyId])
  • (B) API path 그대로 사용 (예: /api/problems/all) ← 선택

선택: (B) — fetchApi(path) 시그니처가 이미 path 기반이므로, key = path일 때 fetcher((key) => fetchApi(key))가 자연스럽게 성립. 캐시 키 디버깅 시 네트워크 탭과 1:1 매핑. cacheKeys 팩토리로 타입 안전성만 확보.

결과: cacheKeys.problems.all()/api/problems/all 반환. 쿼리 파라미터는 URLSearchParams로 path에 직렬화.


D3. 스터디 전환 시 invalidateAllCache() 호출

배경: fetchApi 내부가 모듈 레벨 _currentStudyId를 읽어 X-Study-ID 헤더를 주입한다. 같은 path라도 스터디가 바뀌면 서버 응답이 달라지는데, SWR 캐시 키에 studyId가 없으면 이전 스터디 데이터가 stale 상태로 보인다.

선택지:

  • (A) 모든 스터디-스코프 키에 studyId 포함 (예: ['study-1', '/api/problems/all'])
  • (B) 스터디 전환 이벤트에서 전체 SWR 캐시 무효화 ← 선택

선택: (B) — 스코프 데이터가 많아 (A)는 키 관리 비용 높음. 스터디 전환은 드문 이벤트이므로 전체 재검증 오버헤드 허용 가능. StudyContext.setCurrentStudy에서 invalidateAllCache() 호출.

결과: lib/swr.tsinvalidateAllCache() export. StudyContext.tsx에서 studyId 변경 시 호출 → 모든 SWR 캐시 재검증.


D4. SWR 범위 제외: SSE 훅 / useAutoSave / 검색 훅

배경: 프론트엔드에 7개 커스텀 훅 존재. 모두 SWR로 전환할지 범위 선정 필요.

선택: 전환 대상 4개 + 제외 3개

  • 전환: useProblems, useStudyStats, useSubmissions, useProfileSettings (단순 GET read cache)
  • 제외:
    • useNotificationSSE, useSubmissionSSE — 실시간 스트림 (SWR 대상 아님, ReadableStream 유지)
    • useAutoSave — localStorage + 30s 서버 동기화 (SWR read-cache 패턴과 부적합)
    • useBojSearch, useProgrammersSearch — 사용자 액션 기반 form 상태 연동 (SWR로 전환 시 오히려 복잡도 증가)

결과: SWR 도입 범위가 "선언적 GET + 자동 캐싱" 용도로 명확해짐. 실시간/form은 기존 패턴 유지.


산출물

신규 파일 (7)

파일역할
frontend/src/lib/swr.tsswrFetcher + cacheKeys + invalidateAllCache
frontend/src/components/providers/SWRProvider.tsx글로벌 SWRConfig (401/403/404 재시도 차단, dedupingInterval 2s)
frontend/src/lib/test-utils.tsxSWRTestWrapper (테스트 캐시 격리)
frontend/src/hooks/use-problems.ts문제 목록 SWR 훅
frontend/src/hooks/use-study-stats.ts스터디 통계 SWR 훅
frontend/src/hooks/use-submissions.ts제출 목록 페이지네이션 SWR 훅
frontend/src/hooks/use-profile-settings.ts프로필 설정 SWR 훅

수정 파일 (7)

파일변경설명
frontend/src/lib/api.ts+1fetchApi export 추가 (SWR fetcher 재사용)
frontend/src/app/layout.tsx+3SWRProvider 삽입 (StudyProvider > SWRProvider)
frontend/src/contexts/StudyContext.tsx+2setCurrentStudy 시 invalidateAllCache 호출
frontend/src/app/problems/page.tsx+12/-22useState+useEffect → useProblems+useStudyStats 전환
frontend/src/app/submissions/page.tsx+31/-35useSubmissions+useProblems 전환 (Herald)
frontend/src/app/settings/page.tsx+26/-19useProfileSettings 전환 + useEffect로 폼 초기화
frontend/src/components/layout/NotificationBell.tsx+52/-7060s setInterval 제거 → useSWR refreshInterval (Herald)

신규 테스트 파일 (4)

파일테스트 수
use-problems.test.tsx5
use-study-stats.test.tsx5
use-submissions.test.tsx6
use-profile-settings.test.tsx5

수정 테스트 파일 (2)

파일변경설명
NotificationBell.test.tsx+90/-30notificationApi 모킹 → swrFetcher 모킹 전환 (26 tests 재활성)
settings/__tests__/page.test.tsx+37/-20settingsApi 모킹 → swrFetcher 모킹 전환

커밋 (6, 전부 atomic)

2c7fd08 feat(frontend): SWR 인프라 + problems 페이지
6f2e591 test(frontend): NotificationBell/settings 테스트 수정
192170a test(frontend): SWR 훅 4개 테스트 추가 (21 tests)
8c5614d refactor(frontend): NotificationBell SWR refreshInterval
1e57f71 refactor(frontend): settings 페이지 SWR
4d7425d refactor(frontend): submissions 페이지 SWR

검증

  • npx tsc --noEmit: 0 에러
  • npm test: 1259/1259 passed (120 suites, 신규 21개 포함)
  • 커버리지: 신규 훅 4개 모두 조건부 페칭/성공/실패/mutate 케이스 커버

이월 항목

없음.

후속 처리 필요 (MEMORY.md 상시)

  • Redis 통계 캐시 (대시보드 통계 DB 직접 조회 → 캐시 전환)
  • problem.tags JSON 컬럼 전환 + seed 데이터 확충
  • dashboard/page.tsx SWR 전환 (4개 병렬 fetch → 개별 SWR 훅)
  • admin/feedbacks/page.tsx SWR 전환 (useSWRInfinite 검토)
  • useSWRMutation 기반 mutation 패턴 도입 (현 직접호출 + mutate 방식의 대안)