Repository navigation
API 호출 최적화 - React Query 도입과 서버 캐시 정비 - #92
Conversation
- SSR-safe QueryClient 팩토리(서버 요청별 신규, 브라우저 싱글턴)와 공통 기본값(staleTime 60s) 정의 - 루트 레이아웃에 QueryProvider 마운트, 도메인별 쿼리 키 팩토리와 테스트 헬퍼 추가 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 무한스크롤 3곳(글/프로젝트 피드, 아카이브)을 useInfiniteQuery로 전환하고 서버 시드를 initialData로 재사용 - 지연 fetch(히어로 프리뷰·인기 태그·PDF 옵션·어드민 가용성)를 useQuery로 통일, 수동 AbortController/로딩 state 제거 - slug 중복 체크와 OG 링크 프리뷰는 fetchQuery로 dedupe(30s/24h), 공용 커서 페이지네이션 훅 삭제 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 60초 TTL 모듈 캐시 삭제, useQuery(staleTime 60s)+keepPreviousData로 정렬/페이지 전환 깜빡임 제거 - 수정/삭제를 useMutation으로 전환, 변이 후 fresh 재조회→setQueryData→scope 무효화(refetchType none)로 결정적 갱신 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 게스트북/댓글 답글 N+1을 .in() 단일 쿼리로, 아카이브 윈도우 독립 쿼리를 Promise.all로 병렬화 - 인기 태그 조합을 use cache 함수로 승격, 전체 use cache 조회에 cacheLife 부여(예약 발행 글 미노출 해소) - generateMetadata/본문 중복 호출 React.cache dedupe, GSC 클라이언트 싱글턴화+15분 unstable_cache Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- React Query 캐시 모델(staleTime/gcTime/initialData), Next.js 4계층 캐시, DB 왕복 최소화 원리 정리 - gitignore에 학습 노트 2건 예외 등록 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (15)
WalkthroughReact Query( ChangesReact Query 클라이언트 도입 및 UI 전환
Estimated code review effort: 4 (Complex) | ~75 minutes 서버 캐시 정책 및 조회 최적화
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ArticleCommentsSection
participant Mutation
participant Server
participant QueryClient
User->>ArticleCommentsSection: 댓글 수정/삭제 확인
ArticleCommentsSection->>Mutation: mutateAsync 호출
Mutation->>Server: updateArticleCommentAction/deleteArticleCommentAction
Server-->>Mutation: 결과 반환
Mutation->>ArticleCommentsSection: refreshCommentsPage 호출
ArticleCommentsSection->>QueryClient: setQueryData / invalidateQueries(refetchType: none)
sequenceDiagram
participant User
participant Feed as useBrowseArticles/DetailArchiveFeed
participant ReactQuery as useInfiniteQuery
participant API
User->>Feed: loadMore() 호출
Feed->>ReactQuery: fetchNextPage()
ReactQuery->>API: cursor/limit/locale 요청
API-->>ReactQuery: 다음 페이지 데이터
ReactQuery-->>Feed: pages 병합 후 items 갱신
Possibly related PRs
Suggested labels: Tip 📚 논문 대신 리뷰 노트: cacheLife 옵션( 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d9fd7fe6c4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- KST 하드코딩 기대값 탓에 UTC CI에서 실패하던 3건(GSC 날짜 범위, 예약 발행 ISO 변환, 저장 시각 포맷)을 실행 타임존과 무관하게 검증하도록 로컬 Date 생성자 기반으로 변경 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 104135c에서 구현이 의도적으로 변경(canonical=실제 해석 locale, x-default=앱 기본 locale ko)됐으나 테스트가 옛 en-폴백 계약을 기대해 실패하던 것을 현행 동작 기준으로 수정 - 해석 locale이 유효하지 않을 때 요청 locale로 돌아가는 분기 케이스 추가 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- dom-heavy 버킷 조기 종료에 가려져 있던 타임존 의존 2건(autosave 저장 라벨, 예약 발행 UTC 표시)을 로컬 컴포넌트 기준으로 수정 - 104135c 이후 계약과 어긋난 x-default 기대값 3건(home/resume/project)을 ko 기준으로, project 아카이브는 canonical 단독 노출로 갱신 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
src/app/api/(content)/article-popular-tags/route.test.ts (1)
15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value테스트 설명을 가이드라인 형식에 맞게 작성 권장
코딩 가이드라인에서는 테스트 설명을 "Under [Condition/Context], [Subject] must [Expected Behavior/State Change]" 형식으로 작성하도록 권장합니다. 현재 설명은 계약을 전달하고 있으나, 주체(Subject)가 명시되지 않고 "must/해야 한다" 형식이 누락되어 있습니다.
예시:
'locale이 주어지면, GET 핸들러는 label이 결합된 인기 태그 목록을 반환해야 한다''인기 태그가 비어 있으면, GET 핸들러는 빈 배열을 반환해야 한다'As per coding guidelines: "Test descriptions must describe a Contract between State and Result using the format: Under [Condition/Context], [Subject] must [Expected Behavior/State Change]"
Also applies to: 37-37
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/`(content)/article-popular-tags/route.test.ts at line 15, The test description in the `it(...)` blocks for the article popular tags route should be rewritten to match the guideline format: “Under [Condition/Context], [Subject] must [Expected Behavior/State Change]”. Update the affected test names in `route.test.ts` so each one names the subject explicitly (for example, the GET handler) and uses “must” to state the expected result, keeping the existing behavior intent unchanged.Source: Coding guidelines
src/features/article-tag-filter/ui/deferred-article-tag-filter-list.tsx (1)
71-98: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
localeprop을AppLocale로 타입 지정하는 것을 권장
localeprop이string으로 타입되어 있어queryKey에서locale as AppLocale캐스팅을 사용하고 있습니다. 이 캐스팅은 타입 안전성을 우회하며, 잘못된 locale 값이 전달될 경우 예상치 못한 캐시 키가 생성될 수 있습니다. prop 타입을AppLocale로 직접 지정하면 컴파일 타임에 유효한 locale만 허용되도록 보장할 수 있습니다.♻️ 제안하는 타입 개선
type DeferredArticleTagFilterListProps = { activeTag?: string; defaultLabel: string; emptyText: string; hrefMode?: TagFilterHrefMode; - locale: string; + locale: AppLocale; source?: TagFilterSource; title?: string; onNavigationStart?: () => void; };이후
queryKey에서as AppLocale캐스팅을 제거할 수 있습니다:queryKey: - source === 'all' ? tags.all(locale as AppLocale) : articles.popularTags(locale as AppLocale), + source === 'all' ? tags.all(locale) : articles.popularTags(locale),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/article-tag-filter/ui/deferred-article-tag-filter-list.tsx` around lines 71 - 98, The locale prop is still typed as string and forces unsafe AppLocale casts in DeferredArticleTagFilterList. Update the component’s locale prop type to AppLocale so useQuery can build queryKey with the strongly typed locale directly, then remove the locale as AppLocale casts from the queryKey branches. Make sure the change is reflected anywhere DeferredArticleTagFilterList is called so only valid AppLocale values are passed in.src/shared/lib/query/query-keys.ts (1)
16-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win정규화 로직을 공용 유틸로 분리해 주세요.
query-keys.ts와get-articles.ts가 같은trim/소문자화 규칙을 각각 구현하고 있어, 한쪽만 바뀌면 캐시 키와 RPC 파라미터가 어긋날 수 있습니다.src/shared/lib로 정규화 함수를 빼서 두 곳에서 재사용하는 편이 안전합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/lib/query/query-keys.ts` around lines 16 - 27, `normalizeSearchQuery` and `normalizeArticleTag` duplicate the same trim/lowercase normalization logic, so extract this behavior into a shared utility under `src/shared/lib` and make both `query-keys.ts` and `get-articles.ts` call that shared helper. Keep the existing symbols (`normalizeSearchQuery`, `normalizeArticleTag`) as thin wrappers or replace their internal logic with the shared function so cache keys and RPC params always use the same normalization rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/api/`(content)/article-popular-tags/route.test.ts:
- Around line 1-27: The route handler test for article-popular-tags should run
in the Node environment instead of the default DOM-based setup. Add an explicit
Vitest environment directive to the route.test.ts file so the GET test and its
getLocalizedPopularArticleTags mock execute under node, matching the Route
Handler context and avoiding jsdom reliance.
In `@src/entities/pdf-file/api/get-pdf-file-availability.ts`:
- Around line 50-51: The comment in getPdfFileAvailability is copied from
article publishing logic and does not match this PDF availability check. Update
the inline comment near getPdfFileAvailability and cacheLife to describe PDF
storage existence/TTL behavior instead of publish_at/article wording, keeping
the explanation aligned with the function’s purpose.
In `@src/features/article-comment/model/use-article-comments-page.ts`:
- Around line 48-67: `useArticleCommentsPage`에서 `locale`이 queryFn 의존성인데
`articleComments.page(articleId, sort, page)` 키에 빠져 있어 로케일 전환 시 잘못된 캐시가 재사용됩니다.
`articleComments.page`가 `locale`까지 포함하도록 키 생성 로직을 수정하고, 이 훅의 `useQuery`와 함께 관련
`setQueryData` 호출도 같은 키 형태로 맞추세요. `articleComments.scope(articleId)` 기반의 상위 무효화는
그대로 유지하면 됩니다.
In `@src/features/browse-articles/model/use-browse-articles.test.ts`:
- Line 1: The test file for use-browse-articles is missing the required jsdom
environment directive, which is needed for renderHook and window.fetch spying.
Add the Vitest pragma at the top of the use-browse-articles.test.ts file so the
hook test runs in JSDOM, matching the project’s test guidelines for React hooks
and DOM-dependent logic.
In `@src/features/edit-markdown-adapter/model/fetch-link-preview-meta.ts`:
- Around line 44-51: createCachedFetchLinkPreviewMeta의 queryClient.fetchQuery
호출에 gcTime이 빠져 있어 기본 inactive GC(5분)로 캐시가 너무 빨리 사라집니다. staleTime과 동일하게
OG_PREVIEW_STALE_TIME을 gcTime에도 설정해서 fetchLinkPreviewMetaAdapter를 통해 가져온 프리뷰 메타가
24시간 동안 유지되도록 수정하세요.
In `@src/widgets/article-comments/ui/article-comments-section.tsx`:
- Around line 194-221: `refreshCommentsPage` currently returns early on a failed
`getArticleCommentsPageAction`, which skips `queryClient.invalidateQueries` and
can leave the article comments cache/UI stale. Update the `refreshCommentsPage`
callback in `article-comments-section.tsx` so the query invalidation for
`articleComments.scope(articleId)` always runs, even when the refresh fetch
fails; keep `setQueryData` and `setQueryState` only on successful `result.ok &&
result.data` paths, but move the invalidation into a path that executes
regardless of success.
---
Nitpick comments:
In `@src/app/api/`(content)/article-popular-tags/route.test.ts:
- Line 15: The test description in the `it(...)` blocks for the article popular
tags route should be rewritten to match the guideline format: “Under
[Condition/Context], [Subject] must [Expected Behavior/State Change]”. Update
the affected test names in `route.test.ts` so each one names the subject
explicitly (for example, the GET handler) and uses “must” to state the expected
result, keeping the existing behavior intent unchanged.
In `@src/features/article-tag-filter/ui/deferred-article-tag-filter-list.tsx`:
- Around line 71-98: The locale prop is still typed as string and forces unsafe
AppLocale casts in DeferredArticleTagFilterList. Update the component’s locale
prop type to AppLocale so useQuery can build queryKey with the strongly typed
locale directly, then remove the locale as AppLocale casts from the queryKey
branches. Make sure the change is reflected anywhere
DeferredArticleTagFilterList is called so only valid AppLocale values are passed
in.
In `@src/shared/lib/query/query-keys.ts`:
- Around line 16-27: `normalizeSearchQuery` and `normalizeArticleTag` duplicate
the same trim/lowercase normalization logic, so extract this behavior into a
shared utility under `src/shared/lib` and make both `query-keys.ts` and
`get-articles.ts` call that shared helper. Keep the existing symbols
(`normalizeSearchQuery`, `normalizeArticleTag`) as thin wrappers or replace
their internal logic with the shared function so cache keys and RPC params
always use the same normalization rule.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d9984aa4-4417-4bdd-8659-d4e6dfd13433
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (74)
package.jsonsrc/app/api/(content)/article-popular-tags/route.test.tssrc/app/api/(content)/article-popular-tags/route.tssrc/app/layout.tsxsrc/entities/article/api/detail/get-article-detail-list.test.tssrc/entities/article/api/detail/get-article-detail-list.tssrc/entities/article/api/detail/get-article.test.tssrc/entities/article/api/detail/get-related-articles.test.tssrc/entities/article/api/detail/get-related-articles.tssrc/entities/article/api/list/get-admin-google-article-traffic.test.tssrc/entities/article/api/list/get-admin-google-article-traffic.tssrc/entities/article/api/list/get-articles.test.tssrc/entities/article/api/list/get-articles.tssrc/entities/article/api/list/get-popular-article-tags.test.tssrc/entities/article/api/list/get-popular-article-tags.tssrc/entities/article/comment/api/get-article-comments.test.tssrc/entities/article/comment/api/get-article-comments.tssrc/entities/guestbook/api/get-guestbook-threads.test.tssrc/entities/guestbook/api/get-guestbook-threads.tssrc/entities/pdf-file/api/get-pdf-file-availability.test.tssrc/entities/pdf-file/api/get-pdf-file-availability.tssrc/entities/pdf-file/api/get-pdf-file-content.test.tssrc/entities/pdf-file/api/get-pdf-file-content.tssrc/entities/project/api/detail/get-project-detail-list.test.tssrc/entities/project/api/detail/get-project-detail-list.tssrc/entities/project/api/detail/get-project.test.tssrc/entities/project/api/list/get-projects.test.tssrc/entities/project/api/list/get-projects.tssrc/entities/tag/api/query-tags.test.tssrc/entities/tag/api/query-tags.tssrc/features/article-comment/model/article-comments-page-cache.test.tssrc/features/article-comment/model/article-comments-page-cache.tssrc/features/article-comment/model/use-article-comments-page.tssrc/features/article-tag-filter/ui/deferred-article-tag-filter-list.test.tsxsrc/features/article-tag-filter/ui/deferred-article-tag-filter-list.tsxsrc/features/browse-articles/model/use-browse-articles.test.tssrc/features/browse-articles/model/use-browse-articles.tssrc/features/browse-projects/model/use-browse-projects.tssrc/features/edit-markdown-adapter/index.tssrc/features/edit-markdown-adapter/model/fetch-link-preview-meta.tssrc/shared/lib/query/create-query-client.tssrc/shared/lib/query/query-keys.test.tssrc/shared/lib/query/query-keys.tssrc/shared/lib/react/cursor-pagination-feed-state.test.tssrc/shared/lib/react/cursor-pagination-feed-state.tssrc/shared/lib/react/use-cursor-pagination-feed.test.tsxsrc/shared/lib/react/use-cursor-pagination-feed.tssrc/shared/lib/seo/canonical.test.tssrc/shared/lib/seo/metadata.test.tssrc/shared/lib/test/render-with-query-client.tsxsrc/shared/providers/index.tssrc/shared/providers/query-provider.tsxsrc/shared/ui/markdown/link-embed-card.test.tsxsrc/shared/ui/markdown/link-embed-card.tsxsrc/shared/ui/markdown/markdown-renderer.test.tsxsrc/shared/ui/pdf-download-popover/deferred-pdf-download-popover.tsxsrc/views/articles/model/get-article-detail-page-data.tssrc/views/articles/model/get-articles-page-data.tssrc/views/project/model/get-project-detail-page-data.tssrc/views/resume/ui/resume-page.test.tsxsrc/widgets/admin-pdf-upload/ui/admin-pdf-upload-panel.test.tsxsrc/widgets/admin-pdf-upload/ui/admin-pdf-upload-panel.tsxsrc/widgets/article-comments/ui/article-comments-section.interactions.test.tsxsrc/widgets/article-comments/ui/article-comments-section.test-support.tsxsrc/widgets/article-comments/ui/article-comments-section.tsxsrc/widgets/detail-page/archive/feed.test.tsxsrc/widgets/detail-page/archive/feed.tsxsrc/widgets/editor/ui/core/editor-core.utils.test.tssrc/widgets/editor/ui/publish/publish-panel.test.tsxsrc/widgets/editor/ui/publish/publish-panel.tsxsrc/widgets/editor/ui/publish/publish-panel.utils.test.tssrc/widgets/home-hero-scene/model/use-home-hero-project-preview.test.tsxsrc/widgets/home-hero-scene/model/use-home-hero-project-preview.tssrc/widgets/home-hero-scene/ui/home-hero-scene.test.tsx
💤 Files with no reviewable changes (6)
- src/shared/lib/react/cursor-pagination-feed-state.test.ts
- src/features/article-comment/model/article-comments-page-cache.test.ts
- src/shared/lib/react/cursor-pagination-feed-state.ts
- src/shared/lib/react/use-cursor-pagination-feed.test.tsx
- src/features/article-comment/model/article-comments-page-cache.ts
- src/shared/lib/react/use-cursor-pagination-feed.ts
- 아카이브 사이드바 쿼리 키에 현재 아이템(selectedPathSegment) 포함 — 동일 커서 시드의 상세 페이지 간 캐시 혼선 방지 - 댓글 페이지 쿼리 키에 locale 포함 — 클라이언트 locale 전환 시 이전 locale 캐시 재사용 방지 - 댓글 fresh 재조회 실패(액션 실패/예외) 시 scope 즉시 무효화로 UI 자동 복구 보장 - OG 프리뷰 fetchQuery에 gcTime 24h 동기화(기본 5분 GC로 staleTime이 무력화되던 문제) - 라우트 핸들러 테스트 node 환경 명시, 훅 테스트 jsdom 명시, PDF cacheLife 오복사 주석 정정 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
작업 내용
1. React Query 도입 (클라이언트 fetch 전면 이관)
2. 댓글: 자체 Map 캐시(60s TTL) → React Query
3. 서버 쿼리 왕복 축소
.in()단일 쿼리(2회)4. 캐시 수명 정비
테스트
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes