Step 3 Search 도메인 테크스펙 문서 - 100-hours-a-week/16-team-katopia-fe GitHub Wiki
Search 도메인은 사용자가 원하는 사람(계정) 또는 원하는 콘텐츠(게시글/해시태그) 를 빠르게 찾게 만드는 “탐색 진입점”이다. FITCHECK 서비스에서 검색 경험이 느리거나 불명확하면, 사용자는 피드 탐색(Home)에서만 머물며 서비스 확장성(팔로우/커뮤니티/투표 전환)이 떨어진다.
또한, 사용자는 명확한 검색어 없이도 내 체형과 유사한 코디를 먼저 보고싶다는 니즈를 자주 가진다.
따라서 본 도메인의 목표는 다음과 같다.
-
사이즈 필터 기반 사전 탐색 지원
- 검색어 입력 이전 단계에서도, 키/몸무게/성별 사이즈 필터만으로 게시글 피드를 탐색할 수 있도록 한다.
-
체형 기반 콘텐츠 소비 경험 강화
- Profile 도메인에서 수집한 신체 정보(키, 몸무게, 성별)가 Search 도메인에서 실질적인 탐색 조건으로 활용되도록 설계한다.
-
탭 기반 검색 UX 일관성 확보
- “계정 / 게시글(콘텐츠) & 해시태그” 검색을 한 화면에서 명확히 분리하여, 사용자가 검색 의도를 즉시 이해하도록 한다.
-
입력 방식만으로 의도 판별 가능한 검색 규칙 제공
- 게시글/해시태그 검색은
#유무로 자동 분기해, 사용자의 “검색 방식 학습 비용”을 줄인다.
- 게시글/해시태그 검색은
-
대량 결과를 안정적으로 제공
- 커서 기반 인피니트 스크롤을 지원하며, 스크롤 중 중복 호출/경합을 제어해 성능/비용을 안정화한다.
- KR1: 검색 결과 최초 노출까지(입력 → 결과 렌더) 체감 지연 최소화 (debounce + 캐시 + skeleton)
- KR2: 탭 전환 시 UX 끊김 없이 결과 유지/복원 (keepPreviousData/캐시 키 전략)
- KR3: 인피니트 스크롤 중 중복 요청/경합 0건 (요청 취소/락/nextCursor 기반)
- KR4: 검색 규칙(
#로 해시태그 분기)을 사용자가 즉시 이해 (placeholder/가이드/빈 상태 문구) - KR5: 검색어 없이 사이즈 필터만 적용한 탐색에서 유의미한 결과 노출 성공률 확보
-
검색어 없는 초기 상태의 탐색 공백
- 검색어 입력 전 상태에서는 의미 있는 콘텐츠가 노출되지 않아, 사용자가 무엇을 해야 할지 망설이게 된다.
- 특히 패션/코디 도메인에서는 검색어를 모르겠는 상태가 빈번하게 발생한다.
-
체형 기반 탐색 요구를 충족하지 못함.
- 사용자는 단순 키워드보다 나와 비슷한 체형의 게시글을 우선적으로 보고 싶어하지만, 검색어 중심 설계만으로는 이러한 탐색 니즈를 충족하기 어렵다.
-
검색 의도 혼재
- “계정 찾기”와 “게시글 찾기”의 결과 UI가 완전히 다름(프로필 리스트 vs 그리드).
- 한 화면에서 섞이면 사용자는 무엇이 나오는지 예측하기 어렵다.
-
무한 스크롤의 안정성
- cursor 기반 API는 적절히 쓰면 효율적이지만, 프론트에서 중복 호출/요청 경합을 제어하지 않으면 같은 데이터가 반복되거나 스크롤이 끊길 수 있다.
-
입력 변화가 잦음
- 검색창 입력은 “매 타이핑”마다 변한다 → 매번 API 호출하면 과부하/지연/UX 악화.
-
유효성/정책 에러가 사용자에게 불친절할 가능성
-
size숫자 형식 오류(COMMON-E-003), 검색어 길이 제한(COMMON-E-004) 등의 서버 정책을 UI에서 명확히 안내하지 않으면 사용자가 계속 실패한다.
-
-
검색 랭킹/개인화(추천/가중치)
- 이번 범위에서는 “입력값 기반 단순 검색 + cursor pagination”에 집중한다.
-
오타 교정 / 자동 완성 / 연관 검색어
- 사용성은 좋지만 구현/정책 범위가 커진다. 후속 과제로 분리한다.
-
검색 히스토리/최근 검색어 동기화(서버 저장)
- 이번 범위는 클라이언트 로컬 저장 정도만 고려하고, 서버 히스토리는 제외한다.
-
복합 필터(성별/스타일/키/몸무게 등) 고급 검색
- Search UI 복잡도가 급증하므로 제외한다.
-
Framework: Next.js 16 (App Router)
- 검색 페이지(
/search)를 CSR 중심 화면으로 구성하여 입력 변화에 즉각 반응하는 UI를 제공 - App Router의 Layout 계층을 활용해 하단 탭 바, 공통 헤더, 인증 상태 여부를 일관되게 유지
- 검색 페이지(
-
Language: TypeScript 5.x
- 검색 의도 분기 로직의 타입 안정성 확보
- API 응답 구조(
posts,nextCursor)의 명시적 모델링 - Query Key 구성 요소의 타입 일관성 유지
-
Server Cache (SSOT): TanStack Query v5
- 검색 결과 리스트 및
nextCursor를 query Cache로 관리 -
useInfiniteQuery를 통해 인피니티 스크롤 구현 - queryKey에 검색 조건을 명시적으로 포함하여 탭/검색어/사이즈 필터 변경 시 결과 혼선 방지
-
keepPreviousData를 활용해 탭 전환 시 UX 깜빡임 최소화
- 검색 결과 리스트 및
-
Client/UI Global State: Zustand Search 도메인에서는 Zustand를 사용하여 검색 UI 상태를 전역으로 관리하고 prop drilling 없이 상태 접근한다. 또한, 입력/필터 변경 시 Query Key와 자연스럽게 연동된다.
-
HTTP Client: Axios (공통 API Client)
Axios 기반 공통 API Client를 사용하여 요청/응답 인터셉터에서 공통 에러 처리, query param 직렬화 일관성 유지, 향후 인증 필요 여부 변경 시 대응 용이하다.
-
Runtime Validation: Zod (Response 검증)
Zod를 활용하여 검색 결과 응답 구조 런타임 검증, 예상치 못한 응답 형식으로 인한 UI 크래시 방지한다.
-
Styling: Tailwind CSS
Tailwind CSS를 사용하여 grid column 수, gap, padding을 상태에 따라 즉각적으로 전환하고, compact / comfortable 모드의 스타일 분기 단순화한다.
주요 기능
- 검색창 입력
- 검색 탭 전환
-
계정탭 -
게시글탭 (입력값에 따라 게시글/해시태그 자동 분기)
-
- 사이즈 필터 토글 on/off
- 사이즈 필터 모달 값 입력(키, 몸무게, 성별)
- 결과 렌더
- 계정: 프로필 리스트 형태
- 게시글/해시태그: 게시물 그리드 형태
- 인피니트 스크롤
-
size,after(nextCursor)기반으로 다음 페이지 요청
-
사용 컴포넌트
SearchInputSearchTabsSizeToggleSizeFilterModalAccountResultListPostGridResultSearchEmptyStateSearchErrorState
데이터 로딩 시점
- 페이지 최초 진입 시
- 즉시 검색 API 호출하지 않음.
- “검색어 입력 대기 상태” 빈 검색어 UI 노출
- 사용자가 검색어 입력 완료 후
- debounce 처리(입력 중 실시간 호출 방지)
- 입력값 유효성 검증 통과 시 API 호출
- 탭 전환 시
- 동일 검색어 기준으로 해당 탭의 검색 API 호출
- 이전 탭 결과는 캐시에서 복원
- 스크롤 하단 도달 시
-
nextCursor가 존재하는 경우에만 다음 페이지 요청
-
라우팅
-
/search단일 라우트 유지 - 검색어, 탭, 사이즈 필터는 페이지 이동 없이 UI 상태로 관리
개요(Overview)
-
역할
- Search 페이지에서 검색어 입력과 검색 종료(뒤로가기)를 담당하는 입력 컴포넌트
-
책임
- 검색어 입력 UI 제공
- 뒤로 가기 버튼 제공
- 검색어 입력창 내 clear(X) 버튼 제공
- 설계 의도
- 검색어 입력은 Search 도메인의 모든 흐름의 시작점이므로 입력 UX에 집중한 전용 컴포넌트로 분리한다.
- 뒤로가기 및 clear 버튼은 입력 UX의 일부로 간주한다.
UI 구성요소
- 뒤로가기 버튼
- Search 화면을 종료하고 이전 화면으로 이동
- 검색어 입력창
- 텍스트 입력
- placeholder 노출
- Clear(X) 버튼
- 입력값이 있을 때만 노출
- 클릭 시 입력값 초기화
Props & Interface
interface SearchHeaderProps {
value: string;
placeholder?: string;
isInvalid?: boolean;
helperText?: string;
onChange: (value: string) => void;
onClear: () => void;
onBack: () => void;
}-
value(required)- 현재 검색어 입력값
-
placeholder(required)- 검색 입력 안내 문구
- 예) “계정 또는 게시글을 검색해보세요.”
-
isInvalid(optional)- 검색어 정책 위반 여부(2자 미안, 100자 초과 등)
-
helperText(optional)- 정책 위반 시 노출할 안내 문구
-
onChange****(required)- 입력값 변경 시 호출
-
onClear(required)- Clear(X) 버튼 클릭 시 호출
-
onBack(required)- 뒤로가기 버튼 클릭 시 호출
내부 상태 & 이벤트
- 내부 상태 없음
- 모든 상태는 상위에서 관리
이벤트 흐름 예시
- 검색어 입력 →
onChange(value) - X 버튼 클릭 →
onClear() - 뒤로가기 버튼 클릭 →
onBack()
에러 처리 / 엣지 케이스
- 검색어 길이 정책 위반 시
- SearchHeader는 직접 검증하지 않음
-
isInvalid+helperText를 받아 UI로만 표현
- 검색어가 비어있는 경우
- Clear(X) 버튼 미노출
- 뒤로가기 클릭 시
- 검색 상태 초기화 여부는 상위에서 결정
스타일링
- Tailwind CSS
- 모바일 기준 상단 고정(Header Sticky)
- 좌측 뒤로가기 버튼 + 중앙 검색 입력창 구조
- Clear(X) 버튼은 입력창 내부 우측에 위치
- 터치 영역 최소 44px 확보
개요
-
역할
- Search 페이지에서 검색 대상 도메인(계정/ 게시물 & 해시태그)을 전환하는 탭 컴포넌트
- 책임
- 현재 선택된 검색 탭을 시각적으로 표시
- 사용자의 탭 전환 액션을 상위로 전달
-
설계 의도
- Search 결과의 데이터 구조와 UI 형태가 완전히 다른 두 영역을 명확히 분리하여 사용자의 검색 의도를 즉시 인지할 수 있도록 한다.
- 탭 전환은 “페이지 이동”이 아니라 검색 조건 변경이므로 단일
/search라우트 내에서 상태 변경만 발생하도록 설계한다.
탭 구성
-
계정
- nickname 기반 계정 검색
- 결과 UI : 프로필 리스트(List)
-
게시글/해시태그
- 게시글/해시태그 검색
- 입력값 규칙에 따라 자동 분기
- #포함 → 해시태그 검색
-
#미포함 → 게시글(콘텐츠) 검색
- 결과 UI: 게시물 그리드(Grid)
Props & Interface
interface SearchTabsProps {
value:'account' |'content';
onChange:(value:'account' |'content') =>void;
}-
value(required)- 현재 선택된 탭 값
'account' | 'content'
-
onChange(required)- 탭 클릭 시 호출되는 콜백
- 실제 검색 API 호출 및 상태 변경은 상위 컴포넌트에서 처리
내부 상태 & 이벤트
- 내부 상태 없음
- 모든 상태는 상위에서 관리
이벤트 흐름 예시
- 사용자가
계정탭 클릭-
onChange('account')호출
-
- 사용자가
게시글탭 클릭-
onChange('content')호출
-
- 상위 컴포넌트에서
- 검색 대상 도메인 변경
- TanStack Query queryKey 분기
- 결과 UI 전환
에러 처리 / 엣지 케이스
- 탭 전환 시
- 검색어는 유지
- 이전 탭의 검색 결과는 캐시로 보존
- 탭 변경 직후
-
keepPreviousData전략을 통해결과 깜빡임 없이 자연스럽게 전환
-
스타일링
- Tailwind CSS
- 상단 고정 영역 아래 배치
- 활성 탭 강조
- 비활성 탭은 시각적 대비 최소화
개요
-
역할
- 검색 피드에 사이즈 필터 적용 여부를 on/off 하는 토글 컴포넌트
-
설계 의도
- 사이즈 필터는 “검색 조건”이 아니라 탐색 보조 옵션이므로 검색어 입력 여부와 관계없이 독립적으로 제어 가능해야 한다.
- 필터의 활성/비활성 상태와 상세 설정 UI를 분리하여 토글은 빠른 on/off, 모달은 상세 설정으로 UX 부담을 줄인다.
- 동일한 토글 UI를 검색 결과 하단, 사이즈 필터 모달 내부 양쪽에서 재사용할 수 있도록 Stateless 컴포넌트로 설계한다.
UI 구성요소
- 체크박스 형태 토글
- 현재 선택된 사이즈 요약 텍스트
Props & Interface
interface SizeToggleProps {
enabled:boolean;
summaryText?:string;
onToggle:() =>void;
}-
enabled(required)- 사이즈 필터 활성 여부
-
summaryText(optional)- 현재 적용 중인 사이즈 조건 요약 문자열
- 예:
"160cm 60kg WOMEN"
-
onToggle(required)- 토글 상태 변경 시 호출
개요
- 역할
- 사이즈 필터의 상세 조건(키/몸무게/성별)을 설정하는 모달 컴포넌트
- 책임
- 사이즈 필터 상세 값 입력 UI 제공
- 입력된 조건을 상위 상태로 전달
- 설계 의도
- 검색 전 탐색 단계에서 사용자가 “내가 보고 싶은 체형 기준”으로 피드를 미리 좁혀볼 수 있도록 한다.
- 토글과 상세 설정을 한 화면에서 함께 제공해 사용자가 현재 필터 적용 상태를 명확히 인지하도록 한다.
UI구성요소
- 모달 헤더
- 타이틀 : 사이즈 필터
- 닫기 버튼
- 키(cm) 선택 필드
- 몸무게(kg) 선택 필드
- 성별 선택(남성/여성)
Props & Interface
interface SizeFilterModalProps {
isOpen:boolean;
enabled:boolean;
height?:number;
weight?:number;
gender?:'MALE' |'FEMALE';
onClose:() =>void;
onToggle:() =>void;
onChange:(value: {
height?:number;
weight?:number;
gender?:'MALE' |'FEMALE';
}) =>void;
}-
isOpen(required)- 모달 노출 여부
-
enabled(required)- 사이즈 필터 활성 여부
-
height,weight,gender(optional)- 현재 설정된 사이즈 조건
-
onClose(required)- 모달 닫기 이벤트
-
onToggle(required)- 모달 내부 토글 변경 이벤트
-
onChange(required)- 사이즈 조건 변경 시 호출
에러 처리 / 엣지 케이스
- 필터 off 상태에서 값만 변경
- 값은 저장되지만 적용은 안 됨
- 일부 값만 설정된 경우
- 가능한 조건만 반영
- 모달 닫힘
- 입력 값 유지 (임시 상태 유지)
개요
-
역할
- Search 페이지에서 계정(사용자) 검색 결과를 리스트 형태로 렌더링하는 컴포넌트
-
책임
- 계정 검색 결과를 프로필 리스트 UI로 표시
- 인피니티 스크롤 하단 도달 이벤트를 상위로 전달
- 로딩/ 결과없음/ 추가 로딩 상태를 명확히 구분하여 표현
-
설계 의도
- 계정 검색 결과는 게시글 검색과 달리 텍스트 중심, 단일 행 리스트 UI, 프로필 이동이 핵심 액션이므로 그리드 결과와 완전히 분리된 컴포넌트로 설계한다.
- 데이터 요청/페이지네이션 로직은 상위에서 관리하고 이 컴포넌트는 결과 렌더링과 사용자 인터랙션 전달에만 집중한다.
UI 구성요소
- 프로필 리스트 아이템
- 프로필 이미지(없을 경우 기본 아이콘)
- 닉네임
- 각 아이템은 클릭 가능
- 클릭 시 해당 사용자 프로필 페이지로 이동
Props & Interface
interface AccountResultListProps {
items:Array<{
memberId:number;
nickname:string;
profileImageUrl?:string |null;
}>;
isLoading:boolean;
isFetchingNextPage:boolean;
onReachEnd:() =>void;// Intersection Observer 콜백
}-
items(required)- 계정 검색 결과 리스트
- TanStack Query
useInfiniteQuery의 pages를 flatten한 결과
-
isLoading(required)- 최초 검색 요청 로딩 상태
-
isFetchingNextPage(required)- 다음 페이지를 불러오는 중인지 여부
-
onReachEnd(required)- 리스트 하단 도달 시 호출되는 콜백
- 상위에서
fetchNextPage()트리거
에러 처리 / 엣지 케이스
-
검색 결과 0건
-
SearchEmptyState렌더링 (상위에서 분기)
-
-
프로필 이미지 없음
- 기본 아바타 아이콘 표시
-
추가 페이지 없음 (
nextCursor === null)-
onReachEnd호출되어도 추가 요청 없음
-
-
네트워크 지연
-
isFetchingNextPage동안 하단 Skeleton 유지
-
스타일링
- Tailwind CSS
- 세로 스크롤 리스트 구조
- 각 리스트 아이템
- 터치 영역 충분히 확보
- 닉네임 길이에 따른 말줄임 처리
- 모바일 기준 스크롤 성능 고려
- 불필요한 리렌더링 최소화
개요
-
역할
- Search 페이지에서 게시글 / 해시태그 검색 결과를 그리드 형태로 렌더링하는 컴포넌트
- 책임
- 게시글 검색 결과를 이미지 중심 그리드 UI로 표시
- 인피니트 스크롤 하단 도달 이벤트를 상위로 전달
- 로딩 / 결과 없음 / 추가 로딩 상태를 명확히 표현
-
설계의도
- 게시글/해시태그 검색 결과는 이미지 중심, 빠른 스크롤 탐색이 핵심이므로 계정 검색과는 완전히 다른 UX를 가진다.
- 데이터 요청/페이지네이션 로직은 상위(SearchPage + TanStack Query)에서 관리하고 이 컴포넌트는 결과 시각화에만 집중한다.
UI 구성요소
- 게시글 그리드 카드
- 대표 이미지
- 그리드 아웃
- 3X3 직사각형
- 리스트 하단 추가 로딩 시 Skeleton Grid 노출
Props & Interface
interface PostGridResultProps {
items:Array<{
id:number;
imageUrls:string[];
createdAt:string;
}>;
isLoading:boolean;
isFetchingNextPage:boolean;
onReachEnd:() =>void;// Intersection Observer 콜백
}-
items(required)- 게시글 검색 결과 리스트
- TanStack Query
useInfiniteQuery결과 pages를 flatten한 데이터
-
isLoading(required)- 최초 검색 요청 로딩 상태
-
isFetchingNextPage(required)- 다음 페이지 요청 중 여부
-
onReachEnd(required)- 그리드 하단 도달 시 호출
- 상위에서
fetchNextPage()실행
에러 처리 / 엣지 케이스
-
검색 결과 0건
-
SearchEmptyState노출 (상위에서 분기)
-
-
추가 페이지 없음 (
nextCursor === null)- 더 이상 요청하지 않음
-
네트워크 지연
-
isFetchingNextPage동안 Skeleton Grid 유지
-
스타일링
- Tailwind CSS
- CSS Grid 기반 레이아웃
-
gridMode에 따른- 컬럼 수
- gap
- 카드 비율 조정
- 모바일 기준 스크롤 성능 최적화
- 이미지 lazy loading
- 불필요한 리렌더 방지
개요
-
역할
- 검색 요청은 성공했으나 조건에 맞는 검색 결과가 0건인 경우 사용자에게 안내 메시지를 제공하는 상태 컴포넌트
-
책임
- “검색 결과 없음” 상태를 명확히 시각화
- 사용자가 다음 행동을 취할 수 있도록 가이드 제공
- 단순 오류가 아닌 정상 결과 없음을 인지시키는 UX 제공
-
설계 의도
- 검색 결과가 없는 상황은 에러가 아니라 정상 상태이므로 토스트나 경고 UI가 아닌 화면 중심의 안내 메시지로 처리한다.
- 사용자가 검색이 실패했다고 느끼지 않도록 원인 가능성과 해결 방법을 함께 제시해 이탈 대신 재검색을 유도한다.
UI 구성요소
- 결과 없음 안내 문구
- “검색 닉네임 1” 일치하는 검색 결과가 없습니다.
- 가이드 리스트
- 단어의 철자가 정확한지 확인해 보세요.
- 한글을 영어로 혹은 영어를 한글로 입력했는지 확인해 보세요.
- 검색 옵션을 변경해서 다시 검색해 보세요.
Props & Interface
interface SearchEmptyStateProps {
query:string;
tab:'account' |'content';
}-
query(required)- 사용자가 입력한 검색어
- 안내 문구에 그대로 노출
-
tab(required)- 현재 검색 탭
- 계정 / 게시글·해시태그에 따라 문구 톤 조정 가능
에러 처리 / 엣지 케이스
- 검색어가 공백인 경우
- SearchEmptyState 대신 “검색어 입력 전 상태”를 상위에서 분기
- 탭 전환 시
- 새로운 검색 결과에 따라 자동으로 제거
개요
- 역할
- 검색 과정에서 네트워크 오류 또는 서버 에러가 발생했을 때 사용자에게 에러를 전달하는 상태 처리 컴포넌트
- 책임
- 검색 API 실패를 사용자에게 즉시 인지시킴
- 화면 전체를 막지 않고 토스트 메시지로만 안내
- 검색 입력/기존 결과 UI는 유지
- 설계 의도
- Search 도메인에서 에러는 대부분 일시적이며 검색 입력 자체는 계속 가능해야 한다.
- 따라서, 에러를 전체 화면 상태로 만들지 않고 비차단 토스트 메시지로만 처리한다.
- 결과적으로, 사용자는 검색을 중단하지 않고 입력 수정 또는 재시도를 자연스럽게 시도할 수 있다.
UI처리 방식
-
Toast 메시지
예) “검색 중 문제가 발생했습니다.” , “네트워크 상태를 확인해주세요.”
에러 처리 기준
-
처리 대상
- 네트워크 오류
- 5xx 서버 오류
- 알 수 없는 검색 실패
-
처리 제외
- 검색 결과 0건 (→ SearchEmptyState)
- 정책 오류(2자 미만, 100자 초과)
- → SearchInput helperText로 처리
-
Global State (Zustand Stores):
-
searchUIStore: 검색 조작 상태 및 UI 표현 모드 관리-
초기화 시점: 검색 페이지 이탈 시 또는
resetAll()호출 시 초기화. -
주요 액션:
-
setTab(tab): 검색 탭(계정/콘텐츠) 전환 -
setQuery(q): 입력값 정규화(Trim) 및contentMode파생 결정 -
setSizeFilterValue(v): 사이즈 필터 옵션(키, 몸무게, 성별) 업데이트 -
toggleGridMode(): 결과 리스트 레이아웃(Compact/Comfortable) 전환
-
-
Store 구조:
interface SearchUIState { tab: 'account' | 'content'; query: string; normalizedQuery: string; contentMode: 'post' | 'tag'; // '#' 시작 여부에 따른 자동 파생 gridMode: 'compact' | 'comfortable'; sizeFilter: SizeFilter; // Actions: setTab, setQuery, setSizeFilter, etc. }
-
-
설계 의도: 검색창, 필터 모달, 결과 리스트 등 파편화된 컴포넌트 간의 Prop Drilling을 방지하고, 탭 전환 시에도 입력값이 유지되는 일관된 UX를 제공합니다.
-
-
Local State (React
useState):- 단일 컴포넌트 내 UI 상태: 다른 컴포넌트와 공유할 필요가 없는 순수 표현 상태.
-
예시:
-
SearchInput: 포커스 여부, IME 조합 문자 처리, 헬퍼 텍스트 노출 상태. -
SizeFilterModal: 모달의 Open/Close 애니메이션 상태, 저장 버튼 클릭 전의 임시 입력값(취소 시 원상복구 용도).
-
-
Server Cache State (TanStack Query v5):
- 검색 결과 데이터 (SSOT): 무한 스크롤 및 서버 데이터 동기화 관리.
-
Query Key 설계: 데이터 혼선을 방지하기 위해
[tab, normalizedQuery, contentMode, sizeFilter]를 키로 조합합니다. (단, UI 레이아웃인gridMode는 키에서 제외하여 불필요한 리패치 방지) -
검색 최적화 전략:
- Enabled 조건: 쿼리 길이가 2자 미만이거나 100자 초과 시 API 호출 차단.
- Placeholder Data: 탭 전환 시 UI가 비어 보이지 않도록 이전 데이터를 유지하며 자연스러운 로딩 연결.
- Abort Controller: 입력 변경 시 이전 요청을 즉시 취소하여 응답 순서 뒤섞임 방지.
Search 도메인은 단일 검색 엔드포인트(/api/search)를 사용하며 query parameter 조합을 통해 검색 유형을 분기한다.
호출할 백엔드 API 목록
| Method | Endpoint | 설명 | Query Params | 호출 시점 |
|---|---|---|---|---|
| GET | /api/search | 계정 검색 | nickname, size, after, height?, weight?, gender? | 계정 탭 + 입력 디바운스 후 |
| GET | /api/search | 게시글 검색 | content, size, after, height?, weight?, gender? | 게시글 탭 + # 없는 입력 |
| GET | /api/search | 해시태그 검색 | tag, size, after, height?, weight?, gender? | 게시글 탭 + # 포함 입력 |
| GET | /api/search | 검색 전 탐색 (사이즈 필터만) | size, after, height, weight, gender | 검색어 없이 사이즈 필터 ON 시 |
API 호출 처리
- Axios & TanStack Query 활용 : 프로젝트 컨벤션에 따라 axios로 통신하며, 중복 호출 방지 및 자동 재요청을 처리한다.
-
검색 최적화
- 사용자의 타이핑마다 요청을 보내지 않도록 Debounce를 적용한다.
-
GET /api/search?nickname: 계정 검색- 호출 시점
- '계정' 탭 활성화 상태에서 검색어 입력 후 300ms 디바운스 완료 시
- 처리 방식 및 캐시 반영
-
useInfiniteQuery-
queryKey 예시:
['search','account', nickname, size, height, weight, gender
-
-
응답의
nextCursor를after파라미터로 넘겨 무한 스크롤 구현.
-
- 호출 시점
-
GET /api/search?content: 게시글 검색- 호출 시점
- 게시글/해시태그 탭에서 '#'이 포함되지 않은 검색어 입력 후 디바운스 완료 시
- 처리 방식 및 캐시 반영
-
['search', 'post', query, sizeFilter]키로 호출. - 사이즈 필터가 변경되면
invalidateQueries를 통해 목록을 새로고침함.
-
- 호출 시점
-
GET /api/search?tag: 해시태그 검색- 호출 시점
- 게시글/해시태그 탭에서 '#'이 포함된 검색어 입력 후 디바운스 완료 시
- 처리 방식 및 캐시 반영
-
['search', 'tag', query]키로 호출. - 입력값이 바뀔 때마다
staleTime: 0을 적용하여 실시간 검색 결과 반영.
-
- 호출 시점
-
GET /api/search?size?after?height?weight?gender: 사이즈 필터 탐색- 호출 시점
-
/search진입 후 - 검색어 입력 전
- 사이즈 필터 ON 상태
-
- 처리 방식 및 캐시 반영
- 사용자가 검색어를 입력하기 시작하면 해당 쿼리는 멈추고 게시글/계정 검색으로 전환.
- 호출 시점
-
Cursor Pagination: 페이지 번호 대신 커서(
after)로 다음 데이터를 요청하는 방식 - nextCursor: 다음 페이지를 요청하기 위한 커서 값 (없으면 더 이상 데이터 없음)
- Infinite Scroll: 스크롤 하단 도달 시 다음 페이지를 자동 로딩하는 UX
- Debounce: 입력이 멈춘 뒤 일정 시간 후에만 이벤트를 실행하는 기법(검색 API 과호출 방지)
- Account Search: nickname 기반 계정 검색, 결과는 프로필 리스트 형태
- Post Search: content 기반 게시글 검색, 결과는 게시물 그리드 형태
-
Tag Search:
#기반 태그 검색, 결과는 게시물 그리드 형태 - Race Condition : 이전 요청이 늦게 도착해 최신 결과를 덮어쓰는 비동기 충돌 상황
- Request Lock : 이미 요청 중일 때 동일 요청을 막아 중복 호출을 방지하는 제어 방식