--- id: web-history-api-routing title: Browser History API — push / replace / popstate category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [web, history, routing, browser, vibe-coding] tech_stack: { language: "JavaScript / browser", applicable_to: ["Web"] } applied_in: [] aliases: [pushState, replaceState, popstate, back/forward] --- # Browser History API > SPA 라우팅의 기반. **pushState / replaceState** 로 URL 변경 + 뒤로가기 지원. 라우터 (RR, Next) 가 추상화하지만 알아야 모달 / 검색 필터 / scroll restoration 등 처리 가능. ## 📖 핵심 개념 - pushState: 새 history entry. 뒤로가기 가능. - replaceState: 현재 entry 교체. 뒤로가기 안 변함. - popstate: 사용자 뒤로/앞으로. listener. - scroll restoration: 자동 복원. ## 💻 코드 패턴 ### URL 변경 ```ts // 새 entry history.pushState({ page: 1 }, '', '/users/1'); // 현재 교체 (검색 필터 typing 시 디폴트) history.replaceState({ filter: 'name' }, '', '?filter=name'); // 뒤로가기 감지 window.addEventListener('popstate', e => { const state = e.state; // pushState 의 첫 인자 render(state); }); ``` ### 모달 — URL 동기 ```ts function openModal(id: string) { history.pushState({ modal: id }, '', `?modal=${id}`); setOpenModal(id); } // popstate (back) 시 모달 닫기 window.addEventListener('popstate', () => { setOpenModal(null); }); // 모달 닫기 버튼 → 뒤로 function closeModal() { history.back(); // popstate 트리거 → 위 핸들러 } ``` ### 검색 필터 — replaceState (back 무한 폭주 방지) ```ts function setSearch(q: string) { const url = new URL(location.href); url.searchParams.set('q', q); history.replaceState(null, '', url.toString()); // 매 keystroke push 면 history 폭증 → replace } ``` ### Scroll restoration ```ts // 디폴트: 'auto' — 브라우저가 위치 복원. // SPA 에서 비동기 데이터 로드 후 위치 안 맞으면 'manual' 후 직접 history.scrollRestoration = 'manual'; window.addEventListener('popstate', () => { // 데이터 로드 후 window.scrollTo(0, savedScrollPositions[location.pathname] ?? 0); }); ``` ### 라우터 안에서 (React Router) ```tsx const navigate = useNavigate(); navigate('/users/1'); // pushState navigate('/users/1', { replace: true }); // replaceState navigate(-1); // back ``` ## 🤔 의사결정 기준 | 액션 | 메서드 | |---|---| | 새 화면 (back 가능) | pushState | | URL 만 갱신 (필터, 정렬) | replaceState | | 모달 / drawer | pushState — back 으로 닫힘 (UX) | | 인증 후 redirect | replaceState — back 으로 안 돌아감 | | 외부 링크 | location.href = ... 또는 anchor | ## ❌ 안티패턴 - **검색 typing 매번 pushState**: 100번 typing → 100 history. replaceState. - **state 없이 pushState**: popstate 시 state null. 정보 손실. 항상 state object. - **state 에 큰 객체**: 직렬화 비싸고 5MB 한계. ID 만, 본문은 별도 store. - **popstate 에서 fetch 호출 + race**: 빠른 back/forward 시 stale. AbortController. - **scroll 위치 안 저장**: 뒤로 시 top 으로. 직접 saveScrollPosition. - **history.back() 호출 후 해당 entry 가 없음**: 사이트 떠남. canGoBack 체크. ## 🤖 LLM 활용 힌트 - "새 화면 = push, URL 만 갱신 = replace" 명확. - 모달 = pushState + popstate close. ## 🔗 관련 문서 - [[React_Router_Patterns]] - [[React_Refs_Patterns]]