-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/#67 유저 정보 조회 api 관련 로직 구현 및 찜하기 낙관적 업데이트 도입 #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- axiosInstance.interceptors.response 에서 응답 처리 일괄 실행
- 사용자 프로필 및 메뉴 항목 컴포넌트 분리 - UserProfile에서만 api 데이터 사용함
- 낙관적 업데이트 도입 (UI 먼저 변경 후 api실행)
Walkthrough사용자 프로필 조회 기능을 추가하고 프로필 페이지를 재구성했으며, API 서비스 응답 처리 일부를 단순화하고 좋아요 뮤테이션 성공 시 관련 캐시(byLike 및 detail)를 병렬로 무효화하도록 변경했습니다. 또한 Next 이미지 원격 패턴과 USER API 경로를 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant LikeBtn as LikeButton
participant Mutation as Like Mutation
participant QC as QueryClient
participant API as API
rect rgb(230, 245, 255)
User->>LikeBtn: 좋아요 클릭
alt isPending
LikeBtn->>User: 무시(작업 중)
else
LikeBtn->>LikeBtn: 낙관적 업데이트(isLiked 토글)
LikeBtn->>Mutation: toggleLikeMutate 호출
Mutation->>API: POST/DELETE
end
end
alt 성공
API-->>Mutation: { placeId }
Mutation->>QC: invalidateQueries(byLike)
Mutation->>QC: invalidateQueries(detail(placeId))
QC-->>LikeBtn: 업데이트된 데이터
else 실패
Mutation->>LikeBtn: onError
LikeBtn->>LikeBtn: isLiked 롤백
end
sequenceDiagram
participant Page as Profile Page
participant HB as HydrationBoundary
participant QC as QueryClient
participant UP as UserProfile
participant API as API
Page->>HB: prefetch(useUserQueries.detail)
HB->>QC: prefetch 쿼리 등록
QC->>API: getUserData 요청
API-->>QC: 사용자 데이터
QC-->>HB: 캐시 저장
HB->>UP: 렌더 트리로 진입
UP->>QC: useSuspenseQuery(useUserQueries.detail)
QC-->>UP: 캐시된 데이터 반환
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 분
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🔇 Additional comments (1)
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (6)
apps/web/app/profile/page.tsx (1)
33-39: 외부 URL을 상수로 분리하는 것을 권장합니다.Notion URL이 하드코딩되어 있습니다. 향후 URL 변경이나 환경별 분기를 위해
CLIENT_PATH또는 별도의 상수 파일로 관리하는 것이 좋습니다.+// apps/web/app/_constants/path.ts에 추가 +export const EXTERNAL_LINKS = { + TERMS_OF_SERVICE: 'https://www.notion.so/woopaca/722d2e1180f94eeead36ec09436d4576?pvs=4', +} // page.tsx - <ProfileMenuItem - href={ - 'https://www.notion.so/woopaca/722d2e1180f94eeead36ec09436d4576?pvs=4' - } - title={'이용약관'} - icon={'paper'} - /> + <ProfileMenuItem + href={EXTERNAL_LINKS.TERMS_OF_SERVICE} + title={'이용약관'} + icon={'paper'} + />apps/web/app/_apis/schemas/user.ts (1)
3-7: HEX 색상 코드 유효성 검증을 추가하는 것을 권장합니다.
profileBackgroundHexCode가 단순 문자열로 정의되어 있어 잘못된 형식의 값이 들어올 수 있습니다. 정규식을 사용한 검증을 추가하면 런타임 에러를 방지할 수 있습니다.export const UserSchema = z.object({ nickname: z.string(), profileImageUrl: z.url(), - profileBackgroundHexCode: z.string(), + profileBackgroundHexCode: z + .string() + .regex(/^[0-9A-Fa-f]{6}$/, 'Invalid hex color code (expected format: RRGGBB)'), })apps/web/app/profile/_components/UserProfile/UserProfile.tsx (1)
19-25: 이미지 품질 설정을 재검토해주세요.
quality={100}은 최고 품질 설정으로 파일 크기가 커질 수 있습니다. 프로필 이미지는 작은 크기(35x35px)이므로quality={80}정도로도 충분한 품질을 얻을 수 있으며 성능이 개선됩니다.<Image src={profileImageUrl} alt={'profileImage'} width={35} height={35} - quality={100} + quality={80} />apps/web/app/places/[id]/_components/LikeButton/LikeButton.tsx (1)
48-60: 로딩 상태에 대한 시각적 피드백을 추가하는 것을 권장합니다.
isPending상태일 때 버튼의 투명도를 조절하거나 커서 스타일을 변경하면 사용자에게 더 명확한 피드백을 제공할 수 있습니다.return ( <motion.button onClick={onClick} animate={{ scale: isAnimating ? 1.3 : 1 }} transition={{ duration: 0.2, ease: 'easeOut' }} - className='relative flex items-center justify-center p-2' + className='relative flex items-center justify-center p-2' + style={{ + opacity: isPending ? 0.6 : 1, + cursor: isPending ? 'not-allowed' : 'pointer' + }} >apps/web/app/profile/_components/ProfileMenuItem/ProfileMenuItem.tsx (1)
11-19: 내부 링크에는 Next.js Link 컴포넌트 사용을 권장합니다.현재
<a>태그를 사용하고 있어 내부 페이지 이동 시 전체 페이지가 새로고침됩니다. Next.js의Link컴포넌트를 사용하면 클라이언트 사이드 네비게이션으로 더 빠른 전환이 가능합니다.외부 링크(Notion)는
<a>태그가 적절하므로, href가 외부 링크인지 판단하여 조건부로 렌더링하는 것도 고려해보세요.+import Link from 'next/link' import { Icon, IconType } from '@repo/ui/components/Icon' import { Flex, JustifyBetween } from '@repo/ui/components/Layout' import { Text } from '@repo/ui/components/Text' type Props = { href: string title: string icon: IconType } -export const ProfileMenuItem = ({ href, title, icon }: Props) => ( - <JustifyBetween as={'a'} href={href}> +export const ProfileMenuItem = ({ href, title, icon }: Props) => { + const isExternalLink = href.startsWith('http') + const Component = isExternalLink ? 'a' : Link + + return ( + <JustifyBetween as={Component} href={href}> <Flex className={'gap-2.5'}> <Icon type={icon} size={18} /> <Text variant={'body1'}>{title}</Text> </Flex> <Icon type={'arrowRight'} size={18} color={'--color-gray-200'} /> </JustifyBetween> -) + ) +}apps/web/app/_apis/mutations/useAddLike.ts (1)
10-22: 좋아요 성공 시 리스트/상세 동시 무효화 설계 좋습니다만, placeId 누락 시 최소 로깅을 권장합니다
addLike응답에서placeId를 꺼내서 찜 리스트와 상세 정보를 동시에 무효화하는 흐름은 버그 재발을 막는 데 충분해 보입니다. 다만 현재는placeId가 없으면 조용히return해서, 서버 응답 이상이나 스키마 변경 시 캐시가 갱신되지 않아도 원인을 찾기 어려울 수 있습니다.아주 간단히라도 로그를 남기는 쪽을 고려해 보세요:
- if (!placeId) return + if (!placeId) { + console.error('addLike 응답에 placeId가 없습니다.', response) + return + }이 정도만 있어도 예상치 못한 응답 형태를 빠르게 캐치하는 데 도움이 됩니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
apps/web/app/_apis/mutations/useAddLike.ts(1 hunks)apps/web/app/_apis/mutations/useRemoveLike.ts(1 hunks)apps/web/app/_apis/queries/user.ts(1 hunks)apps/web/app/_apis/schemas/user.ts(1 hunks)apps/web/app/_apis/services/category.ts(1 hunks)apps/web/app/_apis/services/event.ts(1 hunks)apps/web/app/_apis/services/like.ts(1 hunks)apps/web/app/_apis/services/place.ts(3 hunks)apps/web/app/_apis/services/request.ts(1 hunks)apps/web/app/_apis/services/user.ts(1 hunks)apps/web/app/_constants/path.ts(1 hunks)apps/web/app/places/[id]/_components/LikeButton/LikeButton.tsx(2 hunks)apps/web/app/profile/ProfilePage.tsx(0 hunks)apps/web/app/profile/_components/ProfileMenuItem/ProfileMenuItem.tsx(1 hunks)apps/web/app/profile/_components/ProfileMenuItem/index.ts(1 hunks)apps/web/app/profile/_components/UserProfile/UserProfile.tsx(1 hunks)apps/web/app/profile/_components/UserProfile/index.ts(1 hunks)apps/web/app/profile/page.tsx(1 hunks)apps/web/next.config.ts(1 hunks)
💤 Files with no reviewable changes (1)
- apps/web/app/profile/ProfilePage.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
apps/web/app/_apis/services/category.ts (4)
apps/web/app/categories/[id]/CategoryDetailPage.tsx (1)
data(15-55)apps/web/app/_components/Categories/Categories.tsx (2)
data(8-20)category(15-17)apps/web/app/categories/[id]/page.tsx (1)
generateMetadata(7-27)apps/web/app/_apis/queries/category.ts (1)
queryOptions(12-16)
apps/web/app/_apis/services/event.ts (5)
apps/web/app/_apis/schemas/event.ts (4)
PublicEventSchema(3-8)PrivateEvent(31-31)PrivateEventSchema(10-21)EventResult(32-32)apps/web/app/_apis/mutations/useParticipationEvent.ts (1)
body(10-12)apps/web/app/_apis/queries/event.ts (3)
EventQueryKeys(10-10)queryOptions(16-20)queryOptions(21-25)apps/web/app/events/lucky-draw/_components/Pages/Participation/Participation.tsx (1)
useDisclosure(13-52)apps/web/app/events/lucky-draw/_components/Pages/Participation/ParticipationModal.tsx (1)
participationEvent(29-31)
apps/web/app/_apis/services/user.ts (2)
apps/web/app/_apis/schemas/user.ts (2)
User(9-9)UserSchema(3-7)apps/web/app/_constants/path.ts (1)
API_PATH(4-49)
apps/web/app/_apis/mutations/useRemoveLike.ts (1)
apps/web/app/_apis/queries/place.ts (1)
PlaceQueryKeys(13-25)
apps/web/app/places/[id]/_components/LikeButton/LikeButton.tsx (3)
apps/web/app/_apis/services/like.ts (2)
addLike(9-12)removeLike(14-19)apps/web/app/_apis/mutations/useAddLike.ts (1)
useAddLike(5-27)apps/web/app/_apis/mutations/useRemoveLike.ts (1)
useRemoveLike(5-27)
apps/web/app/profile/_components/UserProfile/UserProfile.tsx (1)
apps/web/app/_apis/queries/user.ts (1)
useUserQueries(9-15)
apps/web/app/_apis/services/place.ts (2)
apps/web/app/_constants/path.ts (1)
API_PATH(4-49)apps/web/app/_apis/schemas/place.ts (5)
BasePlaceSchema(27-33)BasePlace(76-76)PlaceByMapSchema(35-38)PlaceDetail(78-78)PlaceByPreviewSchema(48-55)
apps/web/app/profile/page.tsx (5)
apps/web/app/page.tsx (1)
Page(25-59)apps/web/app/HydrationBoundaryPage.tsx (1)
HydrationBoundaryPage(29-44)apps/web/app/_apis/queries/user.ts (1)
useUserQueries(9-15)apps/web/app/profile/_components/UserProfile/UserProfile.tsx (1)
UserProfile(9-32)apps/web/app/profile/_components/ProfileMenuItem/ProfileMenuItem.tsx (1)
ProfileMenuItem(11-19)
apps/web/app/_apis/services/request.ts (2)
apps/web/app/_constants/path.ts (1)
API_PATH(4-49)apps/web/app/_apis/schemas/request.ts (2)
RequestSchema(7-13)RequestDetail(23-23)
apps/web/app/_apis/services/like.ts (1)
apps/web/app/_constants/path.ts (1)
API_PATH(4-49)
apps/web/app/_apis/mutations/useAddLike.ts (1)
apps/web/app/_apis/queries/place.ts (1)
PlaceQueryKeys(13-25)
apps/web/app/_apis/queries/user.ts (1)
apps/web/app/_apis/services/user.ts (1)
getUserData(5-8)
🔇 Additional comments (15)
apps/web/app/_apis/services/category.ts (1)
6-6: 코드 간소화가 잘 적용되었습니다.불필요한 중간 변수를 제거하여 axios 응답에서 직접
data를 구조 분해하는 방식으로 개선되었습니다. 이 패턴이 PR 전체에서 일관되게 적용되어 코드 가독성이 향상되었습니다.apps/web/app/_apis/services/place.ts (1)
28-31: 일관된 리팩토링이 잘 적용되었습니다.모든 장소 관련 API 함수에서 응답 구조 분해 패턴이 일관되게 개선되었습니다.
getSearchPlaceByKakao함수는 별도의 axios 인스턴스를 사용하므로 적절하게 제외되었습니다.Also applies to: 38-41, 50-58, 62-63, 88-91, 95-96
apps/web/app/_apis/services/event.ts (1)
13-14: 일관된 코드 개선이 적용되었습니다.이벤트 관련 모든 API 함수에서 응답 구조 분해 방식이 통일되어 코드의 일관성이 향상되었습니다.
Also applies to: 18-19, 26-27, 31-32
apps/web/app/profile/_components/UserProfile/index.ts (1)
1-1: 표준 배럴 익스포트 패턴이 적절하게 사용되었습니다.컴포넌트를 모듈화하고 깔끔한 import 경로를 제공하는 표준적인 구조입니다.
apps/web/app/profile/_components/ProfileMenuItem/index.ts (1)
1-1: 일관된 컴포넌트 구조가 적용되었습니다.UserProfile과 동일한 배럴 익스포트 패턴을 따라 일관성 있는 코드 구조를 유지하고 있습니다.
apps/web/app/_apis/services/user.ts (1)
5-8: 새로운 유저 서비스가 기존 패턴을 잘 따르고 있습니다.다른 서비스 파일들과 일관된 구조로 구현되었으며, Zod 스키마를 통한 응답 검증도 적절하게 적용되었습니다. PR 전체의 리팩토링 방향과도 일치합니다.
단,
API_PATH.USER가apps/web/app/_constants/path.ts에서 선행 슬래시 없이 정의되어 있으므로, 해당 파일의 리뷰 코멘트를 참고하여 경로 형식을 통일하는 것을 권장합니다.apps/web/app/_apis/mutations/useRemoveLike.ts (1)
10-22: 병렬 쿼리 무효화 로직이 잘 구현되었습니다.찜 제거 후 '찜 리스트'와 '맛집 상세 정보' 쿼리를 동시에 무효화하여 캐시 일관성을 확보했습니다.
useAddLike와 동일한 패턴을 따라 코드의 일관성도 좋습니다.apps/web/app/profile/page.tsx (1)
14-21: 서버 사이드 프리페칭 패턴이 올바르게 적용되었습니다.
HydrationBoundaryPage를 사용하여 유저 데이터를 미리 가져오고UserProfile컴포넌트에 주입하는 구조가 좋습니다. 다른 페이지(예:page.tsx)와 일관된 패턴을 따르고 있습니다.apps/web/app/_apis/services/like.ts (1)
9-12: 코드 간소화가 잘 적용되었습니다.불필요한 중간 변수를 제거하고 직접 구조 분해하여 가독성이 향상되었습니다.
apps/web/app/_apis/services/request.ts (1)
10-18: 응답 처리 로직 간소화가 잘 적용되었습니다.
like.ts와 동일한 패턴으로 중간 변수를 제거하여 코드베이스 전체의 일관성이 향상되었습니다.apps/web/app/profile/_components/UserProfile/UserProfile.tsx (1)
9-32: 컴포넌트 구조와 데이터 페칭이 올바르게 구현되었습니다.
useSuspenseQuery를 사용하여 유저 데이터를 가져오고 렌더링하는 패턴이 좋습니다. React 19의 Suspense 패턴과 잘 통합되어 있습니다.apps/web/app/places/[id]/_components/LikeButton/LikeButton.tsx (3)
17-20: 중복 클릭 방지 로직이 잘 구현되었습니다.양쪽 mutation의 pending 상태를 모두 확인하여 요청 중 중복 클릭을 방지하는 것이 좋습니다.
22-42: 낙관적 업데이트와 에러 롤백이 올바르게 구현되었습니다.UI를 먼저 업데이트하고 API 실패 시 이전 상태로 되돌리는 패턴이 잘 적용되었습니다. 사용자 경험이 크게 개선될 것입니다.
44-46: useEffect의 필요성을 재검토해주세요.props가 변경될 때 상태를 동기화하는 로직이지만, 낙관적 업데이트 중에
initIsLiked가 변경되면 의도하지 않은 상태 재설정이 발생할 수 있습니다.만약 사용자가 좋아요를 클릭한 직후(낙관적 업데이트 적용 후) 백그라운드 쿼리 재페칭으로
initIsLiked가 업데이트되면, useEffect가 낙관적 상태를 덮어쓸 위험이 있습니다.다음 시나리오를 테스트해보시기 바랍니다:
- 좋아요 버튼 클릭 (낙관적 업데이트)
- API 요청 진행 중
- 다른 쿼리 무효화로 인해 상세 정보가 재페칭되어
initIsLiked가 업데이트- useEffect가 트리거되어 낙관적 상태가 덮어써지는지 확인
대안으로는 mutation의
onSuccess에서만 상태를 업데이트하거나, useEffect에isPending체크를 추가하는 방법을 고려해보세요:useEffect(() => { if (!isPending) { setIsLiked(initIsLiked) } }, [initIsLiked, isPending])apps/web/app/_apis/queries/user.ts (1)
1-15: 유저 쿼리 키/헬퍼 분리 패턴이 깔끔하고 재사용성 좋아 보입니다
UserQueryKeys로 키를 모듈화하고,useUserQueries.detail()로queryKey/queryFn을 한 곳에서 관리하는 구조가 다른 쿼리 모듈(PlaceQueryKeys등)과도 일관되고, 서버/클라이언트 어디서든 재사용하기 좋은 형태입니다. 현재 요구사항 범위에서는 추가 손볼 부분 없이 그대로 사용해도 무방해 보입니다.
| type Response = { | ||
| placeId: string | ||
| placeId: number | ||
| message: string | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Response 타입 변경을 백엔드 API와 검증해주세요.
placeId의 타입이 string에서 number로 변경되었습니다. 백엔드 API 응답 스펙과 일치하는지 확인이 필요합니다. 만약 백엔드가 문자열을 반환한다면 런타임 에러가 발생할 수 있습니다.
백엔드 API 응답 형식을 확인하기 위한 스크립트:
🏁 Script executed:
#!/bin/bash
# 백엔드 API 스펙 문서나 타입 정의를 확인합니다.
# OpenAPI/Swagger 문서가 있다면 해당 엔드포인트의 응답 스키마를 확인하세요.
# 코드베이스에서 placeId 타입 사용을 확인
rg -nP --type=ts 'placeId.*:.*string' -A2 -B2Length of output: 1658
추가 검증을 위해 코드베이스 내 Response 타입 사용 패턴과 API 응답 스펙을 확인하겠습니다.
---
🏁 Script executed:
```shell
#!/bin/bash
# 코드베이스에서 Response 타입 사용처를 확인
rg -nP --type=ts 'Response' apps/web/app/_apis/services/like.ts -A5 -B5
Length of output: 550
🏁 Script executed:
#!/bin/bash
# Response 타입이 어디서 사용되는지 확인
rg -nP --type=ts 'addLike|removeLike' apps/web -A3 -B3Length of output: 4277
🏁 Script executed:
#!/bin/bash
# 백엔드 API 명세나 타입 정의 파일 찾기
fd -e ts -e d.ts -e json | grep -i 'api\|spec\|schema\|types' | head -20Length of output: 819
🏁 Script executed:
#!/bin/bash
# like.ts 파일의 전체 내용 확인
cat apps/web/app/_apis/services/like.tsLength of output: 502
---
🏁 Script executed:
```shell
#!/bin/bash
# place 스키마 확인
cat apps/web/app/_apis/schemas/place.ts
Length of output: 1935
🏁 Script executed:
#!/bin/bash
# like 관련 스키마가 있는지 확인
rg -nP --type=ts 'placeId.*number|placeId.*string' apps/web/app/_apis/schemas/Length of output: 160
🏁 Script executed:
#!/bin/bash
# 백엔드 API 경로 확인 - placeId가 어떻게 사용되는지
rg -nP 'PLACES\.LIKE' apps/web --type=ts -B2 -A2Length of output: 2178
🏁 Script executed:
#!/bin/bash
# useAddLike 및 useRemoveLike에서 Response placeId 사용 전체 확인
cat apps/web/app/_apis/mutations/useAddLike.ts && echo "---" && cat apps/web/app/_apis/mutations/useRemoveLike.tsLength of output: 1532
---
🏁 Script executed:
```shell
#!/bin/bash
# Mock 응답 데이터 확인
cat apps/web/app/_mocks/handlers/likeHandlers.ts
Length of output: 464
🏁 Script executed:
#!/bin/bash
# PlaceQueryKeys.detail 함수 확인 - String() 변환이 필요한 이유 확인
rg -nP 'detail.*:' apps/web/app/_apis/queries/place.ts -B5 -A5Length of output: 842
Response 타입의 placeId를 number에서 string으로 수정해주세요.
Mock 데이터(apps/web/app/_mocks/handlers/likeHandlers.ts)에서 백엔드 응답은 placeId: '1'(문자열)로 정의되어 있으나, Response 타입(apps/web/app/_apis/services/like.ts)은 placeId: number로 선언되어 있습니다.
현재 코드에서 useAddLike.ts와 useRemoveLike.ts가 String(placeId) 변환을 하는 것도 이 불일치 때문입니다. TypeScript는 런타임 타입 체크를 하지 않으므로, 실제 백엔드도 문자열을 반환한다면 예상치 못한 타입 에러가 발생할 수 있습니다.
수정 위치: apps/web/app/_apis/services/like.ts 4-7줄
type Response = {
placeId: string
message: string
}🤖 Prompt for AI Agents
In apps/web/app/_apis/services/like.ts around lines 4 to 7, the Response.type
declares placeId as number but the mock/backend returns a string; change placeId
to string in the Response type (placeId: string) and then update callers (e.g.,
remove or adjust String(placeId) conversions in useAddLike/useRemoveLike and any
other usages) so TypeScript types align with the backend/mock response.
| { | ||
| protocol: 'https', | ||
| hostname: process.env.NEXT_PUBLIC_API_URL_HOST || '', //테스트용 주소 | ||
| pathname: '/**', | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
환경 변수 미설정 시 빈 문자열 폴백으로 인한 이미지 로딩 실패 가능성
NEXT_PUBLIC_API_URL_HOST 환경 변수가 설정되지 않으면 hostname이 빈 문자열이 되어 Next.js 이미지 최적화가 실패할 수 있습니다.
다음 중 하나의 방식으로 수정하는 것을 권장합니다:
옵션 1: 유효한 기본값 제공
{
protocol: 'https',
- hostname: process.env.NEXT_PUBLIC_API_URL_HOST || '', //테스트용 주소
+ hostname: process.env.NEXT_PUBLIC_API_URL_HOST || 'localhost', //테스트용 주소
pathname: '/**',
},옵션 2: 환경 변수가 설정된 경우에만 패턴 추가
remotePatterns: [
// ... existing patterns
...(process.env.NEXT_PUBLIC_API_URL_HOST
? [
{
protocol: 'https' as const,
hostname: process.env.NEXT_PUBLIC_API_URL_HOST,
pathname: '/**',
},
]
: []),
],🤖 Prompt for AI Agents
In apps/web/next.config.ts around lines 38 to 42, hostname is set to
process.env.NEXT_PUBLIC_API_URL_HOST || '' which yields an empty string when the
env var is missing and can break Next.js image optimization; fix by either
providing a sensible default host (e.g. 'localhost' or your CDN host) instead of
'', or conditionally add the remotePattern only when
process.env.NEXT_PUBLIC_API_URL_HOST is truthy (i.e., wrap the pattern in a
conditional spread so no pattern with an empty hostname is included), ensuring
the resulting remotePatterns array contains only valid hostnames.
#️⃣연관된 이슈
📝작업 내용
프로필 페이지의 구조를 개선하고, 찜 기능의 UX와 데이터 동기화 로직을 강화했습니다.
1. 프로필 페이지 컴포넌트 구조 리팩토링
프로필 페이지 내에서 실제 API 데이터를 사용하는 부분이 '유저 프로필 정보' 부분에만 사용되어 있어, 관심사 분리를 위해 구조를 변경했습니다.
2.찜 쿼리 무효화(Invalidation) 로직 개선
3. 찜하기 버튼 낙관적 업데이트 적용
스크린샷 (선택)
💬리뷰 요구사항(선택)
Summary by CodeRabbit
새로운 기능
버그 수정
리팩토링
✏️ Tip: You can customize this high-level summary in your review settings.