SPA 라우팅의 기반. pushState / replaceState 로 URL 변경 + 뒤로가기 지원. 라우터 (RR, Next) 가 추상화하지만 알아야 모달 / 검색 필터 / scroll restoration 등 처리 가능.
📖 핵심 개념
pushState: 새 history entry. 뒤로가기 가능.
replaceState: 현재 entry 교체. 뒤로가기 안 변함.
popstate: 사용자 뒤로/앞으로. listener.
scroll restoration: 자동 복원.
💻 코드 패턴
URL 변경
// 새 entry
history.pushState({page: 1},'','/users/1');// 현재 교체 (검색 필터 typing 시 디폴트)
history.replaceState({filter:'name'},'','?filter=name');// 뒤로가기 감지
window.addEventListener('popstate',e=>{conststate=e.state;// pushState 의 첫 인자
render(state);});
모달 — URL 동기
functionopenModal(id: string){history.pushState({modal: id},'',`?modal=${id}`);setOpenModal(id);}// popstate (back) 시 모달 닫기
window.addEventListener('popstate',()=>{setOpenModal(null);});// 모달 닫기 버튼 → 뒤로
functioncloseModal() {history.back();// popstate 트리거 → 위 핸들러
}
검색 필터 — replaceState (back 무한 폭주 방지)
functionsetSearch(q: string){consturl=newURL(location.href);url.searchParams.set('q',q);history.replaceState(null,'',url.toString());// 매 keystroke push 면 history 폭증 → replace
}
Scroll restoration
// 디폴트: 'auto' — 브라우저가 위치 복원.
// SPA 에서 비동기 데이터 로드 후 위치 안 맞으면 'manual' 후 직접
history.scrollRestoration='manual';window.addEventListener('popstate',()=>{// 데이터 로드 후
window.scrollTo(0,savedScrollPositions[location.pathname]??0);});
라우터 안에서 (React Router)
constnavigate=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 체크.