-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
Context7 공식 문서 기반 리팩토링 목록
담당 파일들을 Next.js, React, TanStack Query 공식 문서(Context7)에서 확인한 권장 패턴과 비교 분석한 결과입니다.
CRITICAL (공식 권장 패턴 위반)
1. 서버 컴포넌트에서 new QueryClient() 매번 생성 — getQueryClient 싱글턴 패턴 미사용
- 파일:
src/app/(with-header)/layout.tsx,src/features/session/components/SessionList/SessionListPrefetch.tsx,src/features/member/hooks/useMemberHooks.ts(prefetchMe, prefetchMeForEdit, prefetchMyReport) - 출처: TanStack Query Advanced SSR Guide
- 현재: 서버 컴포넌트마다
new QueryClient()를 직접 생성.defaultOptions(staleTime, dehydrate 설정 등)이 적용되지 않으며, 같은 request 내에서 QueryClient가 공유되지 않아 중복 fetch 가능 - 공식 권장:
React.cache()를 사용한 request-scoped 싱글턴 패턴 - 개선:
// src/lib/get-query-client.ts (신규)
import { QueryClient, defaultShouldDehydrateQuery } from "@tanstack/react-query";
import { cache } from "react";
const getQueryClient = cache(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
dehydrate: {
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === "pending",
},
},
}));
export default getQueryClient;- 영향 범위:
layout.tsx,SessionListPrefetch.tsx,prefetchMe(),prefetchMeForEdit(),prefetchMyReport()모두getQueryClient()사용으로 교체
2. with-header layout에서 await prefetchQuery — 비동기 스트리밍 차단
- 파일:
src/app/(with-header)/layout.tsx - 출처: TanStack Query — Prefetch Without Await
- 현재:
await queryClient.fetchQuery(memberQueries.me())로 사용자 정보를 동기적으로 가져옴. 이 동안 모든 children 렌더링이 블로킹됨 - 공식 권장:
prefetchQuery를await없이 호출하면 데이터가 준비되는 동안 children이 먼저 스트리밍됨 - 개선:
fetchQuery→prefetchQuery(await 제거).dehydrate설정에서pending쿼리도 포함하도록 설정 (위getQueryClient에 포함)
// Before
const response = await queryClient.fetchQuery(memberQueries.me());
memberProfile = response.result;
// After — 블로킹 없이 prefetch
queryClient.prefetchQuery(memberQueries.me());
// memberProfile은 클라이언트에서 useMe() 훅으로 접근- 주의:
memberProfile.firstLogin으로 OnboardingModal을 조건부 렌더링하는 로직이 있으므로, 이 부분을 클라이언트 컴포넌트로 분리하여useMe()훅으로 판단하도록 변경 필요
3. SessionListPrefetch에서 await prefetchQuery — 불필요한 블로킹
- 파일:
src/features/session/components/SessionList/SessionListPrefetch.tsx - 출처: 동일
- 현재:
await queryClient.prefetchQuery(...)—prefetchQuery는 반환값이 없으므로 await가 불필요하게 렌더링을 차단 - 공식 권장:
prefetchQuery는 await 없이 호출하여 데이터 스트리밍 - 개선:
// Before
await queryClient.prefetchQuery({
queryKey: sessionKeys.list(listParams),
queryFn: () => sessionApi.getList(listParams),
});
// After — await 제거
queryClient.prefetchQuery({
queryKey: sessionKeys.list(listParams),
queryFn: () => sessionApi.getList(listParams),
});HIGH (중요 개선)
4. profile/report 에러 처리 — Next.js error.tsx 파일 컨벤션 활용
- 파일:
src/app/(with-header)/profile/report/디렉토리 - 출처: Next.js Error Handling
- 현재:
StatsContent.tsx,SessionHistoryContent.tsx에서throw new Error()사용.src/app/(with-header)/profile/report/error.tsx가 존재하여 catch는 되지만, Suspense 단위의 세분화된 에러 처리가 아닌 전체 페이지 단위 에러 처리 - 공식 권장: Suspense 내부에서 throw된 에러는 가장 가까운 error boundary에서 잡힘. 현재 구조에서는
profile/report/error.tsx가 두 Suspense를 모두 감싸므로 하나가 실패하면 전체 리포트가 에러 UI로 교체됨 - 개선: 각 섹션별 ErrorBoundary 래핑으로 부분 에러 처리 지원
// profile/report/page.tsx
<ErrorBoundary fallback={<StatsErrorFallback />}>
<Suspense fallback={<StatsSkeleton />}>
<StatsContent />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<SessionHistoryErrorFallback />}>
<Suspense fallback={<SessionHistorySkeleton />}>
<SessionHistoryContent page={page} />
</Suspense>
</ErrorBoundary>5. login 페이지 — searchParams와 cookies() 병렬 await
- 파일:
src/app/(auth)/login/page.tsx,src/app/@modal/(.)login/page.tsx - 출처: Next.js — cookies and headers functions
- 현재:
searchParams와cookies()순차 await - 공식 문서: 두 작업은 독립적이므로 병렬 실행 가능
- 개선:
// Before
const params = await searchParams;
const cookieStore = await cookies();
// After
const [params, cookieStore] = await Promise.all([searchParams, cookies()]);6. prefetchMe 등 유틸 함수 — dehydrate 반환 패턴 개선
- 파일:
src/features/member/hooks/useMemberHooks.ts(prefetchMe, prefetchMeForEdit, prefetchMyReport) - 출처: TanStack Query Advanced SSR Guide
- 현재: 각 prefetch 함수가 자체
new QueryClient()를 생성하고dehydrate()까지 수행. 호출 측에서HydrationBoundary에 직접 전달하는 패턴 - 공식 권장:
getQueryClient()싱글턴을 사용하면 별도 prefetch 유틸 함수가 불필요. 서버 컴포넌트에서 직접getQueryClient().prefetchQuery()를 호출 - 개선:
prefetchMe(),prefetchMeForEdit(),prefetchMyReport()함수 제거 또는getQueryClient()활용으로 단순화
MEDIUM (개선 권장)
7. QueryProvider — useState 초기화 패턴 개선
- 파일:
src/providers/QueryProvider.tsx - 출처: TanStack Query Advanced SSR Guide
- 현재:
useState(() => new QueryClient({...}))패턴 사용 - 공식 권장: Suspense boundary가 QueryClient 생성 아래에 없으면 React가 initial render 중 suspend 시 client를 버릴 수 있음.
getQueryClient()싱글턴 패턴이 더 안전 - 개선:
// Before
const [queryClient] = useState(() => new QueryClient({...}));
// After
import { getQueryClient } from "@/lib/get-query-client";
const queryClient = getQueryClient();- 참고: 현재
useState패턴도 동작하지만, TanStack Query 공식 문서가 권장하는getQueryClient패턴으로 통일하면 서버/클라이언트 모두 동일한 설정 공유 가능
8. React use 훅 활용 가능성 — Promise를 prop으로 전달
- 파일:
src/app/(with-header)/page.tsx등 서버 컴포넌트 - 출처: React 19 — use API
- 현재: 서버 컴포넌트에서 데이터를 prefetch 후 HydrationBoundary로 전달하는 패턴
- 공식 문서: React 19의
use()훅으로 Promise를 직접 prop으로 전달하여 Suspense에서 resolve 가능 - 상태: 현재 TanStack Query 기반 패턴이 잘 동작하고 있으므로 즉시 변경 불필요. 향후 데이터 패칭 레이어 단순화 시 고려할 옵션
9. dehydrate 설정 — pending 쿼리 포함
- 파일:
src/providers/QueryProvider.tsx또는 새로운get-query-client.ts - 출처: TanStack Query Advanced SSR Guide
- 현재:
dehydrate기본 설정 사용 — pending 상태의 쿼리는 dehydrate되지 않음 - 공식 권장:
shouldDehydrateQuery에서pending상태 쿼리도 포함하면,await없이prefetchQuery를 호출해도 클라이언트에서 데이터를 받을 수 있음 - 개선:
getQueryClient싱글턴 도입 시 함께 설정 (항목 1에 포함)
10. metadata — createPageMetadata 패턴 검증
- 파일:
src/app/(auth)/login/page.tsx,src/app/(with-header)/page.tsx - 출처: Next.js Metadata API
- 현재:
createPageMetadata()유틸로 정적 metadata 생성 —export const metadata - 상태: ✅ 이미 양호 — 정적 metadata 패턴이 공식 문서와 일치
11. Intercepting Routes — 모달 패턴 검증
- 파일:
src/app/@modal/(.)login/page.tsx,src/app/@modal/(.)session/[sessionId]/page.tsx - 출처: Next.js Parallel Routes & Intercepting Routes
- 현재:
@modal슬롯 +(.)인터셉팅 라우트로 모달 구현 - 상태: ✅ 이미 양호 — 공식 문서의 모달 패턴과 정확히 일치
요약
실행 가능한 작업 목록
| # | 영향도 | 파일 | 작업 |
|---|---|---|---|
| 1 | CRITICAL | 신규 get-query-client.ts + 서버 컴포넌트 전체 |
React.cache() 기반 QueryClient 싱글턴 도입 |
| 2 | CRITICAL | (with-header)/layout.tsx |
fetchQuery → prefetchQuery (await 제거), OnboardingModal 로직 클라이언트 분리 |
| 3 | CRITICAL | SessionListPrefetch.tsx |
await prefetchQuery → prefetchQuery (await 제거) |
| 4 | HIGH | profile/report/page.tsx |
Suspense별 ErrorBoundary 래핑 |
| 5 | HIGH | login/page.tsx, modal login/page.tsx |
searchParams & cookies 병렬 Promise.all |
| 6 | HIGH | useMemberHooks.ts |
prefetch 유틸 함수들 getQueryClient() 활용으로 단순화 |
| 7 | MEDIUM | QueryProvider.tsx |
useState → getQueryClient() 패턴 통일 |
| 8 | MEDIUM | 향후 고려 | React 19 use() 훅 활용 가능성 |
| 9 | MEDIUM | get-query-client.ts |
dehydrate에 pending 쿼리 포함 설정 (항목 1에 포함) |
이미 잘 되어 있는 부분
| 항목 | 관련 공식 문서 | 파일 |
|---|---|---|
| 정적 metadata export 패턴 | Next.js Metadata API | login/page.tsx, home page.tsx |
| Intercepting Routes 모달 패턴 | Next.js Parallel Routes | @modal/(.)login, @modal/(.)session |
| Suspense 기반 스트리밍 구조 | React Suspense | home page.tsx, report page.tsx |
| HydrationBoundary 사용 | TanStack Query SSR | layout.tsx, SessionListPrefetch.tsx |
| QueryClient staleTime 설정 | TanStack Query Defaults | QueryProvider.tsx |
| ReactQueryDevtools 동적 로딩 | Next.js dynamic import | QueryProvider.tsx |
| error.tsx 파일 컨벤션 | Next.js Error Handling | 각 라우트 세그먼트 |
검증 방법
pnpm build— 빌드 성공 확인pnpm lint— lint 통과 확인- 홈 페이지 로드 — 세션 목록이 스트리밍으로 표시되는지 확인 (Suspense fallback → 데이터)
- 프로필 리포트 페이지 — 통계/히스토리 각각 독립적으로 로드되는지 확인
- 로그인 모달 — 정상 동작 확인
- Network 탭 — prefetch가 await 없이 동작하여 TTFB 개선 확인
- React Query Devtools — 쿼리 상태 (stale, fresh) 확인
Reactions are currently unavailable