몇 주 전에 URL 디자인의 숨겨진 비용을 발행할 때 SQL 구문 강조 표시를 추가해야 했습니다. PrismJS 웹사이트로 향했는데 플러그인으로 추가해야 하는지 기억하려고 했습니다. 다운로드 페이지의 옵션이 너무 많아서 코드로 돌아갔습니다. PrismJS 파일을 확인했을 때 파일 상단에 URL이 포함된 주석을 찾았습니다:
완전히 잊고 있었습니다. URL을 클릭했을 때 모든 체크박스, 드롭다운, 옵션이 제 정확한 구성과 일치하도록 미리 선택된 PrismJS 다운로드 페이지였습니다. 테마가 선택되었습니다. 언어가 선택되었습니다. 플러그인이 활성화되었습니다. 모든 것이 그 단 하나의 URL에서 완벽하게 재구성되었습니다.
한때 알고 있던 것이 갑자기 새로운 의미로 다시 클릭되는 순간이었습니다. 여기 URL은 단순히 페이지를 가리키는 것 이상을 하고 있었습니다. 상태를 저장하고, 의도를 인코딩하고, 제 전체 설정을 공유 가능하고 복구 가능하게 만들고 있었습니다. 데이터베이스 없음. 쿠키 없음. localStorage 없음. 단지 URL일 뿐입니다.
이것이 저를 생각하게 했습니다: 우리 프론트엔드 엔지니어들이 URL을 상태 관리 도구로 간과하는 경우가 얼마나 많을까요? 우리는 전역 저장소, 컨텍스트, 캐시 같은 모든 종류의 추상화에 손을 뻗으면서 웹의 가장 우아하고 오래된 기능 중 하나인 겸손한 URL을 무시합니다.
이전 기사에서 저는 나쁜 URL 디자인의 숨겨진 비용에 대해 썼습니다. 오늘은 그 관점을 뒤집고 좋은 URL 디자인의 엄청난 가치에 대해 이야기하고 싶습니다. 특히 URL을 현대 웹 애플리케이션의 일급 상태 컨테이너로 취급하는 방법에 대해 말입니다.
URL의 간과된 힘
Scott Hanselman은 유명하게 "URL은 UI입니다"라고 말했고 그는 완전히 맞습니다. URL은 단순히 브라우저가 리소스를 가져오는 데 사용하는 기술적 주소가 아닙니다. 그들은 인터페이스입니다. 그들은 사용자 경험의 일부입니다.
하지만 URL은 UI 이상입니다. 그들은 상태 컨테이너입니다. URL을 만들 때마다 어떤 정보를 보존할지, 무엇을 공유 가능하게 만들지, 무엇을 북마크 가능하게 만들지에 대한 결정을 내리고 있습니다.
URL이 무료로 제공하는 것들을 생각해 봅시다:
공유 가능성: 누군가에게 링크를 보내면 그들은 정확히 당신이 보는 것을 봅니다
북마크 가능성: URL을 저장하면 시간의 한 순간을 저장한 것입니다
브라우저 히스토리: 뒤로 가기 버튼이 그냥 작동합니다
딥 링킹: 특정 애플리케이션 상태로 직접 이동합니다
URL은 웹 애플리케이션을 탄력적이고 예측 가능하게 만듭니다. 그들은 웹의 원래 상태 관리 솔루션이며 1991년 이후로 안정적으로 작동해 왔습니다. 문제는 URL이 상태를 저장할 수 있는지가 아닙니다. 우리가 그들의 전체 잠재력을 사용하고 있는지입니다.
예제를 살펴보기 전에 URL이 상태를 어떻게 인코딩하는지 분석해 봅시다. 여기 전형적인 상태 저장 URL이 있습니다:
많은 해 동안 이것들이 URL의 유일한 구성 요소로 간주되었습니다. 이것은 텍스트 조각의 도입으로 변경되었습니다. 이것은 페이지 내의 특정 텍스트 부분으로 직접 링크할 수 있는 기능입니다. 제 기사 더 똑똑한 'Ctrl+F': 웹 페이지 콘텐츠로 직접 링크하기에서 더 자세히 읽을 수 있습니다.
URL의 다른 부분은 다른 유형의 상태를 인코딩합니다:
경로 세그먼트 (/path/to/myfile.html). 계층적 리소스 네비게이션에 가장 잘 사용됩니다:
플래그나 토글의 경우 부울을 명시적으로 전달하거나 키의 존재를 참으로 사용하는 것이 일반적입니다. 이것은 URL을 더 짧게 유지하고 기능 토글을 쉽게 만듭니다.
?debug=true&analytics=false
?mobile (존재 = true)
배열 (괄호 표기법)
?tags[]=frontend&tags[]=react&tags[]=hooks
또 다른 오래된 패턴은 괄호 표기법으로, 쿼리 매개변수에서 배열을 나타냅니다. 이것은 PHP 같은 초기 웹 프레임워크에서 시작되었으며 매개변수 이름에 []를 추가하면 여러 값이 함께 그룹화되어야 함을 나타냅니다.
?tags[]=frontend&tags[]=react&tags[]=hooks
?ids[0]=42&ids[1]=73
많은 현대 프레임워크와 파서 (Node의 qs 라이브러리나 Express 미들웨어 같은)는 여전히 이 패턴을 자동으로 인식합니다. 그러나 URL 사양에서 공식적으로 표준화되지 않았으므로 서버 또는 클라이언트 구현에 따라 동작이 다를 수 있습니다. 제 웹사이트의 구문 강조 표시를 어떻게 깨뜨리는지 주목하세요.
공유 가능한 디자인 링크 이전에 큰 파일에서 업데이트된 화면이나 구성 요소를 찾는 것은 번거로웠습니다. 누군가가 말 그대로 당신에게 보여줘야 했고, 레이어 전체를 스크롤하고 확대/축소했습니다. 오늘날 Figma 링크는 캔버스 위치, 줌 레벨, 선택된 요소 같은 모든 컨텍스트를 전달합니다. 말 그대로 작업 공간에 바로 떨어뜨리는 데 필요한 모든 것입니다.
popstate 이벤트는 사용자가 브라우저의 뒤로 또는 앞으로 버튼으로 네비게이션할 때 발생합니다. 이것은 URL과 일치하도록 UI를 복원할 수 있게 해주며, 이는 앱의 상태와 히스토리를 동기화 상태로 유지하는 데 필수적입니다. 보통 프레임워크의 라우터가 이것을 처리하지만 내부적으로 어떻게 작동하는지 아는 것이 좋습니다.
React를 사용한 구현
React Router와 Next.js는 이것을 훨씬 더 깔끔하게 만드는 훅을 제공합니다:
pushState와 replaceState 중에서 선택할 때 브라우저 히스토리가 어떻게 동작하기를 원하는지 생각하세요. pushState는 새 히스토리 항목을 만들며, 이는 필터 변경, 페이지 매김, 새 보기로의 네비게이션 같은 뚜렷한 네비게이션 작업에 적합합니다 — 사용자는 뒤로 버튼을 사용하여 이전 상태로 돌아갈 수 있습니다. 반면 replaceState는 새 항목을 추가하지 않고 현재 항목을 업데이트하며, 검색 입력 중이나 사소한 UI 조정 같은 개선 사항에 이상적입니다. 여기서 모든 키 입력으로 히스토리를 넘치고 싶지 않습니다.
URL을 계약으로
신중하게 설계되면 URL은 단순한 상태 컨테이너 이상이 됩니다. 그들은 애플리케이션과 소비자 간의 계약이 됩니다. 좋은 URL은 인간, 개발자, 기계 모두에게 기대치를 정의합니다.
명확한 경계
잘 구조화된 URL은 공개와 비공개, 클라이언트와 서버, 공유 가능과 세션별 사이의 선을 그립니다. 상태가 어디에 있는지, 어떻게 동작해야 하는지를 명확히 합니다. 개발자는 무엇을 유지하기에 안전한지 알고, 사용자는 무엇을 북마크할 수 있는지 알고, 기계는 무엇이 인덱싱할 가치가 있는지 압니다.
분석 도구는 추가 계측 없이 이 흐름을 추적할 수 있습니다. 모든 URL 매개변수는 분석할 수 있는 차원이 됩니다.
버전 관리 및 진화
URL은 API 버전, 기능 플래그, 실험을 전달할 수 있습니다:
?v=2 // API 버전
?beta=true // 베타 기능
?experiment=new-ui // A/B 테스트 변형
이것은 점진적 롤아웃과 하위 호환성을 훨씬 더 관리하기 쉽게 만듭니다.
피해야 할 안티패턴
좋은 의도에도 불구하고 URL 상태를 오용하기는 쉽습니다. 다음은 일반적인 함정입니다:
"메모리 전용" SPA
고전적인 단일 페이지 앱 실수:
// 사용자가 새로 고침을 누르면 모든 것을 잃음
const [filters, setFilters] = useState({});
앱이 새로 고침 시 상태를 잊으면 웹의 기본 기능 중 하나를 깨뜨리고 있습니다. 사용자는 URL이 컨텍스트를 보존할 것으로 예상합니다. 몇 년 전 Reddit 사용자가 전자상거래 사이트에 대해 화낸 바이럴 비디오를 기억합니다: 뒤로 가기를 누를 때마다 모든 필터가 사라졌습니다. 그녀의 좌절감이 완벽하게 요약했습니다. 사용자가 컨텍스트를 잃으면 인내심을 잃습니다.
URL의 민감한 데이터
이것은 명백해 보이지만 반복할 가치가 있습니다:
// 절대 이렇게 하지 마세요
?password=secret123
URL은 모든 곳에 기록됩니다: 브라우저 히스토리, 서버 로그, 분석, 레퍼러 헤더. 그들을 공개로 취급하세요.
거대한 JSON 객체를 base64로 인코딩해야 한다면 URL이 그 상태를 위한 올바른 장소가 아닐 가능성이 높습니다.
URL 길이 제한
브라우저와 서버는 URL 길이에 실질적인 제한을 부과합니다 (보통 2,000~8,000자 사이) 하지만 현실은 더 미묘합니다. 이 상세한 Stack Overflow 답변에서 설명하듯이 제한은 브라우저 동작, 서버 구성, CDN, 심지어 검색 엔진 제약의 혼합에서 나옵니다. 제한에 부딪히면 접근 방식을 다시 생각해야 한다는 신호입니다.
뒤로 가기 버튼 깨뜨리기
// 잘못된 상태 교체
history.replaceState({}, '', newUrl); // pushState가 필요했을 때 사용됨
브라우저 히스토리를 존중하세요. 사용자 작업이 뒤로 가기 버튼을 통해 "실행 취소"할 수 있어야 한다면 pushState를 사용하세요. 개선 사항이면 replaceState를 사용하세요.
마무리 생각
그 PrismJS URL은 저에게 중요한 것을 상기시켰습니다: 좋은 URL은 단순히 콘텐츠를 가리키지 않습니다. 그들은 사용자와 애플리케이션 간의 대화를 설명합니다. 그들은 의도를 포착하고, 컨텍스트를 보존하고, 다른 상태 관리 솔루션이 일치할 수 없는 방식으로 공유를 가능하게 합니다.
우리는 Redux, MobX, Zustand, Recoil 같은 점점 더 정교한 상태 관리 라이브러리를 구축했습니다. 그들 모두 자신의 자리가 있지만 때때로 최고의 솔루션은 항상 그곳에 있던 것입니다.
이전 기사에서 저는 나쁜 URL 디자인의 숨겨진 비용에 대해 썼습니다. 오늘 우리는 반대편을 탐색했습니다: 좋은 URL 디자인의 엄청난 가치입니다. URL은 단순한 주소가 아닙니다. 그들은 상태 컨테이너, 사용자 인터페이스, 계약이 모두 하나로 합쳐진 것입니다.
앱이 새로 고침 시 상태를 잊으면 웹의 가장 오래되고 가장 우아한 기능 중 하나를 놓치고 있는 것입니다.