1. 리팩토링의 필요성
기존 검색 기능은 다음과 같은 UX를 보였습니다.
- 검색 후 결과 페이지 이동 -> 정상
- 상세 페이지 -> 뒤로가기 -> 검색 결과 유지 ✅
- 하지만 새로고침 시
-> 다시 검색이 실행되면서 마치 다시 검색하는 것 같은 UI가 먼저 보이는 문제 발생
사용자 입장에서 생각해본다면,
"방금 본 검색 결과인데 왜 다시 검색하지??"
분명 이런 생각이 들 것 같았습니다. 이는 분명 사용감의 저하로 이어질 것 같았고 리팩토링이 필요하다고 생각했습니다.
2. 추정되는 문제점
처음에는 페이지 구조 문제를 의심했습니다.
현재 검색 결과 페이지가 별도로 존재하는 것은 아니기에 검색 결과를 별도 페이지로 분리해야 하는가?
메인 페이지에서 상태로 관리하는 구조가 문제인가?
문제가 없는 뒤로가기는 문제가 없기 때문에 뒤로가기와 비교하며 문제의 원인을 파악했습니다.
| 상황 | 캐시 상태 | 결과 |
| 뒤로가기 | 메모리 캐시 있음 | 즉시 결과가 보임 |
| 새로고침 | 메모리 캐시 초기화 | 다시 검색하는 것처럼 보임 |
검색 결과가 상태(state)에만 존재하고 있었기 때문에 새로고침 시 모두 초기화 되고 있었던 것입니다.
3. 캐싱 전략 도입
이 문제를 해결하기 위해 접근 방향을 바꿨습니다.
기본 방식
검색 결과 = React state
변경 방식
검색 결과 = 서버 상태 (TanStack Query)
캐시를 localStorage에 저장(persist 활용)
즉, 구조를 다음 처럼 변경하도록 계획했습니다.
URL(q) -> Query Key -> 캐시 -> UI
4. 검색 로직 리팩토링
기존 구조
useEffect(() => {
fetch(...)
setResults(...)
}, [q])
문제점
- 매번 새 요청 발생
- 캐시 없음
- 상태 기반 -> 휘발성
변경 구조
const {
data,
isLoading,
isFetching,
} = useQuery({
queryKey: ['recipes', q],
queryFn: () => fetchSearch(q),
enabled: !!q,
});
핵심 변화
- queryKey 기반 캐싱
- 동일 검색어 재요청 방지
- 상태 → 캐시 중심 구조
persist 적용
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
해당 처리를 추가함으로써 localStorage에 캐시를 저장할 수 있게 되었습니다.
5. 리팩토링 이후 새로고침의 흐름
이전 흐름
새로고침
→ state 초기화
→ API 요청
→ 로딩 UI
→ 결과 렌더
리팩토링 후 흐름
새로고침
→ localStorage 캐시 복원
→ 즉시 결과 렌더
→ (백그라운드 refetch)
6. 해결
이 리팩토링을 통해서 3가지를 개선할 수 있었습니다.
- 뒤로가기 UX 유지
- 새로고침 UX 개선
- 불필요한 API 요청 감소
결과적으로 '사용자가 이미 본 데이터는 다시 기다리지 않게 한다' 는 원래의 목적을 달성할 수 있었습니다.
7. 리팩토링을 통해 배운점
1. 상태(state) vs 서버 상태(server state)
state → 휘발성
server state → 캐싱 가능
검색 결과 처럼 다시 앞으로 갔다가 뒤로 가는 등 자주 새로 로딩할 필요가 있는 경우에는 상태보다는 서버 상태로 관리하는 것이 더 적절합니다.
2. TanStack Query의 역할
- 캐싱
- 중복 요청 방지
- background refetch
- 상태 관리 단순화
3. persist의 중요성
- 새로고침 UX 개선
- 캐시 생명주기 확장
- 앱처럼 동작하는 경험 제공
마지막으로 UX는 '데이터 흐름'에서 나온다는 것을 깨달았습니다. 이번 문제는 UI 문제가 아니라 데이터가 어디에 저장되느냐의 문제였습니다. 앞으로는 사용자 입장에서 UX 개선을 위해 데이터 처리에 신경을 써야함을 느꼈습니다.
'개인 프로젝트 MenuMate' 카테고리의 다른 글
| MenuMate 배포 전략 고민 사항 정리 (0) | 2026.04.19 |
|---|---|
| MenuMate 검색 로직 리팩토링 이후 Recoverable Error 해결 (1) | 2026.04.15 |
| TypeScript가 css import를 타입으로 인식하지 못하는 문제 해결 과정 (0) | 2026.04.14 |
| Next.js 검색 API 500 에러 디버깅 및 문제 해결 과정 정리 (1) | 2026.04.09 |
| Next.js next/image 외부 이미지 오류 문제 해결 경험 정리 (0) | 2026.04.02 |