
프론트엔드 개발을 하다 보면 자연스럽게 “이 상태를 어디에 저장해야 하지?”라는 질문과 마주하게 된다. 로그인 상태, 테마 설정, 폼 입력 값, API 응답 캐시, 오프라인 대응 데이터까지. 이 모든 것은 결국 브라우저 어딘가에 저장되어야 한다.
브라우저는 이미 여러 가지 저장 수단을 제공하고 있다. 쿠키, 로컬 스토리지, 세션 스토리지, IndexedDB, Cache Storage 등이다. 문제는 이 저장소들이 모두 “데이터를 저장한다”는 공통점만 있을 뿐, 용도와 철학은 전혀 다르다는 점이다. 이 차이를 이해하지 못하면 상태 관리가 꼬이고, 보안이나 성능 문제로 이어지기 쉽다.
쿠키
쿠키는 서버 중심의 저장소다. 서버가 설정하고, 브라우저가 보관하며, 모든 HTTP 요청에 자동으로 포함된다. 이 특성 때문에 쿠키는 여전히 인증과 세션 관리의 핵심 도구로 사용된다.
프론트엔드 관점에서 중요한 점은 쿠키가 네트워크 계층에 묶여 있다는 사실이다. 쿠키에 저장된 값은 UI 상태라기보다는 “서버와 공유해야 하는 상태”에 가깝다. 예를 들어 로그인 세션 ID, 리프레시 토큰, 서버에서 해석해야 하는 플래그 등이 여기에 해당한다.
하지만 프론트엔드에서 쿠키를 직접 다루는 경험은 그리 좋지 않다.
문자열 기반 파싱이 필요하고
4KB라는 매우 작은 용량 제한이 있으며
매 요청마다 전송되기 때문에 성능 부담도 있다
document.cookie = "theme=dark; path=/";이 코드 한 줄 뒤에는 보안 속성(HttpOnly, Secure, SameSite)에 대한 고민이 항상 따라온다. 특히 XSS 관점에서 보면, 프론트엔드에서 직접 접근 가능한 쿠키는 매우 조심스럽게 다뤄야 한다. 그래서 프론트엔드 개발자 입장에서 쿠키는 “내가 적극적으로 상태를 넣는 공간”이라기보다는, 서버와의 계약 결과를 읽는 공간”에 가깝다.
참고로 "쿠키"라는 용어는 웹 브라우저 프로그래머 루 몬툴리가 만들어냈다. 이는 유닉스 프로그래머들이 사용한, 프로그램이 수신 후 변경하지 않은 채로 반환하는 데이터의 패킷을 의미하는 매직 쿠키라는 용어에서 비롯된 것이다.
로컬/세션 스토리지
로컬 스토리지와 세션 스토리지는 프론트엔드 개발자에게 훨씬 친숙한 저장소다. 서버와 자동으로 통신하지 않고, 자바스크립트 API로 직접 접근할 수 있으며, 비교적 넉넉한 용량을 제공한다.
로컬 스토리지는 “브라우저를 껐다 켜도 유지되는 상태”를 저장하기에 적합하다. 대표적으로 다크 모드 여부, 언어 설정, 마지막으로 본 페이지 같은 정보가 여기에 들어간다.
localStorage.setItem("theme", "dark");세션 스토리지는 반대로 “탭 단위의 상태”를 저장하는 데 적합하다. 같은 도메인이라도 탭이 다르면 공유되지 않기 때문에, 멀티 탭에서 충돌이 나면 안 되는 UI 상태에 유용하다.
sessionStorage.setItem("form-draft", JSON.stringify(values));프론트엔드 관점에서 웹 스토리지의 가장 큰 장점은 명확한 생명주기다.
로컬 스토리지: 사용자 의도가 바뀔 때까지
세션 스토리지: 탭이 살아 있는 동안
다만 단점도 분명하다.
동기 API라 메인 스레드를 블로킹할 수 있고
문자열만 저장 가능하며
보안적으로 민감한 정보에는 부적합하다
또 하나 중요한 특징은 storage 이벤트다. 이 이벤트 덕분에 프론트엔드는 탭 간 상태 동기화라는 강력한 기능을 얻는다.
window.addEventListener("storage", (event) => {
if (event.key === "theme") {
applyTheme(event.newValue);
}
});이 기능은 글로벌 상태 관리와 결합될 때 꽤 큰 힘을 발휘한다.
IndexedDB
IndexedDB는 프론트엔드에서 다루는 저장소 중 가장 강력하지만, 동시에 가장 무겁다. 이 저장소는 단순한 key-value를 넘어, 클라이언트 사이드 데이터베이스에 가깝다.
IndexedDB가 필요한 순간은 명확하다.
대량의 데이터가 필요할 때
오프라인 환경을 고려해야 할 때
API 응답을 구조적으로 저장해야 할 때
예를 들어 메신저 앱의 대화 기록, 지도 앱의 타일 데이터, 대규모 검색 결과 캐시 같은 경우다.
const request = indexedDB.open("AppDB", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore("messages", { keyPath: "id" });
};프론트엔드 관점에서 IndexedDB의 가장 큰 특징은 상태 관리 도구가 아니라는 점이다. React state나 전역 상태처럼 자주 읽고 쓰는 값보다는, “장기 보관되는 데이터”에 더 가깝다. 그래서 실제로는 IndexedDB를 직접 쓰기보다는, 이를 감싼 라이브러리(Dexie, localForage 등)를 사용하는 경우가 많다. 이 역시 프론트엔드 아키텍처의 일부다.
Cache Storage
Cache Storage는 프론트엔드 개발자가 처음 접하면 다소 낯설다. 이 저장소는 값이 아니라 요청과 응답을 저장한다. 즉, 데이터 저장소라기보다는 네트워크 계층을 다루는 도구다. Service Worker와 함께 사용하면, 프론트엔드는 네트워크 요청을 가로채고 캐시 전략을 직접 정의할 수 있다.
caches.open("v1").then((cache) => {
cache.add("/index.html");
});프론트엔드 관점에서 Cache Storage는
성능 최적화
오프라인 UX
반복 요청 감소
를 위한 도구다. 상태 관리나 사용자 설정을 넣는 공간은 아니다. 대신 “이 리소스는 언제, 어디서, 어떻게 가져올 것인가”를 통제하는 레이어라고 보는 편이 정확하다.
더 읽어보기
2026.03.04
JavaScript를 위한 더 나은 Streams API가 필요하다
이 포스트는 node.js의 코어 컨트리뷰트이며 Cloudflare Workers 팀 소속 개발자 James M Snell이 cloudflare 블로그에 올린 We deserve a better streams API for JavaScript 게시글을 번역한 것이다. 번역하는 과정에서…
2026.03.01
함수, 펑터, 그리고 모나드
복잡한 버그는 대개 거대한 기능이 아니라 사소한 데이터 변환 구간에서 시작된다. 문자열을 한 번 다듬고, 숫자를 한 번 바꾸고, 그 결과를 다음 단계로 넘기는 단순한 흐름이다. 그런데 조건이 조금씩 추가되는 순간 로직은 빠르게 복잡해진다. 값만 바꾸던 코드가 어느새 값의 부재, 비동기…
2026.01.15
Static Hermes로 JavaScript를 C 코드로 컴파일하기
이 포스트는 parcel의 메인테이너 Devon Govett가 자신의 블로그에 올린 How to compile JavaScript to C with Static Hermes 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는 사견이 포함되어있기도 하다…
2026.01.03
CSS Color Functions
이 포스트는 Sunkanmi Fafowora가 css-tricks에 올린 CSS Color Functions 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는 사견이 포함되어있기도 하다.몇 달 전 누군가 저에게 “웹사이트가 돋보이려면 무엇이 필요할까…
2026.04.11
Trie 자료구조
문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다.Trie는 무엇…
2026.03.19
Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까
웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…
2026.03.19
Streams API 부록 1. HTTP 다운로드 진행률은 어떻게 계산될까
파일을 다운로드할 때 가끔 몇 퍼센트 진행되었는지 혹은 진행 막대(progress bar)가 조금씩 채워지는 모습을 볼 수 있다. 그런데 모든 다운로드가 이런 식으로 진행률을 보여 주는 것은 아니다. 어떤 다운로드는 퍼센트가 표시되지만, 어떤 경우에는 진행 막대 없이 로딩 스피너만 계속…
2026.03.13
Streams API 4. 왜 모든 언어에는 Stream API가 존재할까
Streams API를 공부하다 보면 묘한 기시감을 느끼게 된다. JavaScript에서 ReadableStream, WritableStream, TransformStream을 살펴보고 있는데, 어딘가 낯설지 않다. Java를 써 본 사람이라면 InputStream, OutputStre…
댓글
댓글을 불러오는 중...