Skip to content

refactor: 프로필 섹션 구조 개선#194

Merged
MintPansy merged 27 commits into
developfrom
feature/185-profile-page-refactor
Jun 3, 2026
Merged

refactor: 프로필 섹션 구조 개선#194
MintPansy merged 27 commits into
developfrom
feature/185-profile-page-refactor

Conversation

@D5ng
Copy link
Copy Markdown
Member

@D5ng D5ng commented May 31, 2026

📝 개요

Note

현재 token을 client에 저장하고 있기 때문에 console에 error가 표시되고 있어요. 이 부분은 백엔드에서 cookie 기반으로 변경하도록 해주어야 에러를 없앨 수 있고, 더 완전한 ssr을 사용할 수 있을것 같아요. 추가적으로 이번 PR에는 프로필(페이지 내 상단)만 리팩토링 해두었어요

🔗 관련 이슈

🛠️ 변경 사항 (Checklist)

  • ✨ Feature: 새로운 기능 추가
  • 🚀 Enhancement: 기존 기능 개선/성능 향상
  • 🐞 Bug: 버그 수정
  • ♻️ Refactor: 코드 구조 개선 (기능 변화 없음)
  • 🏗️ Chore: 빌드/패키지 설정/단순 잡일
  • 🎨 Design: UI/UX 스타일 수정
  • 📚 Documentation: 문서 수정

✅ 아래 내용을 한 번 더 점검해 주세요

1. 의도와 가독성 (Naming & Readability)

  • 의도 중심 네이밍: 변수명에서 '역할'이, 함수명에서 '행위+대상'이 명확히 드러나나요?
  • 선언적 코드: '어떻게'가 아닌 '무엇을' 하는지 코드만 보고도 알 수 있나요? (복잡한 로직은 내부 메서드로 숨겼나요?)
  • 주석: 코드만으로 설명이 어려운 '특정 로직'에만 주석을 달았나요?

2. 타입과 논리 (Type Safety & Logic)

  • 타입 안전성: any 사용을 지양하고, 모든 함수의 반환 타입을 명시했나요?
  • 엣지 케이스: 데이터가 없거나(null/undefined), 에러가 발생할 경우를 처리했나요?
  • 하드코딩 방지: API 주소나 설정값들이 환경 변수나 상수로 분리되었나요?

3. 코드 다이어트 (Clean-up)

  • 찌꺼기 제거: 디버깅용 console.log나 사용하지 않는 import를 모두 지웠나요?
  • 불필요한 코드: "나중에 쓰겠지" 하고 남겨둔 죽은 코드(Dead Code)는 없나요?
  • Linter: 린트 에러나 워닝이 남아있지 않나요?

4. 지속 가능성 (Sustainability)

  • 테스트: 수동으로든 코드로든 정상 작동을 확인했나요? (특히 기존 기능이 망가지지 않았나요?)
  • 문서화: 새로운 환경 변수나 라이브러리가 추가되어 README 업데이트가 필요한가요?

💭 회고 (Optional)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 프로필 공유 기능 추가 (프로필 링크 복사)
    • 프로필 페이지 향상된 로딩 상태 UI 적용
  • 버그 수정

    • 잘못된 프로필 ID 접근 시 오류 처리 개선
    • 프로필 로드 실패 시 재시도 옵션 제공
  • 개선사항

    • 프로필 페이지 안정성 강화
    • 오류 경계 및 에러 복구 메커니즘 적용

@D5ng D5ng self-assigned this May 31, 2026
@D5ng D5ng added the ♻️ Refactor 기능 변화 없이 코드 구조만 개선 label May 31, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sossbar Ready Ready Preview, Comment Jun 3, 2026 9:43am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Walkthrough

프로필 페이지 아키텍처를 서버 사이드 프리페칭 기반으로 재설계하고, Error Boundary와 Suspense로 경계 계층을 구성하며, 기존 prop 기반 로직을 재사용 가능한 훅으로 분리했습니다. 데이터 프리페칭, 로딩/에러 처리, 상태 관리 훅, 컴포넌트 구조 리팩터를 체계적으로 구현합니다.

Changes

프로필 섹션 구조 개선

Layer / File(s) Summary
서버 컴포넌트 프리페치 및 하이드레이션
app/profile/[userId]/page.tsx, shared/lib/parse-positive-int.ts
프로필 페이지를 async 서버 컴포넌트로 전환하여 parsePositiveInt로 userId를 검증하고, 실패 시 notFound() 호출. React Query prefetchQuery로 내 프로필과 상세 프로필 데이터를 미리 로드하고, dehydrate로 상태를 HydrationBoundary에 주입하여 클라이언트에 전달합니다. 양수 정수 파싱 유틸을 추가하여 메타데이터와 페이지 검증에 사용합니다.
API 헬퍼 및 Query 훅
features/profile/api/fetch-my-profile.ts, features/profile/api/fetch-profile-by-id.ts, features/profile/hooks/use-my-profile.query.ts, features/profile/hooks/use-profile-by-id.query.ts, features/profile/query-keys.ts
fetchMyProfile()fetchProfileById(userId)를 추가하여 내/상세 프로필 조회를 통합합니다. useMyProfile 훅은 인증 토큰 유무에 따라 쿼리 활성화를 제어하고, useProfileById는 Suspense 기반 조회를 제공합니다. 쿼리 키에 my 항목을 추가하여 내 프로필 캐시를 분리합니다.
Boundary 계층: Error, Loading, Suspense
features/profile/components/profile-section-boundary.tsx, features/profile/components/profile-section-skeleton.tsx, shared/components/retry-error-state.tsx, package.json
ProfileSectionBoundaryQueryErrorResetBoundary+ErrorBoundary+Suspense를 조합하여 로딩 시 ProfileSectionSkeleton, 에러 시 RetryErrorState를 표시합니다. 재시도 시 오류 경계 상태를 리셋하여 데이터 재페칭을 트리거합니다. react-error-boundary 패키지를 추가하여 에러 처리 인프라를 지원합니다.
상태 관리 훅: 프로필 조회, 편집, 공유
features/profile/hooks/use-is-my-profile.ts, features/profile/hooks/use-profile-editing.ts, features/profile/hooks/use-profile-share.ts
useIsMyProfile(userId)로 내 프로필 여부를 판별하고, useProfileEditing()으로 편집 상태와 제출 흐름을 관리합니다. useProfileShare(userId)로 프로필 링크 복사 기능을 캡슐화하여 상태와 핸들러를 함께 제공합니다.
ProfileSection 리팩터: 훅 기반 상태 관리
features/profile/components/profile-section.tsx, features/profile/components/profile-owner-actions.tsx
ProfileSectionisMyProfile prop을 제거하고 훅으로 모든 상태를 계산하도록 전환합니다. 편집/공유 버튼과 툴팁을 ProfileOwnerActions 컴포넌트로 분리하여 소유자 액션 UI를 위임합니다. 프로필 데이터 조회, 편집, 공유 로직이 명확하게 분리됩니다.
클라이언트 페이지 구조 재구성
app/profile/[userId]/_components/profile-detail-view.tsx, app/profile-examples/profile-examples-client.tsx, app/profile-examples/page.tsx
ProfileDetailView를 새로운 클라이언트 컴포넌트로 생성하여 탭 UI("전체", "프로젝트별")를 구성하고 ProfileSectionBoundary로 감싼 ProfileSection을 렌더링합니다. ProfileExamplesClient는 동적 임포트로 ProfileDetailView를 로드하고 스켈레톤 로딩 상태를 표시합니다. ProfilePageClient 클라이언트 래퍼를 제거합니다.
관련 모듈 정리 및 정렬
features/profile/api/update-profile.ts, features/profile/components/profile-edit-form.tsx, features/profile/hooks/use-profile-edit-form.ts, features/profile/hooks/use-update-profile.mutation.ts, features/profile/index.ts, features/review/components/review-list/user-review-container.tsx, shared/lib/api/resolve-api-url.ts
타입 임포트 경로를 ../types로 통합하고, 훅 임포트를 hooks/ 하위로 정렬합니다. UserReviewContainerisMyProfile prop을 제거하고 useIsMyProfile(userId) 훅으로 계산하도록 변경합니다. features/profile/index.ts에서 useProfile, ProfilePageContent 내보내기를 제거하고 새 훅/컴포넌트를 추가합니다. API origin 파싱 로직을 개선하여 환경별 분기를 명확히 합니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • she0108
  • MintPansy

Poem

🐰 프로필 섹션을 갈아엎어요
서버 프리페치로 데이터 미리 챙기고,
에러/로딩은 경계로 쓱 감싸고,
훅으로 상태를 나눠 정리하니
복잡한 로직도 쏙쏙 빠져나와요! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항인 프로필 섹션 구조 개선을 명확하게 요약하고 있으며, 리팩토링 의도를 잘 반영하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 있으며, 변경 사항(리팩토링), 관련 이슈 번호, 체크리스트 항목이 모두 작성되어 있습니다.
Linked Issues check ✅ Passed 코드 변경이 이슈 #185의 모든 주요 목표를 충족합니다: 프로필 폴더 정리, ErrorBoundary와 Suspense 적용, 컴포넌트 결합도 감소, 관심사 분리, 훅 기반 상태 관리 추출이 완료되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경이 프로필 섹션 리팩토링 범위 내에 있습니다. 새 의존성(react-error-boundary), API 유틸 수정, 파싱 유틸 추가는 모두 이슈 목표 달성을 위한 필수 변경입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/185-profile-page-refactor

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
features/profile/hooks/use-update-profile.mutation.ts (1)

11-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

my 캐시도 함께 갱신해 주세요.

여기서는 detail(profile.userId)만 덮어써서, 이번 PR에서 새로 분리한 useMyProfile() 쪽 캐시는 수정 전 값으로 남습니다. 내 프로필 수정 직후 useIsMyProfile()나 다른 마이프로필 소비 지점이 stale 상태를 계속 볼 수 있으니 profileKeys.my()도 같이 갱신하거나 관련 쿼리를 invalidate하는 편이 안전합니다.

수정 예시
   return useMutation({
     mutationFn: updateProfile,
     onSuccess: (profile) => {
       queryClient.setQueryData(profileKeys.detail(profile.userId), profile);
+      queryClient.setQueryData(profileKeys.my(), profile);
     },
   });
🤖 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 `@features/profile/hooks/use-update-profile.mutation.ts` around lines 11 - 13,
In the onSuccess handler of the mutation in use-update-profile.mutation.ts you
only update profileKeys.detail(profile.userId), leaving the useMyProfile() cache
stale; update the "my" cache as well by calling
queryClient.setQueryData(profileKeys.my(), profile) (or alternatively call
queryClient.invalidateQueries(profileKeys.my())) so consumers like
useMyProfile() and useIsMyProfile() see the updated profile immediately.
🤖 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 `@app/profile-examples/profile-examples-client.tsx`:
- Around line 7-15: The dynamic import for ProfileDetailView uses ssr: false so
the server renders only ProfileSectionSkeleton; remove the ssr: false option (or
set ssr: true) in the dynamic(...) call in profile-examples-client.tsx so the
actual ProfileDetailView is included in server HTML and the page can demonstrate
server-side prefetch/hydration; keep the loading fallback
(ProfileSectionSkeleton) if desired and ensure the import still resolves
mod.ProfileDetailView as the default.

In `@app/profile/`[userId]/page.tsx:
- Around line 24-27: The parser currently allows non-integer numbers (e.g.,
"1.5") so requests like /profile/1.5 proceed; update the validation so only
positive integers are accepted: either modify parsePositiveInt to return null
when Number.isInteger(parsed) is false, or add an explicit check after parsing
(e.g., if (!Number.isInteger(profileUserId)) return { title: '프로필' } or call
notFound()) before metadata/profile lookup; target the parsePositiveInt function
and the usage around profileUserId and notFound() to ensure fractional IDs are
rejected.
- Around line 43-51: You're prefetching the authenticated profile
unconditionally (queryClient.prefetchQuery with queryKey profileKeys.my and
queryFn fetchMyProfile) which runs on the server even when no auth is present;
change the logic to only prefetch profileKeys.my when the server request
actually contains authentication (e.g., inspect incoming cookies/authorization
header in the SSR context) and skip the prefetch otherwise so the client-side
useMyProfile guard (enabled when token exists) handles it; keep the existing
unconditional prefetch for profileKeys.detail(profileUserId)/fetchProfileById
but wrap or gate the fetchMyProfile prefetch behind an auth check.

In `@features/profile/components/profile-section.tsx`:
- Around line 15-19: Wrap the server-side prefetch in
app/profile/[userId]/page.tsx (the queryClient.prefetchQuery call that primes
fetchProfileById(profileUserId)) in a try/catch, catch errors thrown by
shared/lib/response-parser (ApiError) and if err.status === 404 call notFound(),
otherwise rethrow; keep parsePositiveInt validation as-is and do not change
throwOnError in shared/lib/get-query-client.ts; this ensures missing profiles
are promoted to notFound() before rendering reaches the client-side
ProfileSectionBoundary.

In `@features/review/components/review-list/user-review-container.tsx`:
- Around line 12-13: UserReviewContainer currently calls useIsMyProfile(), which
indirectly calls useMyProfile() and can suspend/error outside
ProfileSectionBoundary; either move the ownership check into a component that is
rendered inside ProfileSectionBoundary or replace the call here with a
non-suspending ownership path (e.g., read current user id from a synchronous
source or create a useIsMyProfileNonSuspense that checks ownership without
calling useMyProfile). Locate UserReviewContainer and change the ownership logic
so this top-level container never calls useIsMyProfile/useMyProfile directly (or
wrap UserReviewContainer with its own ErrorBoundary/Suspense) to ensure the
review area still hits the PR’s fallback/retry UI when profile fetches
suspend/error.

In `@shared/lib/parse-positive-int.ts`:
- Around line 1-3: The parsePositiveInt function currently treats positive
non-integer numbers (e.g., 1.5) as valid; update parsePositiveInt to also verify
the parsed value is an integer by using Number.isInteger(number) alongside
Number.isFinite(number) and number > 0 so only positive integers pass (keep the
same return signature number | null and the same behavior of returning null for
invalid inputs).

---

Outside diff comments:
In `@features/profile/hooks/use-update-profile.mutation.ts`:
- Around line 11-13: In the onSuccess handler of the mutation in
use-update-profile.mutation.ts you only update
profileKeys.detail(profile.userId), leaving the useMyProfile() cache stale;
update the "my" cache as well by calling
queryClient.setQueryData(profileKeys.my(), profile) (or alternatively call
queryClient.invalidateQueries(profileKeys.my())) so consumers like
useMyProfile() and useIsMyProfile() see the updated profile immediately.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35b8ea38-7849-406f-9b73-40132147fc87

📥 Commits

Reviewing files that changed from the base of the PR and between 7e773fd and f6e32af.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • app/profile-examples/page.tsx
  • app/profile-examples/profile-examples-client.tsx
  • app/profile/[userId]/_components/profile-detail-view.tsx
  • app/profile/[userId]/page.tsx
  • app/profile/[userId]/profile-page-client.tsx
  • features/profile/api/fetch-my-profile.ts
  • features/profile/api/fetch-profile-by-id.ts
  • features/profile/api/update-profile.ts
  • features/profile/components/profile-edit-form.tsx
  • features/profile/components/profile-owner-actions.tsx
  • features/profile/components/profile-page-content.tsx
  • features/profile/components/profile-section-boundary.tsx
  • features/profile/components/profile-section-skeleton.tsx
  • features/profile/components/profile-section.tsx
  • features/profile/hooks/use-is-my-profile.ts
  • features/profile/hooks/use-my-profile.query.ts
  • features/profile/hooks/use-profile-by-id.query.ts
  • features/profile/hooks/use-profile-edit-form.ts
  • features/profile/hooks/use-profile-editing.ts
  • features/profile/hooks/use-profile-share.ts
  • features/profile/hooks/use-update-profile.mutation.ts
  • features/profile/index.ts
  • features/profile/queries.ts
  • features/profile/query-keys.ts
  • features/review/components/review-list/user-review-container.tsx
  • package.json
  • shared/components/retry-error-state.tsx
  • shared/lib/api/resolve-api-url.ts
  • shared/lib/parse-positive-int.ts
💤 Files with no reviewable changes (3)
  • features/profile/queries.ts
  • features/profile/components/profile-page-content.tsx
  • app/profile/[userId]/profile-page-client.tsx

Comment thread app/profile-examples/profile-examples-client.tsx
Comment thread app/profile/[userId]/page.tsx
Comment thread app/profile/[userId]/page.tsx
Comment thread features/profile/components/profile-section.tsx
Comment thread features/review/components/review-list/user-review-container.tsx
Comment thread shared/lib/parse-positive-int.ts
@MintPansy MintPansy merged commit db2aae9 into develop Jun 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️ Refactor 기능 변화 없이 코드 구조만 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] 프로필 섹션 구조 개선

2 participants