
브라우저 저장소 완벽 가이드: Cookie, LocalStorage, IndexedDB
프론트엔드 개발자가 알아야 할 4가지 저장소의 차이점과 보안 이슈(XSS, CSRF), 그리고 언제 무엇을 써야 하는지에 대한 명확한 기준.

프론트엔드 개발자가 알아야 할 4가지 저장소의 차이점과 보안 이슈(XSS, CSRF), 그리고 언제 무엇을 써야 하는지에 대한 명확한 기준.
매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

클래스 이름 짓기 지치셨나요? HTML 안에 CSS를 직접 쓰는 기괴한 방식이 왜 전 세계 프론트엔드 표준이 되었는지 파헤쳐봤습니다.

HTTP는 무전기(오버) 방식이지만, 웹소켓은 전화기(여보세요)입니다. 채팅과 주식 차트가 실시간으로 움직이는 기술적 비밀.

시각 장애인, 마우스가 고장 난 사용자, 그리고 미래의 나를 위한 배려. `alt` 태그 하나가 만드는 큰 차이.

웹은 기본적으로 Stateless(무상태)입니다. 서버는 요청이 끝나면 당신이 누구였는지 잊어버립니다. 그래서 "로그인 유지"나 "장바구니" 같은 기능을 구현하려면 어딘가에 데이터를 저장해야 합니다. 옛날엔 쿠키(Cookie) 하나뿐이었지만, 지금은 브라우저 안에 거대한 NoSQL DB(IndexedDB)까지 들어있습니다.
선택지가 많아진 만큼 개발자의 고민도 깊어집니다. "JWT 토큰은 어디에 저장해야 안전할까요?" "장바구니 데이터는 브라우저를 껐다 켜도 남아있어야 하는데, 뭘 써야 하죠?"
이 질문에 대한 답을 찾아봅시다.
각 저장소의 특징과 용도를 명확히 구분할 줄 알아야 합니다.
HttpOnly, Secure 플래그로 보호 가능.HTML5에서 등장한, 쿠키의 단점을 보완하기 위한 저장소입니다.
많은 개발자가 오해하는 부분입니다.
window.open()으로 자식 탭을 열면, 부모 탭의 SessionStorage 데이터가 복사(Cloning)되어 전달됩니다. (연동되는 게 아니라 초기값만 복사됨).LocalStorage의 값이 변경되면, 같은 도메인의 다른 탭들에 storage 이벤트가 발생합니다.
이를 이용해 "A탭에서 로그아웃하면 B탭도 같이 로그아웃"되거나, "설정 변경 시 모든 탭에 즉시 반영"하는 기능을 구현할 수 있습니다.
// 다른 탭에서 localStorage를 변경했을 때 실행됨
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
applyTheme(event.newValue); // 즉시 테마 변경 적용
}
if (event.key === 'token' && event.newValue === null) {
alert('로그아웃되었습니다.');
window.location.reload();
}
});
이 이벤트는 값을 변경한 당사자 탭(Self)에서는 발생하지 않습니다. 오직 다른 탭에서만 발생합니다.
쿠키는 그냥 심으면 털립니다. 보안 엔지니어처럼 설정해야 합니다.
document.cookie)로 쿠키에 접근하는 것을 막습니다.Strict: 내 사이트 안에서만 쿠키 전송. 외부 링크 타고 들어오면 쿠키 안 보냄(로그인 풀림).Lax (기본값): 외부에서 들어올 때(Link Navigation)는 허용하지만, <img> 태그나 <iframe>, POST 요청 등에는 쿠키를 안 보냄. 대부분의 사이트에 적합.None: 다 보냄. (단, Secure 필수로 켜야 함).IndexedDB는 단순한 키-밸류 저장소가 아닙니다. Transactional NoSQL Database입니다. LocalStorage가 5MB짜리 메모장이라면, IndexedDB는 수백 MB를 다루는 엑셀 파일이나 같습니다.
price, date)로 빠르게 검색하기 위해 인덱스를 걸 수 있습니다.네이티브 API는 이벤트 기반(onsuccess, onerror)이라 콜백 지옥에 빠지기 쉽습니다. 실제로는 Dexie.js 같은 래퍼 라이브러리를 쓰는 게 정신 건강에 좋습니다.
/* Native API (복잡함) */
const req = indexedDB.open("MyStore", 1);
req.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore("users", { keyPath: "id" });
};
/* Dexie.js (깔끔함) */
import Dexie from 'dexie';
const db = new Dexie("MyStore");
db.version(1).stores({
users: "++id, name, age" // id는 자동증가, name과 age는 인덱싱
});
// 데이터 추가 (Async/Await 사용 가능!)
await db.users.add({ name: "Ratia", age: 25 });
// 데이터 조회 (쿼리 빌더 지원)
const adults = await db.users.where('age').above(19).toArray();
"장바구니는 어디에 담아야 할까요?" 이 질문 하나로 주니어와 시니어를 가를 수 있습니다.
JSON.stringifyconst cart = [{ id: 1, name: "맥북", price: 3000000 }];
localStorage.setItem('cart', JSON.stringify(cart));
stringify 할 때마다 화면 버벅임(Frame Drop).QuotaExceededError 발생하며 저장 실패.쇼핑몰 장바구니나 텍스트 에디터의 임시 저장 기능처럼 데이터 무결성과 대용량이 필요한 경우 무조건 IndexedDB입니다. IndexedDB는 비동기(Non-blocking)라서 UI 스레드를 방해하지 않고, 트랜잭션 덕분에 데이터 꼬임도 방지합니다.
브라우저는 저장 공간을 무제한으로 주지 않습니다.
브라우저는 디스크가 꽉 차면 LRU(Least Recently Used) 알고리즘으로 안 쓰는 사이트 데이터부터 지웁니다. 내 사이트 데이터가 지워지면 안 된다면, 브라우저에게 "이 데이터는 중요해!"라고 신고해야 합니다.
if (navigator.storage && navigator.storage.persist) {
const isPersisted = await navigator.storage.persist();
console.log(`영구 저장 허용됨: ${isPersisted}`);
}
허용되면, 사용자가 직접 지우기 전까지는 브라우저가 임의로 청소하지 않습니다.
상황별 요약 가이드입니다.
브라우저는 더 이상 단순한 문서 뷰어가 아닙니다. 하나의 작은 운영체제입니다. 각 저장소의 특성을 이해하고 적재적소에 배치하는 것이 프론트엔드 엔지니어의 핵심 역량입니다.