Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. Walkthrough검색 및 검색결과 페이지와 관련 UI 컴포넌트를 추가하고 라우팅을 확장했습니다. NavItem 인터페이스를 다중 경로로 변경하고 NavBar 활성 로직을 업데이트했으며, Input에 onKeyDown을 추가하고 일부 UI를 배럴화했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant SP as SearchPage
participant R as Router
participant SRP as SearchResultPage
U->>SP: 키워드 입력 + Enter
SP->>R: navigate("/searchResult?keyword=...")
R->>SRP: 렌더(SearchResultPage)
SRP->>SRP: query에서 keyword 동기화 및 결과 렌더
U->>SRP: 결과 아이템 클릭
SRP->>R: navigate("/discover/<id>")
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Summary of Changes
Hello @maylh, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
이 PR은 검색 페이지 및 검색 결과 페이지의 UI를 구현하고, 관련 라우팅을 추가합니다. 또한, 기존 내비게이션 바의 다중 경로 처리 기능을 개선하고, 전역 Suspense 설정을 변경하여 애플리케이션의 사용자 경험을 향상시키는 것을 목표로 합니다.
Highlights
- 검색 페이지 및 검색 결과 페이지 UI 구현: 사용자가 검색어를 입력하고 결과를 확인할 수 있는 새로운 페이지들이 추가되었습니다.
- 홈페이지, 검색 페이지, 검색 결과 페이지 라우트 추가: React Router를 사용하여 이들 페이지로의 이동 경로가 설정되었습니다.
- SearchResultItem 컴포넌트 개선: 검색 결과 항목을 표시하는 SearchResultItem 컴포넌트에 type (플레이리스트 또는 사용자) prop이 추가되어, 백엔드에서 반환될 데이터 형식에 맞춰 유연하게 표시할 수 있도록 준비되었습니다.
- 네비게이션 바 다중 경로 처리 지원: NAV_ITEMS 설정이 path 대신 paths 배열을 사용하여 하나의 내비게이션 항목이 여러 관련 경로에서 활성화될 수 있도록 수정되었습니다.
- Routes 전체를 Suspense로 감싸도록 변경: 개별 페이지에 적용되던 Suspense가 Routes 컴포넌트 전체를 감싸도록 변경되어 로딩 처리 방식이 중앙화되었습니다.
- Input 컴포넌트에 onKeyDown prop 추가: 검색 입력 필드에서 엔터 키를 눌러 검색을 실행할 수 있도록 Input 컴포넌트에 onKeyDown 이벤트 핸들러가 추가되었습니다.
- 새로운 UI 컴포넌트 추가: CategoryButton과 TrendKeyword 컴포넌트가 새로 추가되어 검색 페이지의 UI를 구성하는 데 사용됩니다.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
이번 PR은 검색 페이지와 검색 결과 페이지의 UI를 구현하고 관련 라우팅을 설정하는 내용을 담고 있습니다. 전반적으로 FSD 아키텍처에 따라 구조가 잘 잡혀있고, 재사용 가능한 컴포넌트들이 잘 활용되었습니다.
몇 가지 개선점을 제안합니다:
- 검색 결과 페이지에서 항목 클릭 시의 사용자 경험을 개선할 수 있는 방안을 제안했습니다.
- TypeScript 타입 안정성을 높이기 위해 타입 단언 대신 명시적 타입 정의를 사용하는 방법을 제안했습니다.
- 코드 가독성과 유지보수성 향상을 위해 컴포넌트 파일에서 목업 데이터를 분리하는 것을 권장했습니다.
- 웹 접근성 향상을 위해 시맨틱 HTML 태그 사용을 제안했습니다.
PR에 언급해주신 내용들도 잘 확인했습니다. 특히 네비게이션 바의 다중 경로 처리나 Suspense 적용 범위에 대한 고민은 좋은 접근이라고 생각합니다. 코드 리뷰가 개발에 도움이 되기를 바랍니다.
|
🎵 Storybook Link 🎵 |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (24)
src/pages/searchPage/ui/TrendKeyword.tsx (1)
7-9: div 대신 button으로 변경하고 onClick/접근성 보완을 권장해당 컴포넌트는 시각적으로 버튼(칩)이며
:active상태를 정의하고 있어 상호작용 요소임이 암시됩니다. 하지만 현재div로 구현되어 있어 키보드 포커스/엔터 동작/스크린리더 접근성이 부족합니다. 추후 클릭으로 검색 실행/전환이 붙을 가능성이 높으므로 button으로 전환하고 onClick 및 포커스 스타일을 추가하는 것을 권장합니다. 또한 포인터 커서도 함께 지정하는 것이 좋습니다.적용 예시:
interface TrendKeywordProps { text: string + onClick?: () => void } -const TrendKeyword = ({ text }: TrendKeywordProps) => { - return <StyledButton>{text}</StyledButton> +const TrendKeyword = ({ text, onClick }: TrendKeywordProps) => { + return ( + <StyledButton type="button" onClick={onClick}> + {text} + </StyledButton> + ) } export default TrendKeyword -const StyledButton = styled.div` +const StyledButton = styled.button` width: fit-content; background-color: ${({ theme }) => theme.COLOR['gray-700']}; color: ${({ theme }) => theme.COLOR['gray-50']}; padding: 6px 12px; border-radius: 99px; ${({ theme }) => theme.FONT['body2-normal']}; + cursor: pointer; + transition: background-color 0.2s ease, color 0.2s ease; + &:focus-visible { + outline: 2px solid ${({ theme }) => theme.COLOR['primary-normal']}; + outline-offset: 2px; + } &:active { background-color: ${({ theme }) => theme.COLOR['primary-normal']}; color: ${({ theme }) => theme.COLOR['gray-900']}; } `참고: 향후 “선택됨(토글)” 상태가 필요하다면
selected?: booleanprops를 추가하고:active대신 props 기반 동적 스타일을 적용하는 편이 일관됩니다.Also applies to: 13-25
src/shared/ui/NavBar.tsx (2)
16-18: 활성 상태 판별: 하위 경로 및 트레일링 슬래시 대응
paths.includes(location.pathname)는 정확히 일치하는 경우만 활성 처리됩니다./discover/123같은 하위 경로가 생기면 비활성으로 보일 수 있습니다. 단순하고 안전한 쪽으로startsWith기반으로 개선을 제안합니다. 루트(/)는 특수 처리해야 합니다.- {NAV_ITEMS.map(({ icon: Icon, title, paths }) => { - const isActive = paths.includes(location.pathname) + {NAV_ITEMS.map(({ icon: Icon, title, paths }) => { + const isActive = paths.some((p) => + p === '/' + ? location.pathname === '/' + : location.pathname === p || location.pathname.startsWith(`${p}/`) + ) const color = isActive ? theme.COLOR['primary-normal'] : theme.COLOR['gray-100']대안: react-router의
matchPath를 사용하면 패턴 매칭을 더 유연하게 할 수 있습니다. 필요 시 요청 주세요, 해당 방식으로도 패치 드리겠습니다.
21-21: key 안정성 및 a11y 향상
- key로
title을 쓰면 다국어 전환 등으로 title이 바뀔 때 불필요한 재마운트가 발생할 수 있습니다. 기본 경로를 key로 쓰는 편이 안정적입니다.- 현재 페이지 표시를 위해
aria-current="page"를 부여하면 보조기기 접근성이 개선됩니다.- <NavLink to={paths[0]} key={title}> + <NavLink to={paths[0]} key={paths[0]} aria-current={isActive ? 'page' : undefined}>src/app/routes/routes.tsx (2)
19-19: 라우트 경로 네이밍 일관성: 소문자/케밥 케이스 권장URL은 일반적으로 소문자 케밥 케이스를 권장합니다. 현재
/searchResult는 대문자를 포함하고 있어 SEO/가독성 측면에서 일관성이 떨어집니다./search-results로 조정하는 것을 제안합니다.아래 변경 시, navItems 등 경로를 참조하는 설정도 함께 업데이트해 주세요.
- <Route path="/searchResult" element={<SearchResultPage />} /> + <Route path="/search-results" element={<SearchResultPage />} />
15-29: Routes 전체를 하나의 Suspense로 감싸는 전략: UX 트레이드오프 점검 제안전역 Suspense는 간결하지만, 라우트 전환 시 전체 화면이 로딩 상태로 전환될 수 있습니다. 페이지별 스켈레톤/로딩 UI를 유지하고, 전역 Suspense는 최소화하는 혼합 전략도 고려해볼 수 있습니다. 특히 홈/검색 페이지는 진입 빈도가 높아 프리패치(import().then) 또는 idle preloading도 검토해볼 가치가 있습니다.
전환 시 전역 로딩이 UX에 미치는 영향(플리커/레이아웃 점프)이 수용 가능한지 QA에서 확인 부탁드립니다.
src/pages/searchPage/ui/SearchResultItem.tsx (3)
36-41: button 기본 type 명시로 의도치 않은 폼 제출 방지styled.button은 폼 내부에서 기본 type이 'submit'로 동작할 수 있습니다. 명시적으로 type="button"을 지정해 사이드이펙트를 방지하는 것을 권장합니다.
-const ItemContainer = styled.button` +const ItemContainer = styled.button.attrs({ type: 'button' })` display: flex; gap: 12px; width: 335px; `
6-12: props를 판별 유니온으로 강화해 타입 안정성 향상플레이리스트의 경우 userName이 반드시 필요하고, 유저 타입에선 불필요합니다. 판별 유니온을 사용하면 컴파일 타임에 이를 강제할 수 있습니다.
-interface SearchResultItemProps { - imageUrl?: string - type: 'playlist' | 'user' - searchResult: string - userName?: string | null - onClick: () => void -} +type SearchResultBaseProps = { + imageUrl?: string + searchResult: string + onClick: () => void +} + +type PlaylistResultProps = SearchResultBaseProps & { + type: 'playlist' + userName: string +} + +type UserResultProps = SearchResultBaseProps & { + type: 'user' + userName?: null | undefined +} + +type SearchResultItemProps = PlaylistResultProps | UserResultProps
48-65: 텍스트 말줄임 처리 유연화: 하드코딩된 max-width 제거 제안해상도/레이아웃 변화에 대응하기 위해 픽셀 고정값 대신 flex 기반으로 말줄임 처리하는 것을 권장합니다. Right에 min-width: 0을 주고, SearchResult는 width: 100%로 처리하면 컨테이너 크기에 맞춰 자연스럽게 동작합니다.
const Right = styled.div` display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 4px; + min-width: 0; ` const SearchResult = styled.span` - display: inline-block; - max-width: 267px; + display: block; + width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: ${({ theme }) => theme.COLOR['gray-50']}; ${({ theme }) => theme.FONT.headline2}; `src/pages/searchPage/index.tsx (4)
17-19: 뒤로가기 안전 처리(히스토리 없을 때 홈으로 폴백)히스토리 스택이 없는 진입(직접 URL 접근)에서 navigate(-1)는 무효합니다. 폴백을 추가해 주세요.
- left={<SvgButton icon={LeftArrow} onClick={() => navigate(-1)} />} + left={ + <SvgButton + icon={LeftArrow} + onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/'))} + /> + }
34-35: 페이지 내 다중 h1 사용 → 섹션 헤딩을 h2로 조정 권장페이지당 h1은 한 번만 사용하는 것이 시맨틱/접근성 측면에서 안전합니다. 섹션 타이틀은 h2가 적합합니다.
- <h1>인기 검색어</h1> + <h2>인기 검색어</h2>- <h1>장르와 테마</h1> + <h2>장르와 테마</h2>- & h1 { + & h2 { ${({ theme }) => theme.FONT.headline1}; font-weight: 600; }Also applies to: 42-43, 60-63
43-47: 카테고리 선택 시 검색과의 연결 고려CategoryButton 클릭 시 해당 키워드로 즉시 검색하거나 검색창에 채워 넣는 인터랙션을 제공하면 사용성이 좋아집니다. 추후 API 연동 시 onClick 핸들러 연결을 제안합니다.
87-113: 목데이터 분리페이지 파일 내 목데이터는 분리하여 관리하는 것이 유지보수에 유리합니다. 예: src/pages/searchPage/mocks.ts 또는 fixtures로 이동.
src/stories/TrendKeyword.stories.ts (1)
6-6: 스토리 카테고리 정합성TrendKeyword는 searchPage UI 산출물로 보입니다. Storybook 트리에서 ‘Shared’보다는 ‘Search’와 같은 도메인 카테고리가 더 적합해 보입니다.
- title: 'Shared/TrendKeyword', + title: 'Search/TrendKeyword',src/shared/ui/CategoryButton.tsx (1)
14-17: 사이즈 토큰의 테마화 고려sizes를 추후 theme 또는 상수 모듈로 승격하면 일관성 유지와 스케일링에 유리합니다. 현재는 이 수준으로 충분하나, 재사용도가 높아지면 분리 권장합니다.
src/pages/searchPage/SearchResultPage.tsx (6)
88-114: Fast Refresh 경고 해결: mock 데이터를 별도 파일로 분리하고 이 파일에서는 컴포넌트만 export 하세요현재 파일에서 컴포넌트 외에
searchResultMockData를 함께 export 하고 있어 Fast Refresh 최적화가 깨질 수 있습니다. mock/fixture는 별도 파일로 분리하고 이 파일은 페이지 컴포넌트만 export 하도록 권장합니다.적용 예시 (이 파일 수정):
- export const searchResultMockData = [ + const searchResultMockData = [ { id: 1, type: 'playlist', searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎', userName: 'deulak', }, ... ]mock 분리 예시(새 파일 추가):
- 새 파일: src/pages/searchPage/searchResult.mock.ts
export type SearchResultItemData = { id: number type: 'playlist' | 'user' searchResult: string userName?: string imageUrl?: string } export const searchResultMockData: SearchResultItemData[] = [ { id: 1, type: 'playlist', searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎', userName: 'deulak' }, { id: 2, type: 'playlist', searchResult: '카페 재즈 모음', userName: 'jazzlover' }, { id: 3, type: 'user', imageUrl: 'image/url/example.png', searchResult: '김들락' }, { id: 5, type: 'playlist', searchResult: '새벽감성 인디 플레이리스트', userName: 'deulak' }, ]이 파일 상단 import 예시:
+ import { searchResultMockData } from './searchResult.mock'
26-29: 검색어 공백 방지 및 중복 로직 제거: handleSearch에서 trim/guard 처리엔터 처리(onKeyDown)와 동일한 네비게이션 로직이 중복되어 있습니다. 한 곳(handleSearch)에서만 처리하고, 공백 검색은 방지하는 편이 안전합니다.
- const handleSearch = (keyword: string) => { - setSearchValue(keyword) - navigate(`/searchResult?keyword=${encodeURIComponent(keyword)}`) - } + const handleSearch = (keyword: string) => { + const trimmed = keyword.trim() + if (!trimmed) return + setSearchValue(trimmed) + navigate(`/searchResult?keyword=${encodeURIComponent(trimmed)}`) + }
44-48: Enter 키 처리 DRY: handleSearch 재사용엔터 시에도 동일한 로직을 handleSearch로 위임하세요.
- onKeyDown={(e) => { - if (e.key === 'Enter') { - navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`) - } - }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleSearch(searchValue) + } + }}
1-1: 미세 최적화: handleSearch를 useCallback으로 메모이즈(선택)리스트 아이템의 onClick에 핸들러를 전달하므로,
useCallback으로 불필요한 재생성을 줄일 수 있습니다.-import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react'그리고:
const handleSearch = useCallback((keyword: string) => { const trimmed = keyword.trim() if (!trimmed) return setSearchValue(trimmed) navigate(`/searchResult?keyword=${encodeURIComponent(trimmed)}`) }, [navigate])
82-86: 접근성(A11y): 리스트는 시맨틱 태그 사용 고려검색 결과 목록은 의미상 리스트입니다.
<div>대신<ul>/<li>또는role="list"/role="listitem"적용을 고려해 주세요.예시:
-const ResultList = styled.div` +const ResultList = styled.ul` display: flex; flex-direction: column; gap: 20px; `렌더링 시:
- <ResultList> - {searchResultMockData.map((item) => ( - <SearchResultItem key={item.id} ... /> - ))} - </ResultList> + <ResultList role="list"> + {searchResultMockData.map((item) => ( + <li key={item.id} role="listitem" style={{ listStyle: 'none' }}> + <SearchResultItem {...props} /> + </li> + ))} + </ResultList>
104-104: 오타: 'expample' → 'example'mock 데이터의 URL 철자 오타입니다.
- imageUrl: 'image/url/expample.png', + imageUrl: 'image/url/example.png',src/stories/SearchResultItem.stories.tsx (4)
10-16: argTypes의 type 컨트롤이 스토리 렌더에서 덮어써져 비활성화됨각 스토리에서
type을 JSX로 고정하여 전달하고 있어, 컨트롤러에서 변경해도 반영되지 않습니다. 컨트롤을 활용하려면 렌더에서는{...args}만 전달하고,args에서 기본값으로 타입을 지정하세요.
28-32: 컨트롤 친화적 렌더: JSX에서 type 하드코딩 제거렌더에서
type="playlist"를 제거하고args에 기본값을 설정하세요. 이렇게 하면 Controls 패널에서 상호작용이 가능합니다.export const PlaylistSearchResult: Story = { - render: (args) => ( - <Container> - <SearchResultItem {...args} type="playlist" /> - </Container> - ), + render: (args) => ( + <Container> + <SearchResultItem {...args} /> + </Container> + ), args: { + type: 'playlist', searchResult: '플레이리스트 #1', userName: 'deulak', }, }
40-44: 동일 이슈: User 스토리도 JSX 하드코딩 제거User 스토리도 동일하게 수정해 주세요.
export const UserSearchResult: Story = { - render: (args) => ( - <Container> - <SearchResultItem {...args} type="user" /> - </Container> - ), + render: (args) => ( + <Container> + <SearchResultItem {...args} /> + </Container> + ), args: { + type: 'user', searchResult: '닉네임예시입니다', }, }
45-47: User 스토리 기본 이미지 제공 권장
imageUrl컨트롤을 비활성화한 상태라면, User 스토리에서 기본imageUrl을 제공하여 UI가 의도대로 보이도록 하는 것이 좋습니다.export const UserSearchResult: Story = { ... args: { type: 'user', searchResult: '닉네임예시입니다', + imageUrl: 'https://picsum.photos/seed/search-result-user/80/80', }, }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (18)
src/app/routes/routes.tsx(1 hunks)src/pages/homePage/index.tsx(1 hunks)src/pages/homePage/ui/index.ts(1 hunks)src/pages/searchPage/SearchResultPage.tsx(1 hunks)src/pages/searchPage/index.tsx(1 hunks)src/pages/searchPage/ui/SearchResultItem.tsx(1 hunks)src/pages/searchPage/ui/TrendKeyword.tsx(1 hunks)src/pages/searchPage/ui/index.ts(1 hunks)src/shared/config/navItems.ts(1 hunks)src/shared/ui/CategoryButton.tsx(1 hunks)src/shared/ui/Header.tsx(0 hunks)src/shared/ui/Input.tsx(3 hunks)src/shared/ui/NavBar.tsx(1 hunks)src/shared/ui/index.ts(1 hunks)src/stories/CategoryButton.stories.tsx(1 hunks)src/stories/SearchResultItem.stories.tsx(3 hunks)src/stories/TrendKeyword.stories.ts(1 hunks)src/widgets/playlist/index.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/shared/ui/Header.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit Configuration File
**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션포맷팅
.prettierrc설정에 따라 포맷팅 확인- 들여쓰기: 2칸 스페이스
- 최대 줄 길이: 100자
- 세미콜론 사용 안함
- 따옴표: 작은따옴표 사용
- 괄호 안 공백: 있음
- 화살표 함수 괄호: 항상 사용
- 줄바꿈: LF 사용
네이밍 컨벤션
- 컴포넌트: PascalCase (예: UserProfile)
- 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
- 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
- 이미지 파일: kebab-case (예: user-profile-icon.png)
주석 사용
- 복잡한 로직에만 주석 추가
- 불필요한 주석 지양 (코드로 설명 가능한 것)
- TODO/FIXME 형식:
// TODO: 설명 - 작성자가독성
- 매직 넘버 지양, 의미있는 상수 사용
- 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
- 중첩 깊이 최소화 (3단계 이하 권장)
2. React 모범 사례
컴포넌트 작성
- 최신 React hooks 사용 권장
- 컴포넌트는 단일 책임 원칙 준수
- Presentational/Container 컴포넌트 분리
- 성능 최적화: memo, useCallback, useMemo 적절히 사용
- 대용량 리스트는 가상화 라이브러리 사용 고려
상태 관리
- Zustand와 Tanstack Query를 일관되게 사용
- 상태 구조는 정규화된 형태로 관리
- 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용
3. 스타일링
Styled Components
- Styled Components 일관되게 사용
- 스타일드 컴포넌트명은 의미있게 작성
- 동적 스타일링은 props나 CSS 변수 활용
- 테마 시스템 활용하여 글로벌 스타일 관리
- 재사용 가능한 스타일은 mixin이나 확장으로 관리
- CSS 포맷팅 가독성 유지
- 사용하지 않는 스타일이나 중복 스타일 제거
4. Vite 및 빌드 최적화
- 모듈 import 최적화 (tree-shaking 고려)
- 환경변수는 .env 파일로 관리
- vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)
5. 아키텍처 및 개발 환경
폴더 구조
- Feature-Sliced Design (FSD) 구조 준수
- 레이어별 참조 규칙 엄격히 적용
타입스크립트
- strict 모드 사용
- 타입 명시적으로 작성 (any 사용 지양)
- path alias (@/_) 절대 경로 import 사용
Git 훅
- Husky 설정으로 pre-commit, commit-msg 린팅 확인
6. 기타 가이드라인
- 충분한 근거와 함께 리뷰 제공
- 정보 검증 후 답변
- 간결하고 명확한 응답
- 필요시 추가 컨텍스트 요청
- 검증되지 않은 주장 지양
- 가능한 경우 출처 명시
- 별도 언급 없으면 JavaScript 기준
- 한국어로 응답
- 대부분 브라우저에서 지원하는 ES6+ 기능 활용
- 코드 리뷰를 통한 유지보수성 향상에 적극 활용
Files:
src/pages/searchPage/ui/index.tssrc/pages/homePage/ui/index.tssrc/shared/ui/index.tssrc/pages/searchPage/index.tsxsrc/stories/TrendKeyword.stories.tssrc/widgets/playlist/index.tssrc/shared/ui/Input.tsxsrc/shared/ui/CategoryButton.tsxsrc/pages/searchPage/ui/TrendKeyword.tsxsrc/shared/ui/NavBar.tsxsrc/stories/CategoryButton.stories.tsxsrc/pages/searchPage/SearchResultPage.tsxsrc/pages/homePage/index.tsxsrc/shared/config/navItems.tssrc/pages/searchPage/ui/SearchResultItem.tsxsrc/app/routes/routes.tsxsrc/stories/SearchResultItem.stories.tsx
🧠 Learnings (1)
📚 Learning: 2025-08-12T18:31:01.433Z
Learnt from: maylh
PR: dnd-side-project/dnd-13th-8-frontend#14
File: src/stories/NavBar.stories.ts:1-1
Timestamp: 2025-08-12T18:31:01.433Z
Learning: Storybook에서 Meta, StoryObj 타입은 'storybook/react'가 아닌 프레임워크별 패키지에서 import해야 함. React-Vite 프로젝트의 경우 'storybook/react-vite'에서 import하는 것이 공식 가이드에 따른 올바른 방식임.
Applied to files:
src/stories/TrendKeyword.stories.tssrc/stories/CategoryButton.stories.tsx
🧬 Code Graph Analysis (5)
src/pages/searchPage/index.tsx (1)
src/shared/styles/theme.ts (1)
theme(3-116)
src/shared/ui/CategoryButton.tsx (1)
src/shared/styles/theme.ts (1)
theme(3-116)
src/pages/searchPage/ui/TrendKeyword.tsx (1)
src/shared/styles/theme.ts (1)
theme(3-116)
src/shared/ui/NavBar.tsx (2)
src/shared/config/navItems.ts (1)
NAV_ITEMS(11-16)src/shared/styles/theme.ts (1)
theme(3-116)
src/pages/searchPage/SearchResultPage.tsx (2)
src/shared/hooks/useSingleSelect.ts (1)
useSingleSelect(3-11)src/shared/ui/ContentHeader.tsx (1)
SortType(8-8)
🪛 GitHub Check: Build and Lint
src/pages/searchPage/SearchResultPage.tsx
[warning] 88-88:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Chromatic
🔇 Additional comments (13)
src/shared/ui/Input.tsx (1)
30-31: onKeyDown 전달 추가 좋습니다입력값에서 Enter 처리 등을 위해 필요한 이벤트 핸들러가 정상적으로 타입 정의되고 실제 input 엘리먼트에 전달됩니다. 하위 호환성에도 영향이 없습니다. LGTM.
Also applies to: 48-49, 67-67
src/shared/config/navItems.ts (1)
8-9: 다중 경로 지원 설계 타당NavItem의
paths: string[]전환과 홈 항목에 검색/검색결과 경로를 포함시키는 접근이 요구사항과 잘 부합합니다. 라우팅/활성화 로직과의 결합(첫 요소를 기본 이동 경로로 사용)도 명확합니다. LGTM.Also applies to: 12-16
src/pages/searchPage/ui/index.ts (1)
1-2: 배럴 익스포트 추가 적절UI 컴포넌트의 진입점을 일관되게 제공하여 import 경로를 단순화합니다. LGTM.
src/pages/homePage/ui/index.ts (1)
1-2: 배럴(export) 구성 LGTMUI 하위 컴포넌트를 배럴로 노출하는 방향이 일관성 있고, import 경로 단축과 트리 셰이킹에도 유리합니다. 변경사항 문제없습니다.
src/shared/ui/index.ts (1)
14-15: UI 배럴에 컴포넌트 추가 LGTMScrollCarousel, CategoryButton의 배럴 노출이 일관된 공용 UI API 표면을 유지하도록 도와줍니다. 추가적인 우려 없습니다.
src/widgets/playlist/index.ts (1)
1-2: 플레이리스트 위젯 배럴화 LGTM위젯 모듈도 배럴로 정리되어 소비측에서 명확한 import가 가능해졌습니다. 문제 없습니다.
src/app/routes/routes.tsx (1)
8-11: 지연 로딩 구성 LGTMHomePage, SearchPage, SearchResultPage를 코드 스플리팅한 구성이 명확합니다. PrivateRoute와의 조합도 문제 없어 보입니다.
src/stories/TrendKeyword.stories.ts (2)
1-1: Storybook 타입 import가 가이드와 일치합니다우리 프로젝트 컨벤션(react-vite 프레임워크별 패키지에서 타입 import)에 부합합니다.
1-21: Storybook preview에 ThemeProvider가 이미 적용되어 있습니다
.storybook/preview.tsx에서 전역 데코레이터로ThemeProvider(theme)와GlobalStyle이 설정되어 있어, 해당 스토리에서도 테마가 정상 적용됩니다.
- .storybook/preview.tsx: 26–30라인
src/stories/CategoryButton.stories.tsx (3)
1-1: Storybook 타입 import 컨벤션 준수React-Vite 환경에 맞춰 '@storybook/react-vite'에서 타입을 import한 점 좋습니다.
21-31: Small 스토리 컨테이너 폭 지정 적절small 변형이 width: 100%를 사용하므로 컨테이너 폭(160px)으로 실제 렌더 폭을 제어한 접근이 적절합니다.
1-38: Storybook 전역 테마 적용 확인 완료
.storybook/preview.tsx에서ThemeProvider와theme설정이 정상적으로 적용된 것이 확인되었습니다. 추가 조치 불필요합니다.src/pages/homePage/index.tsx (1)
7-9: 배럴(named) 익스포트 전환 LGTMUI/위젯 배럴을 통한 import로 응집도/가독성이 개선되었습니다. 런타임 영향도 없고 일관성도 좋습니다.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/pages/searchPage/SearchResultPage.tsx (1)
60-68: 타입 단언(as) 제거 및 null 전달 지양
- as 단언 없이도 동작하도록 mock 데이터에 명시적 타입을 부여하면 안전합니다.
- React/TS 관례상 선택 props는 undefined로 생략하고 null 전달을 피하는 편이 좋습니다.
- {searchResultMockData.map((item) => ( + {searchResultMockData.map((item) => ( <SearchResultItem key={item.id} - type={item.type as 'playlist' | 'user'} + type={item.type} searchResult={item.searchResult} imageUrl={item.imageUrl} - userName={item.type === 'playlist' ? item.userName : null} - onClick={() => handleItemClick(item.type)} + {...(item.type === 'playlist' && item.userName ? { userName: item.userName } : {})} + onClick={() => handleItemClick(item)} /> ))}
🧹 Nitpick comments (4)
src/pages/searchPage/SearchResultPage.tsx (4)
47-51: 엔터 입력 처리 보완: IME 조합 중 입력·공백 검색·중복 네비게이션 방지한글/일본어 입력기의 조합 중 Enter 입력을 방지하고, 공백만 입력 시 네비게이션을 막으며, 동일 키워드일 때는 replace로 히스토리 오염을 줄이는 편이 안전합니다.
- onKeyDown={(e) => { - if (e.key === 'Enter') { - navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`) - } - }} + onKeyDown={(e) => { + if (e.key === 'Enter' && !(('isComposing' in e.nativeEvent) && e.nativeEvent.isComposing)) { + const next = searchValue.trim() + if (!next) return + const current = (keyword ?? '').trim() + const url = `/searchResult?keyword=${encodeURIComponent(next)}` + if (next === current) { + navigate(url, { replace: true }) + } else { + navigate(url) + } + } + }}
26-33: 아이템 클릭 시 상세 라우팅 및 타입 안전화현재 두 타입 모두 '/discover'로 이동합니다. 일반적으로는 각 타입의 상세 페이지로 이동하는 것이 자연스럽습니다. 아이템 전체를 인자로 받아 타입에 따라 분기하세요.
실제 라우팅 스펙(예: /playlist/:id, /user/:userName)을 확인해 주세요.
- const handleItemClick = (type: string) => { - if (type === 'playlist') { - navigate('/discover') - } else if (type === 'user') { - navigate('/discover') - } - } + const handleItemClick = (item: typeof searchResultMockData[number]) => { + if (item.type === 'playlist') { + // TODO: 실제 라우팅 스펙에 맞게 수정 + navigate(`/playlist/${item.id}`) + } else if (item.type === 'user') { + // TODO: 실제 라우팅 스펙에 맞게 수정 + navigate(`/user/${encodeURIComponent(item.searchResult)}`) + } + }
91-117: Fast Refresh 경고 해결: 컴포넌트 파일의 named export 제거 + mock 타입 명시컴포넌트 파일에서 컴포넌트 외 named export를 제공하면 Fast Refresh가 깨질 수 있습니다. 경고에 따라 export를 제거하고, 동시에 mock 데이터에 명시적 타입을 부여해 위의 단언 제거까지 함께 해결하세요.
-export const searchResultMockData = [ +type SearchResult = { + id: number + type: 'playlist' | 'user' + searchResult: string + userName?: string + imageUrl?: string +} + +const searchResultMockData: SearchResult[] = [ { id: 1, type: 'playlist', searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎', userName: 'deulak', }, { id: 2, type: 'playlist', searchResult: '카페 재즈 모음', userName: 'jazzlover', }, { id: 3, type: 'user', - imageUrl: 'image/url/expample.png', + imageUrl: 'image/url/example.png', searchResult: '김들락', }, { id: 5, type: 'playlist', searchResult: '새벽감성 인디 플레이리스트', userName: 'deulak', }, ]참고: mock을 다른 곳에서 재사용할 계획이라면, 별도 파일(예: src/pages/searchPage/_mock/searchResultMockData.ts)로 분리하여 import하는 방식을 권장합니다. 현재 파일에서는 위와 같이 export만 제거해도 Fast Refresh 경고는 사라집니다.
107-107: 문자열 오탈자(nit): 'expample' → 'example'이미 위 diff에 반영했습니다. 실제 이미지 경로라면 정확한 URL을 확인해 주세요.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/pages/searchPage/SearchResultPage.tsx(1 hunks)src/pages/searchPage/ui/TrendKeyword.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/searchPage/ui/TrendKeyword.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit Configuration File
**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션포맷팅
.prettierrc설정에 따라 포맷팅 확인- 들여쓰기: 2칸 스페이스
- 최대 줄 길이: 100자
- 세미콜론 사용 안함
- 따옴표: 작은따옴표 사용
- 괄호 안 공백: 있음
- 화살표 함수 괄호: 항상 사용
- 줄바꿈: LF 사용
네이밍 컨벤션
- 컴포넌트: PascalCase (예: UserProfile)
- 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
- 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
- 이미지 파일: kebab-case (예: user-profile-icon.png)
주석 사용
- 복잡한 로직에만 주석 추가
- 불필요한 주석 지양 (코드로 설명 가능한 것)
- TODO/FIXME 형식:
// TODO: 설명 - 작성자가독성
- 매직 넘버 지양, 의미있는 상수 사용
- 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
- 중첩 깊이 최소화 (3단계 이하 권장)
2. React 모범 사례
컴포넌트 작성
- 최신 React hooks 사용 권장
- 컴포넌트는 단일 책임 원칙 준수
- Presentational/Container 컴포넌트 분리
- 성능 최적화: memo, useCallback, useMemo 적절히 사용
- 대용량 리스트는 가상화 라이브러리 사용 고려
상태 관리
- Zustand와 Tanstack Query를 일관되게 사용
- 상태 구조는 정규화된 형태로 관리
- 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용
3. 스타일링
Styled Components
- Styled Components 일관되게 사용
- 스타일드 컴포넌트명은 의미있게 작성
- 동적 스타일링은 props나 CSS 변수 활용
- 테마 시스템 활용하여 글로벌 스타일 관리
- 재사용 가능한 스타일은 mixin이나 확장으로 관리
- CSS 포맷팅 가독성 유지
- 사용하지 않는 스타일이나 중복 스타일 제거
4. Vite 및 빌드 최적화
- 모듈 import 최적화 (tree-shaking 고려)
- 환경변수는 .env 파일로 관리
- vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)
5. 아키텍처 및 개발 환경
폴더 구조
- Feature-Sliced Design (FSD) 구조 준수
- 레이어별 참조 규칙 엄격히 적용
타입스크립트
- strict 모드 사용
- 타입 명시적으로 작성 (any 사용 지양)
- path alias (@/_) 절대 경로 import 사용
Git 훅
- Husky 설정으로 pre-commit, commit-msg 린팅 확인
6. 기타 가이드라인
- 충분한 근거와 함께 리뷰 제공
- 정보 검증 후 답변
- 간결하고 명확한 응답
- 필요시 추가 컨텍스트 요청
- 검증되지 않은 주장 지양
- 가능한 경우 출처 명시
- 별도 언급 없으면 JavaScript 기준
- 한국어로 응답
- 대부분 브라우저에서 지원하는 ES6+ 기능 활용
- 코드 리뷰를 통한 유지보수성 향상에 적극 활용
Files:
src/pages/searchPage/SearchResultPage.tsx
🧬 Code Graph Analysis (1)
src/pages/searchPage/SearchResultPage.tsx (2)
src/shared/hooks/useSingleSelect.ts (1)
useSingleSelect(3-11)src/shared/ui/ContentHeader.tsx (1)
SortType(8-8)
🪛 GitHub Check: Build and Lint
src/pages/searchPage/SearchResultPage.tsx
[warning] 91-91:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
🔇 Additional comments (1)
src/pages/searchPage/SearchResultPage.tsx (1)
54-58: 정렬 상태 연동 👍useSingleSelect과 ContentHeader의 currentSort/onSortChange 연동이 깔끔합니다. SortType 범위도 일관적입니다.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
src/pages/searchPage/SearchResultPage.tsx (2)
26-28: 검색 결과 항목 클릭 시 타입별 라우팅 고려현재는 항상
/discover/:id로 이동합니다. 보통은 타입(playlist/user)에 따라 각 상세 페이지로 이동하는 동작이 자연스럽습니다. 이전 리뷰에서도 동일 제안이 있었습니다.예시(프로젝트 라우트 구조에 맞게 경로만 조정해 주세요):
- const handleItemClick = (id: number) => { - navigate(`/discover/${id}`) - } + const handleItemClick = (id: number, type: 'playlist' | 'user') => { + if (type === 'playlist') { + navigate(`/playlist/${id}`) // TODO: 실제 라우트에 맞게 변경 + } else { + navigate(`/user/${id}`) // TODO: 실제 라우트에 맞게 변경 (userName 등으로 이동 시 수정) + } + }- onClick={() => handleItemClick(item.id)} + onClick={() => handleItemClick(item.id, item.type)}Also applies to: 55-63
58-61: 타입 단언(as) 제거 및 null 전달 지양
type={item.type as 'playlist' | 'user'}단언은 타입 안정성을 낮춥니다. 또한userName에null을 넘기기보다 prop을 생략(또는undefined)하는 패턴이 일반적입니다. 이전 리뷰와 동일한 맥락입니다.권장 수정:
- type={item.type as 'playlist' | 'user'} + type={item.type} ... - userName={item.type === 'playlist' ? item.userName : null} + {...(item.type === 'playlist' && item.userName ? { userName: item.userName } : {})}추가로,
SearchResultItem의userNameprop 타입이string | undefined(optional)인지 확인 부탁드립니다. 만약string | null로 선언되어 있다면 아래 둘 중 하나로 정리하는 것을 권장합니다.
- props에서
userName?: string으로 변경- 혹은 호출부에서 항상
string | null규약을 지키도록 처리검증 스크립트:
#!/usr/bin/env bash # SearchResultItem의 props에서 userName 타입 확인 rg -n -C2 --type=tsx '(interface|type)\s+SearchResultItemProps\b|\buserName\??:' src
🧹 Nitpick comments (4)
src/pages/searchPage/SearchResultPage.tsx (4)
42-46: IME(한글 등) 조합 입력 시 엔터 처리 및 빈 검색어 가드 추가 제안조합 중(한글 입력 등) Enter가 눌리면 의도치 않은 검색이 발생할 수 있습니다. 또한 공백만 있는 검색어는 네비게이션을 생략하는 편이 UX에 좋습니다.
아래처럼
isComposing체크와trim()가드를 추가해 주세요.- onKeyDown={(e) => { - if (e.key === 'Enter') { - navigate(`/searchResult?keyword=${encodeURIComponent(searchValue)}`) - } - }} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.nativeEvent.isComposing) { + const value = searchValue.trim() + if (value) { + navigate(`/searchResult?keyword=${encodeURIComponent(value)}`) + } + } + }}
86-112: 컴포넌트 파일 내 mock 데이터 export 분리 권장 (Fast Refresh 경고 해소, FSD 정렬)이 파일에서 컴포넌트 외 상수를 export하면 React Fast Refresh가 비활성화될 수 있다는 경고가 있습니다. 또한 FSD 원칙상 mock은 별도 파일로 분리하는 것이 바람직합니다.
적용 방향:
- mock 데이터를 별도 파일로 이동(예:
src/pages/searchPage/_mock/searchResult.ts)- 이 파일에서는 컴포넌트만 export
이 파일 변경:
+import { searchResultMockData } from './_mock/searchResult'-export const searchResultMockData = [ - { - id: 1, - type: 'playlist', - searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎', - userName: 'deulak', - }, - { - id: 2, - type: 'playlist', - searchResult: '카페 재즈 모음', - userName: 'jazzlover', - }, - { - id: 3, - type: 'user', - imageUrl: 'image/url/expample.png', - searchResult: '김들락', - }, - { - id: 5, - type: 'playlist', - searchResult: '새벽감성 인디 플레이리스트', - userName: 'deulak', - }, -]새 파일 예시:
src/pages/searchPage/_mock/searchResult.tsexport type SearchResultData = | { id: number type: 'playlist' searchResult: string userName: string imageUrl?: string } | { id: number type: 'user' searchResult: string imageUrl?: string } export const searchResultMockData: SearchResultData[] = [ { id: 1, type: 'playlist', searchResult: '도파민이 필요할 땐 이 노동요를 들어주세요 😎', userName: 'deulak', }, { id: 2, type: 'playlist', searchResult: '카페 재즈 모음', userName: 'jazzlover', }, { id: 3, type: 'user', imageUrl: 'image/url/example.png', // typo 수정 searchResult: '김들락', }, { id: 5, type: 'playlist', searchResult: '새벽감성 인디 플레이리스트', userName: 'deulak', }, ]검증:
- 빌드/린트 경고(“Fast refresh only works when a file only exports components”)가 사라지는지 확인해 주세요.
49-66: 정렬 UI와 리스트 데이터 정렬 동기화
ContentHeader에서currentSort와onSortChange를 제공하지만 실제 렌더링 리스트는 정렬되지 않습니다. 선택된 정렬값에 따라 렌더링 데이터를 정렬하면 UX 일관성이 좋아집니다.적용 예시:
- 아래 코드(컴포넌트 상단) 추가:
// 추가: useMemo 임포트 필요 // import { useMemo } from 'react' const sortedData = useMemo(() => { // 실제 정렬 기준에 맞게 구현 (예시는 latest일 때 id 내림차순) if (selected === 'latest') { return [...searchResultMockData].sort((a, b) => b.id - a.id) } return searchResultMockData }, [selected])
- 리스트 렌더링 교체:
- {searchResultMockData.map((item) => ( + {sortedData.map((item) => (
102-102: 오탈자: 'expample' → 'example'목 이미지 URL의 철자가 잘못되었습니다. mock이더라도 혼동 방지를 위해 수정 권장합니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/pages/searchPage/SearchResultPage.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit Configuration File
**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션포맷팅
.prettierrc설정에 따라 포맷팅 확인- 들여쓰기: 2칸 스페이스
- 최대 줄 길이: 100자
- 세미콜론 사용 안함
- 따옴표: 작은따옴표 사용
- 괄호 안 공백: 있음
- 화살표 함수 괄호: 항상 사용
- 줄바꿈: LF 사용
네이밍 컨벤션
- 컴포넌트: PascalCase (예: UserProfile)
- 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
- 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
- 이미지 파일: kebab-case (예: user-profile-icon.png)
주석 사용
- 복잡한 로직에만 주석 추가
- 불필요한 주석 지양 (코드로 설명 가능한 것)
- TODO/FIXME 형식:
// TODO: 설명 - 작성자가독성
- 매직 넘버 지양, 의미있는 상수 사용
- 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
- 중첩 깊이 최소화 (3단계 이하 권장)
2. React 모범 사례
컴포넌트 작성
- 최신 React hooks 사용 권장
- 컴포넌트는 단일 책임 원칙 준수
- Presentational/Container 컴포넌트 분리
- 성능 최적화: memo, useCallback, useMemo 적절히 사용
- 대용량 리스트는 가상화 라이브러리 사용 고려
상태 관리
- Zustand와 Tanstack Query를 일관되게 사용
- 상태 구조는 정규화된 형태로 관리
- 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용
3. 스타일링
Styled Components
- Styled Components 일관되게 사용
- 스타일드 컴포넌트명은 의미있게 작성
- 동적 스타일링은 props나 CSS 변수 활용
- 테마 시스템 활용하여 글로벌 스타일 관리
- 재사용 가능한 스타일은 mixin이나 확장으로 관리
- CSS 포맷팅 가독성 유지
- 사용하지 않는 스타일이나 중복 스타일 제거
4. Vite 및 빌드 최적화
- 모듈 import 최적화 (tree-shaking 고려)
- 환경변수는 .env 파일로 관리
- vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)
5. 아키텍처 및 개발 환경
폴더 구조
- Feature-Sliced Design (FSD) 구조 준수
- 레이어별 참조 규칙 엄격히 적용
타입스크립트
- strict 모드 사용
- 타입 명시적으로 작성 (any 사용 지양)
- path alias (@/_) 절대 경로 import 사용
Git 훅
- Husky 설정으로 pre-commit, commit-msg 린팅 확인
6. 기타 가이드라인
- 충분한 근거와 함께 리뷰 제공
- 정보 검증 후 답변
- 간결하고 명확한 응답
- 필요시 추가 컨텍스트 요청
- 검증되지 않은 주장 지양
- 가능한 경우 출처 명시
- 별도 언급 없으면 JavaScript 기준
- 한국어로 응답
- 대부분 브라우저에서 지원하는 ES6+ 기능 활용
- 코드 리뷰를 통한 유지보수성 향상에 적극 활용
Files:
src/pages/searchPage/SearchResultPage.tsx
🧬 Code Graph Analysis (1)
src/pages/searchPage/SearchResultPage.tsx (2)
src/shared/hooks/useSingleSelect.ts (1)
useSingleSelect(3-11)src/shared/ui/ContentHeader.tsx (1)
SortType(8-8)
🪛 GitHub Check: Build and Lint
src/pages/searchPage/SearchResultPage.tsx
[warning] 86-86:
Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
🔇 Additional comments (1)
src/pages/searchPage/SearchResultPage.tsx (1)
19-25: URL 쿼리 파라미터 ↔ 입력값 동기화 처리 좋아요
keyword변경 시searchValue를 동기화하는 패턴은 뒤로 가기/URL 공유 등 다양한 진입 케이스에서 일관된 UX를 보장합니다. 유지해주세요.
hansololiviakim
left a comment
There was a problem hiding this comment.
멋 져 요 👍
vercel 제 계정이랑 시크릿모드 둘 다 들어가봤는데 잘 들어가져요 ! 감사합니다 ^_______^
원래 오늘 컨퍼런스 스태프 킥오프 모임 가야 하는데 흑흑.... 오늘 한 5~6시까지 달려보고 진행상황 다시 공유드릴게요! 감사합니다ㅎㅎ
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
src/pages/searchPage/index.tsx (1)
35-39: IME(한글 등) 조합 중 Enter 방지 로직 추가 제안Enter 처리 전에 IME 조합 여부를 확인하지 않아 한글 입력 중 조기 네비게이션 가능성이 있습니다. handleSearch 내부에서 trim 빈값은 걸러지지만, 조합 완료 전 상태에서도 이동 트리거가 될 수 있어 UX 이슈입니다. 아래처럼 보완을 권장합니다.
- onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSearch() - } - }} + onKeyDown={(e) => { + if (e.key !== 'Enter') return + // IME 조합 중 Enter 방지 (한글 등) + // @ts-expect-error: vendor typing for isComposing + if (e.nativeEvent?.isComposing) return + handleSearch() + }}src/shared/ui/CategoryButton.tsx (2)
3-10: 기본 버튼 type 지정 및 표준 버튼 props 전달로 재사용성 향상폼 내 우발적 submit 방지와 재사용성 강화를 위해 기본 type='button' 지정 + ButtonHTMLAttributes 확장을 권장합니다.
import styled, { css } from 'styled-components' +import type { ButtonHTMLAttributes } from 'react' -interface CategoryButtonProps { +interface CategoryButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { text: string size: 'small' | 'large' } -const CategoryButton = ({ text, size }: CategoryButtonProps) => { - return <StyledButton $size={size}>{text}</StyledButton> +const CategoryButton = ({ text, size, type = 'button', ...rest }: CategoryButtonProps) => { + return ( + <StyledButton $size={size} type={type} {...rest}> + {text} + </StyledButton> + ) }
19-27: 스타일 안전성/접근성 개선: 객체 기반 css 제거, 커서/포커스 스타일 추가css(sizes[$size])는 styled-components 버전에 따라 CSSObject 처리 호환성 이슈가 있을 수 있습니다. 명시적 속성 매핑으로 교체하고, cursor/outline 등 인터랙션 스타일을 보완하면 좋습니다.
const StyledButton = styled.button<{ $size: 'small' | 'large' }>` display: flex; align-items: flex-start; justify-content: flex-start; background-color: ${({ theme }) => theme.COLOR['gray-700']}; color: ${({ theme }) => theme.COLOR['gray-10']}; - ${({ $size }) => css(sizes[$size])} + cursor: pointer; + border: none; + width: ${({ $size }) => sizes[$size].width}; + height: ${({ $size }) => sizes[$size].height}; + border-radius: ${({ $size }) => sizes[$size].borderRadius}; + padding: ${({ $size }) => sizes[$size].padding}; + + &:focus-visible { + outline: 2px solid ${({ theme }) => theme.COLOR['primary-normal']}; + outline-offset: 2px; + } `
🧹 Nitpick comments (1)
src/pages/searchPage/index.tsx (1)
25-26: 히스토리 미존재 시 뒤로가기 대안 고려navigate(-1)는 브라우저 히스토리가 없을 때 무시됩니다. 홈 등 안전한 경로로의 폴백을 고려해 주세요. 예: onClick={() => (window.history.length > 1 ? navigate(-1) : navigate('/'))}
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/pages/searchPage/index.tsx(1 hunks)src/shared/ui/CategoryButton.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit Configuration File
**/*.{ts,tsx,js,jsx}: ## 1. 일반적인 코딩 컨벤션포맷팅
.prettierrc설정에 따라 포맷팅 확인- 들여쓰기: 2칸 스페이스
- 최대 줄 길이: 100자
- 세미콜론 사용 안함
- 따옴표: 작은따옴표 사용
- 괄호 안 공백: 있음
- 화살표 함수 괄호: 항상 사용
- 줄바꿈: LF 사용
네이밍 컨벤션
- 컴포넌트: PascalCase (예: UserProfile)
- 유틸리티/훅/변수: camelCase (예: getUserData, useUserInfo)
- 상수: UPPER_SNAKE_CASE (예: API_BASE_URL)
- 이미지 파일: kebab-case (예: user-profile-icon.png)
주석 사용
- 복잡한 로직에만 주석 추가
- 불필요한 주석 지양 (코드로 설명 가능한 것)
- TODO/FIXME 형식:
// TODO: 설명 - 작성자가독성
- 매직 넘버 지양, 의미있는 상수 사용
- 함수는 하나의 책임만 가지도록 작성 (최대 20줄 권장)
- 중첩 깊이 최소화 (3단계 이하 권장)
2. React 모범 사례
컴포넌트 작성
- 최신 React hooks 사용 권장
- 컴포넌트는 단일 책임 원칙 준수
- Presentational/Container 컴포넌트 분리
- 성능 최적화: memo, useCallback, useMemo 적절히 사용
- 대용량 리스트는 가상화 라이브러리 사용 고려
상태 관리
- Zustand와 Tanstack Query를 일관되게 사용
- 상태 구조는 정규화된 형태로 관리
- 에러 처리: Error Boundary와 try-catch 또는 onError 콜백 활용
3. 스타일링
Styled Components
- Styled Components 일관되게 사용
- 스타일드 컴포넌트명은 의미있게 작성
- 동적 스타일링은 props나 CSS 변수 활용
- 테마 시스템 활용하여 글로벌 스타일 관리
- 재사용 가능한 스타일은 mixin이나 확장으로 관리
- CSS 포맷팅 가독성 유지
- 사용하지 않는 스타일이나 중복 스타일 제거
4. Vite 및 빌드 최적화
- 모듈 import 최적화 (tree-shaking 고려)
- 환경변수는 .env 파일로 관리
- vite.config.ts에서 빌드 성능 튜닝 (sourcemap 설정, 플러그인 최적화 등)
5. 아키텍처 및 개발 환경
폴더 구조
- Feature-Sliced Design (FSD) 구조 준수
- 레이어별 참조 규칙 엄격히 적용
타입스크립트
- strict 모드 사용
- 타입 명시적으로 작성 (any 사용 지양)
- path alias (@/_) 절대 경로 import 사용
Git 훅
- Husky 설정으로 pre-commit, commit-msg 린팅 확인
6. 기타 가이드라인
- 충분한 근거와 함께 리뷰 제공
- 정보 검증 후 답변
- 간결하고 명확한 응답
- 필요시 추가 컨텍스트 요청
- 검증되지 않은 주장 지양
- 가능한 경우 출처 명시
- 별도 언급 없으면 JavaScript 기준
- 한국어로 응답
- 대부분 브라우저에서 지원하는 ES6+ 기능 활용
- 코드 리뷰를 통한 유지보수성 향상에 적극 활용
Files:
src/shared/ui/CategoryButton.tsxsrc/pages/searchPage/index.tsx
🧬 Code Graph Analysis (2)
src/shared/ui/CategoryButton.tsx (1)
src/shared/styles/theme.ts (1)
theme(3-116)
src/pages/searchPage/index.tsx (1)
src/shared/styles/theme.ts (1)
theme(3-116)
🔇 Additional comments (2)
src/pages/searchPage/index.tsx (1)
14-20: 검색 유틸 함수 분리 및 URLSearchParams 사용 적절trim 체크 + createSearchParams로 쿼리 생성한 점 좋습니다. 재사용/테스트 용이성도 좋아졌습니다.
src/shared/ui/CategoryButton.tsx (1)
19-19: transient prop($size) 사용 적절DOM 속성 누수를 막기 위해 $size 사용한 점 좋습니다.
🛰️ 관련 이슈
✨ 주요 변경 사항
<Suspense>로 감싸도록 변경🔍 테스트 방법 / 체크리스트
🗯️ PR 포인트
엔터 키를 통해서도 검색이 실행될 수 있도록 onKeyDown props를 추가했습니다.
검색 결과는 백엔드에서 플레이리스트인지 유저인지 구분하는 타입이 함께 반환될 예정이라, type props를 미리 추가했습니다. 데이터 형식에 맞춰 추후 컴포넌트를 한 번 더 수정할 계획입니다.
네비게이션 바 다중 경로 처리를 지원하도록 수정했습니다. 아래와 같이 버튼 클릭 시 이동할 기본 경로는 paths 배열의 첫 번째 요소로 설정해두었기 때문에,
활성화 경로가 여러 개인 경우, 배열 뒤쪽에 추가해주셔야 합니다 !
기존에는 각 페이지를 개별 로 감싸 로딩 상태를 처리했지만, 현재는 Routes 전체를 하나의 로 감싸도록 변경했습니다. 이 방식은 개별 페이지마다 를 추가할 필요가 없어 편리하지만, 모든 페이지가 로딩되기 전까지 로딩 화면이 표시되므로 진입 페이지에서도 불필요하게 로딩이 보일 수 있습니다 (아주 짧게 !!) 따라서 사용자가 처음 진입할 가능성이 높은
/와/discover페이지는 적용에서 제외하는 방식도 고려해보면 좋을 것 같습니다 !vercel auth 해제해두어서 전달해드린 프리뷰 링크로 접속 가능합니다 ! (혹시 안되면 말씀해주세요 . .)
🚀 알게된 점
📖 참고 자료 (선택)
🖼️ Demo
2025-08-17.04.20.36.mov
++
너무나도 당연히 검색어 누르면 다시 그 검색어로 재검색 된다고 생각했어요 ... (왜지)
우선 discover 페이지로 이동하도록 수정해두었습니다 ! 데모 영상에서 해당 부분은 눈감아주세요 ..
이게 맞는 것 같은데,
플레이리스트
유저
Summary by CodeRabbit
New Features
Refactor
Style
Documentation