본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev45

localStorage에 객체를 넣었더니 [object Object]가 저장된 이유

  • #Engineering Note
  • #JavaScript

문제 발생

사용자 설정을 저장했는데 다시 읽을 때 깨졌습니다.

localStorage.setItem("prefs", { theme: "dark" });
JSON.parse(localStorage.getItem("prefs"));   // SyntaxError

저장된 값은 "[object Object]"였습니다. 사용량이 많은 사용자에게서는 저장 자체가 실패하는 보고도 들어왔습니다.

원인 분석

저장되는 것은 문자열뿐입니다. MDN 설명대로 localStorage는 키와 값을 모두 문자열로 저장하며, 문자열이 아닌 값은 문자열로 변환됩니다. 객체는 String(obj)의 결과인 "[object Object]"가 됩니다.

용량 제한도 있습니다 — 한도를 넘으면 **QuotaExceededError**가 발생합니다. 브라우저마다 한도가 다르고, 사용자가 저장소를 차단한 경우에는 접근 자체가 **SecurityError**가 될 수 있습니다 — 문서는 사용자가 쿠키를 차단하면 브라우저가 이를 페이지의 데이터 저장을 막으라는 지시로 해석할 수 있다고 적습니다.

성격도 알아둘 것이 있습니다 — 동기적으로 동작하므로 큰 값을 읽고 쓰면 그동안 메인 스레드가 멈추고, 오리진 단위로 격리되며(http://https://가 서로 다른 저장소), 만료가 없어 세션을 넘어 영구히 남습니다.

해결 방안

  1. 직렬화·역직렬화를 감쌉니다. 파싱 실패까지 한 곳에서 처리합니다.
export function readJson(key, fallback) {
  try {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) : fallback;
  } catch {
    return fallback;      // 손상된 값이 화면 전체를 깨뜨리지 않게
  }
}
  1. 쓰기를 try/catch로 감쌉니다. 용량 초과와 저장소 차단이 실제로 일어납니다 — 실패해도 기능이 계속 동작하도록 설계합니다.

  2. 민감한 값을 두지 않습니다. 페이지에서 실행되는 어떤 스크립트든 읽을 수 있어 XSS 한 번이면 그대로 유출됩니다. 세션 토큰은 HttpOnly 쿠키가 맞는 자리입니다.

  3. 만료가 필요하면 직접 넣습니다. 값과 함께 저장 시각을 담아 읽을 때 확인합니다 — localStorage 자체에는 만료 개념이 없습니다.

  4. 큰 데이터는 IndexedDB로 옮깁니다. 동기 API로 수 MB를 다루면 스크롤과 입력이 눈에 띄게 끊깁니다.

  5. SSR에서는 존재하지 않습니다. 서버 렌더 중에 접근하면 오류가 나고, 클라이언트에서만 읽으면 첫 렌더와 값이 달라 하이드레이션 불일치가 생깁니다 — 초기값을 서버에서 내려주거나 마운트 이후에 읽습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.