URL은 상태 컨테이너다
(alfy.blog)- URL 구조가 단순한 주소를 넘어 애플리케이션 상태를 저장·복원하는 수단으로 작동함
- PrismJS 다운로드 페이지처럼, URL 하나로 테마·언어·플러그인 설정이 완전히 재현되는 사례 제시
- 경로, 쿼리 파라미터, 프래그먼트 등 각 구성요소가 계층적 탐색·필터링·클라이언트 내비게이션 등 다양한 상태를 표현
- 검색 필터, 페이지네이션, 보기 모드, 날짜 범위 등은 URL에 포함하기 적합하며, 민감정보나 일시적 UI 상태는 부적합
- 잘 설계된 URL은 공유성·예측 가능성·캐싱 효율성을 높이며, 웹 애플리케이션의 신뢰성과 사용자 경험을 강화함
URL의 잠재력
- URL은 단순한 리소스 주소가 아니라 사용자 인터페이스(UI) 이자 상태 컨테이너로 기능
- 공유, 북마크, 브라우저 히스토리, 딥링크 등에서 상태를 자동으로 보존
- 1991년부터 웹의 기본 상태 관리 메커니즘으로 작동
- URL의 각 구성요소는 다른 종류의 상태를 표현
-
경로(Path) : 계층적 리소스 탐색 (
/users/123/posts) -
쿼리(Query) : 필터·옵션·설정 (
?theme=dark&lang=en) -
프래그먼트(Fragment) : 문서 내 위치나 SPA 라우팅 (
#features,#/dashboard)
-
경로(Path) : 계층적 리소스 탐색 (
- Text Fragments 기능은 페이지 내 특정 텍스트로 직접 연결 가능
쿼리 파라미터 패턴
-
구분자(delimiter) 로 여러 값을 하나의 키에 담는 방식 (
?tags=frontend,react,hooks) -
중첩 데이터를 JSON 또는 Base64로 직렬화 (
?config=eyJyaWNrIjoicm9sbCJ9==) -
불리언 플래그는 키 존재 여부로 표현 (
?mobile) -
배열 표기법(bracket notation) 은
tags[]=frontend&tags[]=react형태로 다중 값 표현- Node의
qs나 Express 미들웨어 등에서 자동 인식되지만 표준화는 되어 있지 않음
- Node의
- 핵심은 일관성 유지
URL을 통한 상태 표현 사례
- PrismJS: URL 해시로 테마·언어·플러그인 설정 전체를 저장
-
GitHub:
#L108-L136으로 특정 코드 라인 범위 강조 - Google Maps: 좌표·줌 레벨·지도 유형을 URL에 포함
- Figma: 캔버스 위치·줌·선택 요소 등 작업 맥락을 URL로 공유
- 전자상거래 사이트: 필터·정렬·가격 범위를 URL에 포함해 검색 상태 복원
프런트엔드 엔지니어링 패턴
- URL에 포함하기 적합한 상태
- 검색어, 필터, 페이지·정렬, 보기 모드, 날짜 범위, 활성 탭, UI 구성, 기능 플래그
- URL에 부적합한 상태
- 비밀번호·토큰 등 민감정보, 임시 UI 상태, 미저장 입력, 대용량 데이터, 고빈도 상태
- 판단 기준: 다른 사용자가 같은 URL을 클릭했을 때 동일한 상태를 봐야 하는가
JavaScript 구현
-
URLSearchParamsAPI로 쿼리 파라미터 읽기·쓰기 가능 -
pushState로 새 히스토리 항목 추가,replaceState로 현재 항목 갱신 -
popstate이벤트로 브라우저 뒤로가기 시 UI 복원
React 구현
- React Router의
useSearchParams훅으로 URL 상태를 간결하게 관리- 파라미터 읽기·갱신 시 자동으로 URL과 UI 동기화
URL 상태 관리 모범 사례
-
기본값은 URL에 포함하지 않기 (
?theme=dark만 유지, 기본값은 코드에서 처리) -
디바운싱으로 입력 중 URL 과도한 갱신 방지 (
lodash.debounce활용) -
pushState vs replaceState
-
pushState: 필터 변경·페이지 이동 등 되돌릴 수 있는 상태 -
replaceState: 검색 입력 등 세밀한 수정
-
URL을 계약(Contract)으로 보기
- 잘 설계된 URL은 애플리케이션과 사용자 간의 명시적 계약 역할
- 공개/비공개, 클라이언트/서버, 공유/세션 상태의 경계를 명확히 함
-
가독성 높은 URL은 의도를 설명하고, 사람과 기계 모두 이해 가능
-
example.com/products/laptop?color=silver&sort=price형태가 의미 전달에 유리
-
-
캐싱 효율성 향상
- 동일 URL은 동일 리소스로 간주되어 캐시 적중률 상승
- 쿼리 파라미터로 캐시 변형 제어 가능
-
버전 관리와 실험
-
?v=2,?beta=true,?experiment=new-ui등으로 API 버전·A/B 테스트 구분
-
피해야 할 안티패턴
- SPA에서 메모리 내 상태만 유지해 새로고침 시 상태 손실
-
민감정보를 URL에 포함 (
?password=secret123) -
불명확한 파라미터명 (
?foo=true&bar=2대신?mobile=true&page=2) - 복잡한 JSON을 Base64로 인코딩해 과도하게 긴 URL 생성
- URL 길이 제한 초과(브라우저·서버·CDN 제약 존재)
-
뒤로가기 버튼 무력화 (
replaceState남용 시 발생)
결론
- 좋은 URL은 콘텐츠를 가리키는 것 이상으로, 사용자와 애플리케이션 간의 대화를 표현
- URL은 의도·맥락·공유 가능성을 담는 가장 오래되고 우아한 상태 관리 수단
- Redux·MobX·Zustand·Recoil 등 복잡한 상태 관리 도구가 존재하지만,
URL이라는 기본 기능을 잊지 않는 것이 진정한 웹의 강점임 - 새로고침 시 상태를 잃는 앱은 웹의 본질적 특성을 놓치고 있음
탭 절전 기능을 애용하는데 URL 고정해서 한 덩어리로 움직이는 웹앱은 절전 들어가면 정보가 날아갑니다.
그런데 또 그런 웹페이지들이 하나같이 무거워서 절전을 안 할 수도 없어요.
Hacker News 의견
-
코드 리뷰 시 가능한 한 많은 상태(state) 를 URL에 저장하려고 함
새로고침 후 완전히 다른 위치로 이동하거나, 공유한 URL이 엉뚱한 화면을 보여주는 건 사용자 입장에서 모욕적임
이런 방식은 개발 속도를 늦추지만, 팀 내에서 UX 인식이 높아지고 뷰에 얼마나 많은 상태를 담는지 명확히 알 수 있음
URL이 일종의 공개 API 가 되어 제약이 생긴다는 우려도 있지만, 대부분의 URL은 단기적으로만 사용되므로 큰 문제는 아니라고 생각함
필요하다면 로드 시 이전 URL을 새 URL로 마이그레이션 하는 코드로 해결 가능함- 이 접근법이 마음에 들지만, 브라우저 히스토리 자동완성 때문에 원치 않는 상태가 불려오는 경우가 있음
경로(path) 대신 쿼리 파라미터를 쓰면 좀 더 낫다고 생각함 - 내가 쓰는 업무용 웹앱은 자체 “뒤로가기” 버튼을 만들어서 브라우저의 뒤로가기가 완전히 깨져 있음
사용자 입장에서는 “뒤로가기”라는 단어가 브라우저 버튼과 연결되어 있어서 혼란스러움
새로고침으로 상태가 초기화되는 건 덜 짜증남. “새로고침 = 처음부터 다시”라는 인식이 있기 때문임 - 서버 렌더링 페이지라면 새로고침 시 스크롤 위치가 자동으로 복원됨
JS로 모든 걸 처리하면 이런 기본 기능들이 미묘하게 깨짐 - URL 설계는 UX 디자인의 일부라고 생각함
하지만 지금까지 30명 넘는 UX 디자이너와 일했어도 URL에 대한 가이드를 받은 적이 없음 - 웹이 발전하면서 새로고침의 의미가 상황마다 달라졌음
특히 모바일에서는 페이지를 초기 상태로 되돌리기 어려워서 새로고침이 가장 빠른 해결책이 됨
무한 스크롤이나 복잡한 필터 UI에서는 URL에 상태가 많을수록 초기화가 더 귀찮아짐
이미 UX에 불만이 있는 상황에서 URL까지 정리해야 한다면 그건 사용자에게 이중 스트레스임
- 이 접근법이 마음에 들지만, 브라우저 히스토리 자동완성 때문에 원치 않는 상태가 불려오는 경우가 있음
-
디지털 리터러시가 높은 사람들조차 URL과 DNS 이해도가 낮다고 느낌
피싱 위험을 줄이고, URL 파라미터(?t=_,utm_)의 의미를 이해하며, 공유 전 개인정보를 제거할 수 있어야 함
HTTPS 자물쇠가 ‘신뢰’를 의미하지 않는다는 점도 알아야 함- 하지만 브라우저가 기본적으로 URL을 숨기거나 축약하고, 기업들이 QR 코드나 검색어만 홍보하는 환경이라 교육이 어려움
-
URL을 상태 컨테이너로 쓰면 내부 구조가 노출되고, 버전 관리가 필요해짐
브라우저 간 호환성이나 인증 흐름에서도 문제가 생길 수 있음
그래도 명령줄 인자처럼 가능한 많은 상태를 URL에 노출하려고 함
다만 이는 의도적 트레이드오프로, 무지나 경험 부족 때문은 아님 -
오래된 라이브러리지만 여전히 유용한 Rison을 추천함
JSON을 URL에 깔끔하게 저장할 수 있고, Elastic의 Kibana에서도 사용됨
예시: http://example.com/service?query=q:'*',start:10,count:10- 이런 걸 찾고 있었음! 예전엔 직접 임시로 만들었는데, 이건 훨씬 표준적이고 정돈된 방식처럼 보임
-
시스템이 발전하면 상태 구조도 바뀌므로, URL에 상태를 넣으면 진화가 제약됨
URL은 기본적으로 영구 문자열이기 때문임
대신 URL을 일종의 프로토콜로 보고, 상태를 인코딩·디코딩하는 방식이 적절하다고 생각함
단순한 페이지라면 전체 상태를 URL에 담는 것도 가능함- 유지 기간이 긴 콘텐츠(예: 블로그 포스트)는 URL 상태 보존이 유용함
하지만 피드처럼 “새로고침 시 최신 상태로 돌아가야 하는가?” 같은 사용자 기대치에 따라 달라짐 - 버전 관리를 도입하면 이런 문제를 완화할 수 있음
- 유지 기간이 긴 콘텐츠(예: 블로그 포스트)는 URL 상태 보존이 유용함
-
URL 길이 제한은 브라우저·서버·CDN·검색엔진 설정에 따라 다르지만, 보통 2000자 이하임
이 제한 안에서 얼마나 많은 상태를 담을 수 있을지, 혹은 다른 접근법이 필요할지 고민됨- 도메인을 제외한 각 문자는 66가지(대소문자, 숫자, 특수문자
- . _ ~)를 쓸 수 있으므로, 정보 밀도는 꽤 높음
- 도메인을 제외한 각 문자는 66가지(대소문자, 숫자, 특수문자
-
draw.io는 전체 상태를 URL에 저장해 공유할 수 있음
다이어그램 데이터가 Base64로 인코딩되어 링크 하나로 완전한 복원이 가능함
다만 이것이 ‘state container’ 정의에 부합하는지는 확신이 없음 -
나는 셀프호스팅 앱에서 hash routing (#/dashboard) 을 사용함
서버 측 URL 재작성(.htaccess 등)이 필요 없어서, 완벽하진 않아도 배포 환경 제약을 줄일 수 있음 -
최신 Microsoft Teams는 모든 화면이 하나의 URL로 처리되어 북마크 불가임
특정 팀이나 채널을 바로 열 수 없어서 매우 불편함 -
HATEOAS는 이름이 별로라서 주목받지 못하지만, 결국 웹의 기본 개념임
- 사용자 입장에서는 링크를 따라가고 폼을 제출하는 게 곧 HATEOAS임
하지만 서버·클라이언트를 모두 제어하는 환경에서는 추가 복잡성만 생김
특히 클라이언트가 여전히 엔드포인트 구조를 알아야 한다면 URL을 불투명하게 만들 뿐임 - 이 주제는 사실 HATEOAS와 직접 관련이 없음. 둘 다 URL을 쓰긴 하지만, HATEOAS는 상태 저장이 아니라 탐색 구조에 관한 것임
- 농담이지만, 결국 HATEOAS는 “시리얼화된 포맷(cerealization) ”이라는 말이 어울림
- 사용자 입장에서는 링크를 따라가고 폼을 제출하는 게 곧 HATEOAS임