[React] 로컬스토리지는 왜 문제를 일으킬까?

2025. 4. 22. 23:09·JavaScript/React

웹 프론트엔드 개발에서 가장 손쉽게 사용할 수 있는 클라이언트 저장소인 localStorage. 설정이 필요 없고, 사용법도 간단해 많은 개발자들이 애용합니다. 하지만 이 간단함 이면에는 반드시 이해하고 넘어가야 할 구조적 한계가 존재합니다. 특히 로컬스토리지는 동기(synchronous) API라는 점에서 의도치 않은 버그나 퍼포먼스 문제를 유발할 수 있습니다.

 

로컬스토리지는 왜 동기 방식일까?

로컬스토리지는 브라우저 스펙상 동기적으로 설계된 API입니다. 즉, 다음과 같은 특성을 가집니다.

  • JavaScript의 메인 실행 스레드에서 즉시 동작합니다.
  • 읽기와 쓰기 작업은 대기 없이 즉시 반환되어야 합니다.
  • 따라서 하나의 작업이라도 지연되면, 앱 전체의 반응성이 떨어질 수 있습니다.

이러한 구조는 단순한 앱에서는 문제가 되지 않지만, 실제 서비스에서는 여러 가지 문제로 이어질 수 있습니다.

동기 API로 인해 발생하는 주요 문제 유형

유형 1. UI 프리즈 / 메인 스레드 블로킹

현상:
localStorage.getItem()을 대량 반복 호출하면, 특히 모바일 환경에서 UI가 버벅이거나 잠시 멈추는 현상이 발생할 수 있습니다.

for (let i = 0; i < 1000; i++) {
  const data = localStorage.getItem('someKey-' + i);
}

해결책:

  • 초기에 데이터를 한 번에 읽어 메모리 캐싱해 두기
  • 반복 접근을 막고 Map이나 useRef 등 인메모리 구조로 대체
const cache = new Map();
for (let i = 0; i < 1000; i++) {
  cache.set(`someKey-${i}`, localStorage.getItem(`someKey-${i}`));
}

유형 2. 탭 간 동기화 문제

현상:
다른 탭에서 setItem()으로 값을 변경한 뒤, 현재 탭에서 아직 그 변경을 인지하지 못해 이전 값을 읽는 현상 발생

원인:

  • 로컬스토리지는 모든 탭에 공유되지만
  • 변경 알림은 storage 이벤트로 비동기 전달됨
  • 즉, 실시간성이 떨어짐

해결책:

  • window.addEventListener('storage')로 변경 감지 처리
window.addEventListener('storage', (event) => {
  if (event.key === 'token') {
    console.log('다른 탭에서 토큰 변경:', event.newValue);
  }
});
  • 또는 Zustand 등 상태관리 라이브러리 + storage sync 플러그인 사용

유형 3. 저장 용량 초과

현상:
QuotaExceededError 발생. 특히 모바일 브라우저는 저장 용량이 5MB 이하로 제한될 수 있음.

try {
  localStorage.setItem('bigData', JSON.stringify(data));
} catch (e) {
  if (e.name === 'QuotaExceededError') {
    // 대체 로직 실행
  }
}

해결책:

  • 데이터를 압축 형식(JSON) 으로 저장
  • 대용량 구조는 IndexedDB로 분리
  • 저장 전에 사이즈를 미리 계산하거나 예외 처리 로직 작성

유형 4. 페이지 초기화 Race Condition

현상:
컴포넌트가 마운트되기 전에 localStorage에 접근 시 에러 발생. 특히 SSR 환경(Next.js)에서 빈번

const token = localStorage.getItem('token'); // SSR 시 에러

해결책:

  • typeof window !== 'undefined'로 브라우저 환경인지 먼저 확인
const token = typeof window !== 'undefined'
  ? localStorage.getItem('token')
  : null;
  • 커스텀 훅으로 안전하게 래핑
function useSafeLocalStorage(key: string): string | null {
  if (typeof window === 'undefined') return null;
  try {
    return localStorage.getItem(key);
  } catch {
    return null;
  }
}

유형 5. 타이밍 불일치 문제

현상:
localStorage 값이 필요한 로직보다 늦게 실행되어 초기 상태가 잘못 반영됨

해결책:

  • 로컬스토리지 접근을 useEffect로 분리하고 상태로 관리
const [token, setToken] = useState<string | null>(null);

useEffect(() => {
  const stored = localStorage.getItem('token');
  setToken(stored);
}, []);
  • Zustand 등에서는 비동기 hydration 기능 사용

문제 해결 전략 정리

문제 유형 해결 전략
UI 프리즈 캐싱, 메모리 구조 사용
탭 간 충돌 storage 이벤트 활용
용량 초과 압축 저장, IndexedDB 분리
초기화 Race 브라우저 체크 후 접근
타이밍 불일치 useEffect에서 상태 관리

 

근본적인 대안: IndexedDB

localStorage의 구조적 한계를 극복하고 싶다면, 비동기 스토리지인 IndexedDB가 훨씬 강력한 대안이 됩니다.

  • 비동기 I/O
  • 대용량 구조화 데이터 저장 가능
  • 트랜잭션 지원

라이브러리 예시: idb, Dexie.js

import { openDB } from 'idb';

const db = await openDB('my-db', 1, {
  upgrade(db) {
    db.createObjectStore('tokens');
  },
});

await db.put('tokens', 'abc123', 'token');
const token = await db.get('tokens', 'token');

결론: 로컬스토리지는 신중하게, 단순하게

장점 단점
빠르고 사용법이 간단 동기 API로 인한 블로킹 위험
탭 간 공유 가능 실시간 동기화 어려움, 충돌 발생
영구 저장 저장 용량 제한, 만료 없음

 

로컬스토리지는 여전히 간단한 토큰, 설정값, 플래그 저장처럼 작은 용도에는 유용합니다. 하지만 빈번한 접근, 동시성, 대용량 저장이 필요한 경우에는 반드시 IndexedDB 또는 상태관리 + hydration 구조로 전환하는 것이 더 안전합니다.

728x90
'JavaScript/React' 카테고리의 다른 글
  • [React] Props 지옥에서 벗어나는 법
  • [React] Context API
  • [React] TanStack Query (구 React Query)
  • [React] 컴포넌트 라이프사이클(Component Lifecycle)
츄핑
츄핑
    250x250
  • 츄핑
    개발로그
    츄핑
  • 전체
    오늘
    어제
    • 분류 전체보기
      • CS
        • 자료구조
        • 알고리즘
        • 운영체제
        • 네트워크
        • 데이터베이스
        • 인프라
        • Web
      • PS
        • 백준
      • JavaScript
        • React
        • Express
        • NestJS
        • TypeScript
        • Node.js
        • Electron
      • Java
        • Spring
      • Dart
        • Flutter
      • PHP
        • CodeIgniter
      • etc
        • 이산수학
        • 선형대수학
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    오블완
    티스토리챌린지
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
츄핑
[React] 로컬스토리지는 왜 문제를 일으킬까?
상단으로

티스토리툴바