Step 3 Search 도메인 테크스펙 문서 - 100-hours-a-week/16-team-katopia-fe GitHub Wiki

Search 도메인 테크 스펙 (Search)

배경 (Background)

프로젝트 목표

Search 도메인은 사용자가 원하는 사람(계정) 또는 원하는 콘텐츠(게시글/해시태그) 를 빠르게 찾게 만드는 “탐색 진입점”이다. FITCHECK 서비스에서 검색 경험이 느리거나 불명확하면, 사용자는 피드 탐색(Home)에서만 머물며 서비스 확장성(팔로우/커뮤니티/투표 전환)이 떨어진다.

또한, 사용자는 명확한 검색어 없이도 내 체형과 유사한 코디를 먼저 보고싶다는 니즈를 자주 가진다.

따라서 본 도메인의 목표는 다음과 같다.

  • 사이즈 필터 기반 사전 탐색 지원
    • 검색어 입력 이전 단계에서도, 키/몸무게/성별 사이즈 필터만으로 게시글 피드를 탐색할 수 있도록 한다.
  • 체형 기반 콘텐츠 소비 경험 강화
    • Profile 도메인에서 수집한 신체 정보(키, 몸무게, 성별)가 Search 도메인에서 실질적인 탐색 조건으로 활용되도록 설계한다.
  • 탭 기반 검색 UX 일관성 확보
    • “계정 / 게시글(콘텐츠) & 해시태그” 검색을 한 화면에서 명확히 분리하여, 사용자가 검색 의도를 즉시 이해하도록 한다.
  • 입력 방식만으로 의도 판별 가능한 검색 규칙 제공
    • 게시글/해시태그 검색은 # 유무로 자동 분기해, 사용자의 “검색 방식 학습 비용”을 줄인다.
  • 대량 결과를 안정적으로 제공
    • 커서 기반 인피니트 스크롤을 지원하며, 스크롤 중 중복 호출/경합을 제어해 성능/비용을 안정화한다.

핵심 결과 (KRs) — Search 도메인 관점

  • KR1: 검색 결과 최초 노출까지(입력 → 결과 렌더) 체감 지연 최소화 (debounce + 캐시 + skeleton)
  • KR2: 탭 전환 시 UX 끊김 없이 결과 유지/복원 (keepPreviousData/캐시 키 전략)
  • KR3: 인피니트 스크롤 중 중복 요청/경합 0건 (요청 취소/락/nextCursor 기반)
  • KR4: 검색 규칙(#로 해시태그 분기)을 사용자가 즉시 이해 (placeholder/가이드/빈 상태 문구)
  • KR5: 검색어 없이 사이즈 필터만 적용한 탐색에서 유의미한 결과 노출 성공률 확보

문제 정의 (Problems)

  • 검색어 없는 초기 상태의 탐색 공백
    • 검색어 입력 전 상태에서는 의미 있는 콘텐츠가 노출되지 않아, 사용자가 무엇을 해야 할지 망설이게 된다.
    • 특히 패션/코디 도메인에서는 검색어를 모르겠는 상태가 빈번하게 발생한다.
  • 체형 기반 탐색 요구를 충족하지 못함.
    • 사용자는 단순 키워드보다 나와 비슷한 체형의 게시글을 우선적으로 보고 싶어하지만, 검색어 중심 설계만으로는 이러한 탐색 니즈를 충족하기 어렵다.
  • 검색 의도 혼재
    • “계정 찾기”와 “게시글 찾기”의 결과 UI가 완전히 다름(프로필 리스트 vs 그리드).
    • 한 화면에서 섞이면 사용자는 무엇이 나오는지 예측하기 어렵다.
  • 무한 스크롤의 안정성
    • cursor 기반 API는 적절히 쓰면 효율적이지만, 프론트에서 중복 호출/요청 경합을 제어하지 않으면 같은 데이터가 반복되거나 스크롤이 끊길 수 있다.
  • 입력 변화가 잦음
    • 검색창 입력은 “매 타이핑”마다 변한다 → 매번 API 호출하면 과부하/지연/UX 악화.
  • 유효성/정책 에러가 사용자에게 불친절할 가능성
    • size 숫자 형식 오류(COMMON-E-003), 검색어 길이 제한(COMMON-E-004) 등의 서버 정책을 UI에서 명확히 안내하지 않으면 사용자가 계속 실패한다.

목표가 아닌 것 (Non-goals)

  • 검색 랭킹/개인화(추천/가중치)
    • 이번 범위에서는 “입력값 기반 단순 검색 + cursor pagination”에 집중한다.
  • 오타 교정 / 자동 완성 / 연관 검색어
    • 사용성은 좋지만 구현/정책 범위가 커진다. 후속 과제로 분리한다.
  • 검색 히스토리/최근 검색어 동기화(서버 저장)
    • 이번 범위는 클라이언트 로컬 저장 정도만 고려하고, 서버 히스토리는 제외한다.
  • 복합 필터(성별/스타일/키/몸무게 등) 고급 검색
    • Search UI 복잡도가 급증하므로 제외한다.

설계 및 기술 자료 (Architecture and Technical Documentation)

아키텍처 개요 (Architecture Overview)

기술 스택 (전 도메인 공통 규칙 그대로 적용)

  • 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 모드의 스타일 분기 단순화한다.


주요 페이지 / 컴포넌트 구조

페이지 (Pages)

/search : Search 메인 페이지 (탭 + 검색창 + 결과)

주요 기능

  • 검색창 입력
  • 검색 탭 전환
    • 계정
    • 게시글 탭 (입력값에 따라 게시글/해시태그 자동 분기)
  • 사이즈 필터 토글 on/off
  • 사이즈 필터 모달 값 입력(키, 몸무게, 성별)
  • 결과 렌더
    • 계정: 프로필 리스트 형태
    • 게시글/해시태그: 게시물 그리드 형태
  • 인피니트 스크롤
    • size, after(nextCursor) 기반으로 다음 페이지 요청

사용 컴포넌트

  • SearchInput
  • SearchTabs
  • SizeToggle
  • SizeFilterModal
  • AccountResultList
  • PostGridResult
  • SearchEmptyState
  • SearchErrorState

데이터 로딩 시점

  • 페이지 최초 진입 시
    • 즉시 검색 API 호출하지 않음.
    • “검색어 입력 대기 상태” 빈 검색어 UI 노출
  • 사용자가 검색어 입력 완료 후
    • debounce 처리(입력 중 실시간 호출 방지)
    • 입력값 유효성 검증 통과 시 API 호출
  • 탭 전환 시
    • 동일 검색어 기준으로 해당 탭의 검색 API 호출
    • 이전 탭 결과는 캐시에서 복원
  • 스크롤 하단 도달 시
    • nextCursor 가 존재하는 경우에만 다음 페이지 요청

라우팅

  • /search 단일 라우트 유지
  • 검색어, 탭, 사이즈 필터는 페이지 이동 없이 UI 상태로 관리

2) 주요 컴포넌트 (Components)

SearchInput

개요(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)
    • 뒤로가기 버튼 클릭 시 호출

내부 상태 & 이벤트

  • 내부 상태 없음
  • 모든 상태는 상위에서 관리

이벤트 흐름 예시

  1. 검색어 입력 → onChange(value)
  2. X 버튼 클릭 → onClear()
  3. 뒤로가기 버튼 클릭 → onBack()

에러 처리 / 엣지 케이스

  • 검색어 길이 정책 위반 시
    • SearchHeader는 직접 검증하지 않음
    • isInvalid + helperText를 받아 UI로만 표현
  • 검색어가 비어있는 경우
    • Clear(X) 버튼 미노출
  • 뒤로가기 클릭 시
    • 검색 상태 초기화 여부는 상위에서 결정

스타일링

  • Tailwind CSS
  • 모바일 기준 상단 고정(Header Sticky)
  • 좌측 뒤로가기 버튼 + 중앙 검색 입력창 구조
  • Clear(X) 버튼은 입력창 내부 우측에 위치
  • 터치 영역 최소 44px 확보

SearchTabs

개요

  • 역할
    • 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 호출 및 상태 변경은 상위 컴포넌트에서 처리

내부 상태 & 이벤트

  • 내부 상태 없음
  • 모든 상태는 상위에서 관리

이벤트 흐름 예시

  1. 사용자가 계정 탭 클릭
    • onChange('account') 호출
  2. 사용자가 게시글 탭 클릭
    • onChange('content') 호출
  3. 상위 컴포넌트에서
    • 검색 대상 도메인 변경
    • TanStack Query queryKey 분기
    • 결과 UI 전환

에러 처리 / 엣지 케이스

  • 탭 전환 시
    • 검색어는 유지
    • 이전 탭의 검색 결과는 캐시로 보존
  • 탭 변경 직후
    • keepPreviousData 전략을 통해

      결과 깜빡임 없이 자연스럽게 전환

스타일링

  • Tailwind CSS
  • 상단 고정 영역 아래 배치
  • 활성 탭 강조
  • 비활성 탭은 시각적 대비 최소화

SizeToggle

개요

  • 역할
    • 검색 피드에 사이즈 필터 적용 여부를 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)
    • 토글 상태 변경 시 호출

SizeFilterModal

개요

  • 역할
    • 사이즈 필터의 상세 조건(키/몸무게/성별)을 설정하는 모달 컴포넌트
  • 책임
    • 사이즈 필터 상세 값 입력 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 상태에서 값만 변경
    • 값은 저장되지만 적용은 안 됨
  • 일부 값만 설정된 경우
    • 가능한 조건만 반영
  • 모달 닫힘
    • 입력 값 유지 (임시 상태 유지)

AccountResultList

개요

  • 역할
    • 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
  • 세로 스크롤 리스트 구조
  • 각 리스트 아이템
    • 터치 영역 충분히 확보
    • 닉네임 길이에 따른 말줄임 처리
  • 모바일 기준 스크롤 성능 고려
    • 불필요한 리렌더링 최소화

PostGridResult

개요

  • 역할
    • 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
    • 불필요한 리렌더 방지

SearchEmptyState

개요

  • 역할
    • 검색 요청은 성공했으나 조건에 맞는 검색 결과가 0건인 경우 사용자에게 안내 메시지를 제공하는 상태 컴포넌트
  • 책임
    • “검색 결과 없음” 상태를 명확히 시각화
    • 사용자가 다음 행동을 취할 수 있도록 가이드 제공
    • 단순 오류가 아닌 정상 결과 없음을 인지시키는 UX 제공
  • 설계 의도
    • 검색 결과가 없는 상황은 에러가 아니라 정상 상태이므로 토스트나 경고 UI가 아닌 화면 중심의 안내 메시지로 처리한다.
    • 사용자가 검색이 실패했다고 느끼지 않도록 원인 가능성과 해결 방법을 함께 제시해 이탈 대신 재검색을 유도한다.

UI 구성요소

  • 결과 없음 안내 문구
    • “검색 닉네임 1” 일치하는 검색 결과가 없습니다.
  • 가이드 리스트
    • 단어의 철자가 정확한지 확인해 보세요.
    • 한글을 영어로 혹은 영어를 한글로 입력했는지 확인해 보세요.
    • 검색 옵션을 변경해서 다시 검색해 보세요.

Props & Interface

interface SearchEmptyStateProps {
	query:string;
	tab:'account' |'content';
}
  • query (required)
    • 사용자가 입력한 검색어
    • 안내 문구에 그대로 노출
  • tab (required)
    • 현재 검색 탭
    • 계정 / 게시글·해시태그에 따라 문구 톤 조정 가능

에러 처리 / 엣지 케이스

  • 검색어가 공백인 경우
    • SearchEmptyState 대신 “검색어 입력 전 상태”를 상위에서 분기
  • 탭 전환 시
    • 새로운 검색 결과에 따라 자동으로 제거

SearchErrorState

개요

  • 역할
    • 검색 과정에서 네트워크 오류 또는 서버 에러가 발생했을 때 사용자에게 에러를 전달하는 상태 처리 컴포넌트
  • 책임
    • 검색 API 실패를 사용자에게 즉시 인지시킴
    • 화면 전체를 막지 않고 토스트 메시지로만 안내
    • 검색 입력/기존 결과 UI는 유지
  • 설계 의도
    • Search 도메인에서 에러는 대부분 일시적이며 검색 입력 자체는 계속 가능해야 한다.
    • 따라서, 에러를 전체 화면 상태로 만들지 않고 비차단 토스트 메시지로만 처리한다.
    • 결과적으로, 사용자는 검색을 중단하지 않고 입력 수정 또는 재시도를 자연스럽게 시도할 수 있다.

UI처리 방식

  • Toast 메시지

    예) “검색 중 문제가 발생했습니다.” , “네트워크 상태를 확인해주세요.”

에러 처리 기준

  • 처리 대상
    • 네트워크 오류
    • 5xx 서버 오류
    • 알 수 없는 검색 실패
  • 처리 제외
    • 검색 결과 0건 (→ SearchEmptyState)
    • 정책 오류(2자 미만, 100자 초과)
      • → SearchInput helperText로 처리

상태 관리 전략 (State Management Strategy)

  • 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: 입력 변경 시 이전 요청을 즉시 취소하여 응답 순서 뒤섞임 방지.

API 연동 (API Integration)

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
          
      • 응답의 nextCursorafter 파라미터로 넘겨 무한 스크롤 구현.

  • 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 상태
    • 처리 방식 및 캐시 반영
      • 사용자가 검색어를 입력하기 시작하면 해당 쿼리는 멈추고 게시글/계정 검색으로 전환.

용어 정의 (Glossary)

  • Cursor Pagination: 페이지 번호 대신 커서(after)로 다음 데이터를 요청하는 방식
  • nextCursor: 다음 페이지를 요청하기 위한 커서 값 (없으면 더 이상 데이터 없음)
  • Infinite Scroll: 스크롤 하단 도달 시 다음 페이지를 자동 로딩하는 UX
  • Debounce: 입력이 멈춘 뒤 일정 시간 후에만 이벤트를 실행하는 기법(검색 API 과호출 방지)
  • Account Search: nickname 기반 계정 검색, 결과는 프로필 리스트 형태
  • Post Search: content 기반 게시글 검색, 결과는 게시물 그리드 형태
  • Tag Search: # 기반 태그 검색, 결과는 게시물 그리드 형태
  • Race Condition : 이전 요청이 늦게 도착해 최신 결과를 덮어쓰는 비동기 충돌 상황
  • Request Lock : 이미 요청 중일 때 동일 요청을 막아 중복 호출을 방지하는 제어 방식
⚠️ **GitHub.com Fallback** ⚠️