← Back to list

URL 쿼리로 필터 초기화 문제 해결하기

현재 진행중인 프로젝트에는 프로필, 이력서, 프로젝트 등등을 탐색할 수 있는 기능이 있다. 사용자는 프로필을 탐색하고싶을 때 포지션 / 기수 / 대학 / 학년 같은 조건으로 필터링해서 보고 싶은 프로필만 모아서 탐색할 수 있다.

ukongee · 2025-09-03 07:42 · 100 claps · 5.0 min read
#filtering #nextjs
Open on Medium ↗
Wiki topics: ML · Machine Learning 🌐 · Web Development

URL 쿼리로 필터 초기화 문제 해결하기

현재 진행중인 프로젝트에는 프로필, 이력서, 프로젝트 등등을 탐색할 수 있는 기능이 있다. 사용자는 프로필을 탐색하고싶을 때 포지션 / 기수 / 대학 / 학년 같은 조건으로 필터링해서 보고 싶은 프로필만 모아서 탐색할 수 있다.

근데 큰 문제가 하나 있었다. 프론트엔드 포지션을 필터링해서 프로필을 클릭 → 뒤로가기 했더니… 필터가 전부 초기화되는 것이다.

문제 상황

사용자는 보통 이렇게 행동한다.

  1. 프로필 목록 → 포지션=FRONTEND 필터 적용
  2. 특정 유저 프로필 클릭
  3. 뒤로가기 눌러서 다시 목록으로
  4. 당연히 프론트엔드만 남아있길 기대하지만, 실제로는 전체 목록으로 초기화돼 버렸다.

탐색 흐름이 끊기는 UX였다.

다른 서비스들은 어떻게 할까?

비슷한 문제를 검색하다 보니, 무신사, 마켓컬리, 네이버 쇼핑 같은 서비스는 이미 이 문제를 해결해두었다.

방법은 바로 아래처럼 필터 상태를 URL 쿼리에 싣는 것이다.

/profiles?position=FRONTEND&year=2&sortBy=이름순

위는 마켓컬리의 예시인데 page(현재 몇 페이지인지)값과 선택한 필터의 값등을 url에 저장하고 있다.

이렇게 하면 필터값 유지와 뒤로가기, 앞으로가기 뿐만 아니라 다른 사람에게 공유할 때도 필터값을 유지할 수 있다!

이렇게 해두면 장점이 많다.

  • 뒤로가기/앞으로가기 자연스럽게 유지
  • 새로고침해도 그대로 유지
  • URL 공유 시 동일한 화면 재현 가능

결국 “URL이 곧 상태”가 된다.

커스텀 훅 만들기

이 방식을 적용하기 위해 **useUrlQueryFilters** 라는 훅을 만들었다. 전체 코드는 길지만, 핵심 아이디어만 정리하면 이렇다.

문제 해결 핵심 아이디어

  • 필터 → URL 동기화: 상태를 URLSearchParams로 변환
  • URL → 필터 파싱: 페이지 진입/뒤로가기 시 URL에서 복원
  • 기본값은 URL에 저장하지 않음: 주소를 깔끔하게 유지

1) URL → Filters 파싱

function parseFromParams(params: URLSearchParams) {
  const toArr = (k: string) => params.getAll(k).filter(Boolean)
const filters = {
    position: toArr('position'),
    year: toArr('year'),
    university: toArr('university'),
    grade: toArr('grade'),
    sortBy: toArr('sortBy'),
  }
  // sortBy 기본값 보정
  if (filters.sortBy.length === 0) filters.sortBy = ['기수순']
  return filters
}

2) Filters → URL 머지

function mergeParams(base: URLSearchParams, filters: Filters) {
  const next = new URLSearchParams(base.toString())
  ;['position','year','university','grade','sortBy'].forEach(k => next.delete(k))
const addAll = (key: string, values: string[]) => {
    [...new Set(values)].sort().forEach(v => next.append(key, v))
  }
  if (filters.position.length) addAll('position', filters.position)
  if (filters.year.length) addAll('year', filters.year)
  if (filters.university.length) addAll('university', filters.university)
  if (filters.grade.length) addAll('grade', filters.grade)
  // 기본값('기수순')이면 URL에 안 씀
  const sort = filters.sortBy[0]
  if (sort && sort !== '기수순') next.append('sortBy', sort)
  return next
}

3) 훅 사용법

const { filters, set, remove } = useUrlQueryFilters()
// 드롭다운 선택
<Dropdown
  title="포지션"
  options={['FRONTEND','BACKEND']}
  selectedOptions={filters.position}
  setSelectedOptions={(v) => set('position', v)}
/>
// 필터 제거
{filters.position.map((p) => (
  <FilterBtn key={p} title={p} onClick={() => remove('position', p)} />
))}

적용 결과

  • 뒤로가기/앞으로가기: 히스토리에 쿼리가 남아 UX 자연스러움
  • 새로고침: 필터 초기화 안 됨
  • 공유: URL만 주면 상대방도 같은 조건으로 접근 가능

배운 점

  • 상태를 URL에 담는 건 단순히 편의를 위한 게 아니라, 사용자 기대와 흐름을 지키는 것이었다.
  • 기본값은 URL에서 제거해 주소를 깔끔하게 유지하는 것도 중요했다.
  • replace를 기본으로 해서 필터 변경이 히스토리를 더럽히지 않도록 했다.

결국, 우리 프로젝트도 무신사/마켓컬리 같은 방식으로 동작하게 되었다. 필터링 UX가 훨씬 자연스러워졌고, 탐색의 연속성을 지켜낼 수 있었다! 필터링을 사용하는 다른 페이지에도 훅을 재사용해서 적용해주었다.


메타데이터
post_id
233a9c75d3fc
slug
url-쿼리로-필터-초기화-문제-해결하기-233a9c75d3fc
url
https://medium.com/@qkrb8019/url-%EC%BF%BC%EB%A6%AC%EB%A1%9C-%ED%95%84%ED%84%B0-%EC%B4%88%EA%B8%B0%ED%99%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-233a9c75d3fc
canonical_url
https://medium.com/@qkrb8019/url-%EC%BF%BC%EB%A6%AC%EB%A1%9C-%ED%95%84%ED%84%B0-%EC%B4%88%EA%B8%B0%ED%99%94-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-233a9c75d3fc
author_url
https://medium.com/@qkrb8019
status
ok
fetched_at
2026-06-26 03:39:16