브라우저의 History API(pushState, replaceState)에서 사용하는 state 객체는 브라우저 엔진마다 제한 방식과 용량이 다르지만, 일반적으로 생각보다 매우 보수적인 제한을 가집니다.
질문하신 내용에 대해 브라우저별 특성과 성능 영향을 정리해 드립니다.
1. 브라우저별 state 객체 용량 제한
대부분의 브라우저는 state 객체를 직렬화(Serialization)하여 저장하며, 이 직렬화된 데이터의 크기를 기준으로 제한을 둡니다.
| 브라우저 | 대략적인 제한 용량 | 초과 시 동작 |
|---|---|---|
| Chrome / Edge | 약 2MB | DataCloneError 예외 발생 (저장 실패) |
| Safari | 약 640KB | 예외 발생 혹은 말씀하신 대로 페이지가 새로고침되거나 비정상 동작 가능성 있음 |
| Firefox | 약 640KB ~ 2MB | DataCloneError 예외 발생 |
- Safari의 특성: Safari는 메모리 관리에 매우 엄격합니다. 특히 iOS Safari의 경우 가용 메모리가 부족해지면 History Stack 자체를 비우거나, 한도를 넘어서는 순간 탭이 새로고침되는 현상이 잦습니다. 이는 일종의 안전장치(Crash 방지)로 작동하기 때문입니다.
2. Chrome vs Safari: 초과 시 반응 차이
- Chrome(V8 엔진): 데이터 크기가 한도를 넘으면 콘솔에 에러를 띄우고 명령 자체를 거부합니다. 즉, 상태가 업데이트되지 않을 뿐 페이지 자체가 죽거나 새로고침되지는 않습니다.
- Safari(WebKit 엔진): 한계치에 도달하면
SecurityError를 뱉거나, 메모리 압박이 심할 경우 브라우저가 해당 탭의 프로세스를 다시 시작(Reload)해버리는 경향이 있습니다. 사용자가 느끼기엔 "용량을 넘기니 새로고침된다"고 보일 수 있는 대목입니다.
3. 과도한 용량 사용 시 발생할 수 있는 문제
단순히 "에러가 나느냐"를 떠나, 용량이 큰 객체를 state에 담으면 다음과 같은 성능적 결함이 발생할 가능성이 매우 높습니다.
🛑 메인 스레드 차단 (Jank 발생)
History API는 state 객체를 저장할 때 **구조화된 복제 알고리즘(Structured Clone Algorithm)**을 사용하여 직렬화합니다.
- 객체가 크고 복잡할수록 직렬화/역직렬화 과정이 CPU를 점유합니다.
pushState는 동기적으로 작동하므로, 용량이 크면 사용자가 클릭을 했을 때 화면이 순간적으로 멈추는(Freezing) 현상이 발생합니다.
🛑 메모리 누수 및 오동작
브라우저는 뒤로 가기/앞으로 가기를 위해 History Stack에 있는 state 객체들을 메모리에 유지하려 합니다.
- 사용자가 사이트를 오래 서핑하여 히스토리 스택이 쌓인 상태에서 각 스택마다 큰 용량의
state가 들어있다면, 탭 전체의 메모리 사용량이 급증합니다. - 이 경우 브라우저가 강제로 탭을 닫거나, 예상치 못한 시점에
popstate이벤트가 발생하지 않는 등의 오동작이 일어날 수 있습니다.
💡 권장되는 해결책
History state는 **“페이지를 복원하기 위한 최소한의 ID나 상태 값”**만 저장하는 용도로 설계되었습니다. 대용량 데이터가 필요하다면 다음과 같은 대안을 섞어서 사용하는 것이 좋습니다.
- State에는 키 값만:
state: { id: 12345 }처럼 최소한의 정보만 담습니다. - SessionStorage 활용: 브라우저 탭이 열려 있는 동안 유지되어야 하는 큰 데이터는
sessionStorage에 저장하고, Historystate에는 해당 데이터의 Key만 매칭해 둡니다. - IndexedDB: 오프라인 지원이나 정말 큰 데이터(이미지 바이너리, 대용량 JSON 등)를 다뤄야 한다면
IndexedDB를 사용하는 것이 성능상 가장 유리합니다.
요약하자면, History state에 수백 KB 이상의 데이터를 넣는 것은 성능과 안정성 측면에서 위험합니다. 가능한 10~20KB 이내로 가볍게 유지하는 것을 추천합니다.
브라우저 히스토리 관리에 대해 더 궁금한 구현 방식이나 특정 시나리오가 있으신가요?