---
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]]
