최근 토이 프로젝트를 진행했고 리팩토링까지 완료했다. 기간이 되게 타이트한 프로젝트였어서 마지막 feature였던 브라우저의 히스토리 관리를 위한 쿼리 파라미터를 추가하지 못한 채로 종료되어서 현재 따로 진행중이다. 이번에 사내 직원들이 사용할 수 있는 업무 관리용 캘린더를 구현했다보니 추가해야할 파라미터가 많았다. 쿼리 파라미터 기능을 프로젝트에 추가했던 적이 여러번 있었는데 이번에는 특히 많은 사용자가 공유하는 페이지였기에 설계해야할 파라미터의 복잡도가 증가했다. 설계를 마치고 기능을 구현하기 시작했는데 캘린더를 동적으로 렌더링하기 위한 여러 클라이언트 상태를 앞서 설계한 쿼리 파라미터 상태로 대체할 수 있을 거 같다는 생각이 들었고 대체되어 불필요해진 클라이언트 상태를 걷어내다보니 정리해둘 가치가 있다 생각됐다.
우선 쿼리 파라미터 상태에서 "상태" 라는 워딩이 조금 애매하다고 느낀다. 관점에 따라 상태가 될 수도 있고 상태가 아닐 수도 있다고 생각하는데, 고민 끝에 상태라고 볼 수 있다고 생각되어 현재 글에서는 쿼리 파라미터 상태라고 부르겠다. 대부분의 웹앱이 SPA로 개발되는 지금 개인적으로 파라미터는 여러 방면에서 중요한 역할을 하고 있다고 생각한다. SEO, URL 공유, 브라우저 히스토리 제공 등 다양한 방면에서 최적화와 더 나은 경험을 제공한다. 아래 표를 확인하면 간단하게 비교가 가능하다.
| 특징 | 클라이언트 상태 (useState/Redux) | 쿼리 파라미터 |
|---|---|---|
| 상태 저장 위치 | 메모리 내부 (사용자 세션 동안 유지) | URL (브라우저 히스토리와 함께 저장) |
| 새로고침 | 상태 초기화 (유지 안 됨) | 유지 |
| 뒤로 가기/앞으로 가기 | 히스토리와 연동되지 않음 | 히스토리 연동 가능 |
| 공유 가능성 | URL을 통한 공유 불가 | URL로 상태를 공유 가능 |
| 복잡성 | 단순 | 약간의 설계 필요 |
프로젝트 관점에서 본다면 반드시 초기 설계 단계에서 사용될 쿼리 파라미터를 미리 설계하면 좋을 거 같다. 키가 중복될 수도 있고, 추후 불필요해질 클라이언트 상태로 코드를 작성하지 않을 수 있을 거 같다. 더 나아가면 API 설계 시에도 사용할 수 있다.
이제 사용자 관점에서 본다면 우선 UX 개선이 주된 목적인 거 같다. 브라우저 히스토리를 통해 사용자는 자연스레 뒤로 가기, 앞으로 가기, 새로고침을 사용할 것이고 사용자가 기대하는, 예상하는 화면이 보이길 바란다. 또한 자신이 현재 보고 있는 화면을 다른 사용자에게 정확히 공유할 수 있다. 그리고 SEO인데 잘못 사용하면 검색 엔진의 크롤링 효율성을 떨어뜨리고 중복 컨텐츠를 노출시키는 문제가 발생할 수 있다. SEO와 쿼리 파라미터에 대해 더 얘기해보겠다.
쿼리 파라미터를 사용하면 웹앱 내의 여러 파라미터로 이루어진 다양한 페이지를 개별로 인덱싱할 수 있다. 더 나아가서 UTM 파라미터 등을 활용해서 특정 파라미터가 포함된 주소로 접속한 사용자 수 등을 분석할 수 있다. 하지만 문제점도 있다. 앞서 얘기했던 듯이 다양한 페이지를 개별로 인덱싱하는데 잘못 사용하는 경우 같은 페이지를 반복적으로 크롤링하여 중복 컨텐츠로 취급되어 검색 엔진의 크롤링 효율이 떨어질 수 있고 효율이 떨어지면 검색 엔진이 크롤링하고 노출되어야할 중요한 페이지가 크롤링되지 못할 수 있다. 또한 너무 많은 쿼리 파라미터는 URL 가독성을 떨어트려 중요한 키워드가 누락될 수 있다. 따라서 SEO 관점에서 쿼리 파라미터를 도입하는 것이라면 무분별한 쿼리 파라미터 사용은 자제해야한다. 캐싱 관련 문제도 존재하는데 이는 로딩 속도에 영향을 미치고 로딩 속도가 느려지면 SEO 점수가 낮아진다. 따라서 중요도가 높고 필수적인 파라미터는 반드시 경로 파라미터로 사용해야한다.
쿼리 파라미터는 URL 상태 공유가 중요한 경우, 많은 동적 상태를 다루는 경우, SEO 최적화를 위해, 엔드포인트를 서버와 동기화할 필요가 있을 때 사용하는 것이 좋다. 다양한 필터링, 페이지네이션, 동적 뷰 등이 있다.
인증(로그인)된 현재 사용자의 세션 내에서만 의미는 상태 있을 때, URL에 상태를 노출하지 않아야하는 정보일 때, 매우 간단한 UI 상태를 다룰 때 사용하는 것이 좋다. 입력 폼 데이터, 로딩 상태, 버튼 활성화 여부 등이 있다.
검색 키워드와 필터링 옵션은 URL에 포함하여 쿼리 파라미터 상태로 관리하면 된다.
?query=laptop&sort=lowPrice
그리드 보기 혹은 리스트 보기를 전환하는 UI 상태는 클라이언트 상태로 관리하면 된다.
현재 페이지 번호는 클라이언트 상태가 아닌 쿼리 파라미터 상태로 관리해야한다.
상품 정보 탭과 상품 리뷰 탭이 있다고 가정했을 때 사용자가 선택한 탭을 클라이언트 상태가 아닌 쿼리 파라미터로 관리해야한다.
초기 설계 단계에서 UX와 SEO를 고려하여 쿼리 파라미터로 사용할 상태를 결정한다. 이는 불필요한 클라이언트 상태를 방지한다.
검색어, 페이지, 필터링, 정렬 옵션 등 모두 쿼리 파라미터로 관리하고 있다.
https://www.google.com/search?q=react+query&start=20
필터링(장르, 언어)과 컨텐츠 상세 페이지를 쿼리 파라미터로 관리하고 있다.
https://www.netflix.com/browse?genre=action
쿠팡의 경우 되게 많은 파라미터를 사용중인데 접속시간, 데이터 분석을 위한 접속 경로, 리다이렉트 경로 등 다양한 쿼리 파라미터를 적극적으로 활용중이다.
https://www.coupang.com/?&itime=20241221024857&wRef=www.google.com&redirect=landing
쿼리 파라미터 상태와 클라이언트 상태를 선택함에 있어 서비스의 특성, 상태의 복잡성, UI/UX 요구사항에 따라 달라지지만 모두 사용해야하는 경우도 있다. URL 파라미터와 클라이언트 상태 간 동기화가 필요한 경우인데, UI 업데이트가 빠르게 이루어져야 하거나 상태가 너무 복잡해서 파라미터의 상태를 직접 참조하기 어렵거나 비효율적인 경우가 대표적이다. 퍼포먼스 측면에서 보면 UI 렌더링 시 파라미터의 값을 가져오는 것보다 클라이언트 상태를 이용하는 것이 더 좋다. 따라서 쿼리 파라미터 상태의 이점과 클라이언트 상태의 이점을 모두 사용하기 위해 상태를 중복해서 관리하는 것이다. 다만 중복되는 리소스가 투자되는 것임은 확실하기에 설계를 잘해서 쿼리 파라미터 상태와 클라이언트 상태를 최대한 나누도록 하자!