리스트로 돌아가기
내가 만든 쿠키 Cookie... LocalStorage, SessionStorage thumbnail

내가 만든 쿠키 Cookie... LocalStorage, SessionStorage

브라우저 저장소의 세 가지 방법인 Cookie, LocalStorage, SessionStorage의 차이점과 사용 시나리오를 실무 관점에서 이해하고, 올바르게 활용하는 방법을 알아봤어요.

2026-01-06 00:00

내가 만든 쿠키 Cookie... LocalStorage, SessionStorage

안녕하세요! 포테코입니다.

웹 개발에서 클라이언트 사이드 데이터 저장은 필수적인 기능이에요. 브라우저는 데이터를 저장하는 세 가지 주요 방법을 제공합니다: Cookie, LocalStorage, SessionStorage. 이번 글에서는 각각의 특징, 사용 시나리오, 차이점을 실무 관점에서 다뤄볼게요.

개요

Cookie (쿠키)

쿠키는 서버와 클라이언트 간 데이터를 주고받기 위해 사용되는 작은 텍스트 파일이에요. HTTP 요청과 응답 헤더에 자동으로 포함돼요.

LocalStorage (로컬 스토리지)

LocalStorage는 브라우저에 영구적으로 데이터를 저장하는 Web Storage API예요. 명시적으로 삭제하지 않는 한 데이터가 유지돼요.

SessionStorage (세션 스토리지)

SessionStorage는 브라우저 탭이 열려있는 동안만 데이터를 저장하는 Web Storage API예요. 탭을 닫으면 데이터가 자동으로 삭제돼요.

Cookie는 서버와 클라이언트 간 통신에 주로 사용되고, LocalStorage와 SessionStorage는 클라이언트 사이드에서만 사용되는 Web Storage API예요. 각각의 목적과 특성이 다르므로 상황에 맞게 선택하는 것이 중요해요.

Cookie (쿠키)

특징

  • 크기 제한: 약 4KB (도메인당)
  • 자동 전송: HTTP 요청 시 자동으로 서버에 전송
  • 만료 시간: 설정 가능 (만료 시간 또는 세션 종료 시)
  • 도메인/경로 제한: 특정 도메인과 경로에만 접근 가능
  • 보안 옵션: HttpOnly, Secure, SameSite 플래그 지원

사용 시나리오

  1. 인증 토큰 저장: 로그인 상태 유지
  2. 서버 세션 관리: 서버에서 세션 ID 추적
  3. 사용자 설정: 언어, 테마 등 사용자 선호도
  4. 트래킹: 사용자 행동 분석 (주의 필요)

기본 사용법

// Cookie 설정
document.cookie =
  "username=john; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";
 
// Cookie 읽기
function getCookie(name: string): string | null {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop()?.split(";").shift() || null;
  return null;
}
 
// Cookie 삭제
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";

고급 사용법 (HttpOnly, Secure)

// 서버에서 설정 (Node.js 예시)
res.cookie("token", "abc123", {
  httpOnly: true, // JavaScript에서 접근 불가 (XSS 방지)
  secure: true, // HTTPS에서만 전송
  sameSite: "strict", // CSRF 공격 방지
  maxAge: 3600000, // 1시간
});

Cookie의 장단점

장점:

  • 서버에서 자동으로 접근 가능
  • 만료 시간 설정 가능
  • 도메인/경로 제한으로 보안 강화 가능

단점:

  • 크기 제한이 작음 (4KB)
  • 모든 HTTP 요청에 포함되어 성능 오버헤드
  • JavaScript에서 직접 접근이 불편함

LocalStorage (로컬 스토리지)

특징

  • 크기 제한: 약 5-10MB (브라우저마다 다름)
  • 영구 저장: 명시적으로 삭제하지 않는 한 유지
  • 도메인별 격리: 같은 도메인에서만 접근 가능
  • 동기 API: 블로킹 작업 (메인 스레드 차단)
  • 문자열만 저장: 객체는 JSON으로 변환 필요

사용 시나리오

  1. 사용자 설정 저장: 테마, 언어, 선호도
  2. 캐시 데이터: API 응답 캐싱
  3. 오프라인 데이터: 오프라인 상태에서 사용할 데이터
  4. 장바구니/위시리스트: 사용자 쇼핑 데이터

기본 사용법

// 데이터 저장
localStorage.setItem("username", "john");
localStorage.setItem("user", JSON.stringify({ id: 1, name: "John" }));
 
// 데이터 읽기
const username = localStorage.getItem("username");
const user = JSON.parse(localStorage.getItem("user") || "{}");
 
// 데이터 삭제
localStorage.removeItem("username");
 
// 전체 삭제
localStorage.clear();
 
// 키 목록 가져오기
const keys = Object.keys(localStorage);

React Hook 예제

"use client";
 
import { useState, useEffect } from "react";
 
function useLocalStorage<T>(key: string, initialValue: T) {
  // 초기값 가져오기
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
 
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
 
  // 값 설정 함수
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      // 함수인 경우 이전 값 사용
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
 
      setStoredValue(valueToStore);
 
      if (typeof window !== "undefined") {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.error(error);
    }
  };
 
  return [storedValue, setValue] as const;
}
 
// 사용 예시
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage("theme", "light");
 
  return (
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
      Current theme: {theme}
    </button>
  );
}

LocalStorage의 장단점

장점:

  • 큰 용량 (5-10MB)
  • 간단한 API
  • 영구 저장
  • 서버 요청 없이 클라이언트에서만 처리

단점:

  • 동기 작업으로 성능 영향
  • 문자열만 저장 가능
  • XSS 공격에 취약
  • 서버에서 직접 접근 불가

SessionStorage (세션 스토리지)

특징

  • 크기 제한: 약 5-10MB (브라우저마다 다름)
  • 세션 기반: 탭이 닫히면 자동 삭제
  • 탭별 격리: 각 탭마다 독립적인 저장소
  • 동기 API: LocalStorage와 동일
  • 문자열만 저장: 객체는 JSON으로 변환 필요

사용 시나리오

  1. 임시 데이터: 폼 입력 임시 저장
  2. 탭별 상태: 각 탭의 독립적인 상태 관리
  3. 일회성 데이터: 페이지 새로고침 시 유지하되 탭 닫으면 삭제
  4. 멀티탭 격리: 같은 사이트의 다른 탭과 데이터 공유 불필요

기본 사용법

// 데이터 저장
sessionStorage.setItem(
  "formData",
  JSON.stringify({ name: "John", email: "john@example.com" })
);
 
// 데이터 읽기
const formData = JSON.parse(sessionStorage.getItem("formData") || "{}");
 
// 데이터 삭제
sessionStorage.removeItem("formData");
 
// 전체 삭제
sessionStorage.clear();

React Hook 예제

"use client";
 
import { useState, useEffect } from "react";
 
function useSessionStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
 
    try {
      const item = window.sessionStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
 
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
 
      setStoredValue(valueToStore);
 
      if (typeof window !== "undefined") {
        window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.error(error);
    }
  };
 
  return [storedValue, setValue] as const;
}
 
// 사용 예시: 폼 임시 저장
function ContactForm() {
  const [formData, setFormData] = useSessionStorage("contactForm", {
    name: "",
    email: "",
    message: "",
  });
 
  return (
    <form>
      <input
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        placeholder="Name"
      />
      {/* ... */}
    </form>
  );
}

SessionStorage의 장단점

장점:

  • 탭별 독립성
  • 자동 정리 (탭 닫으면 삭제)
  • 큰 용량 (5-10MB)
  • 간단한 API

단점:

  • 탭이 닫히면 데이터 손실
  • 동기 작업으로 성능 영향
  • 문자열만 저장 가능
  • 서버에서 직접 접근 불가

비교표

특징CookieLocalStorageSessionStorage
크기 제한약 4KB5-10MB5-10MB
저장 기간만료 시간 설정 가능영구 저장탭 닫을 때까지
서버 접근✅ 자동 전송❌ 불가능❌ 불가능
도메인 접근도메인/경로 제한도메인별 격리도메인별 격리
탭 간 공유✅ 공유✅ 공유❌ 탭별 독립
API 방식문자열 조작동기 API동기 API
성능 영향모든 요청에 포함동기 블로킹동기 블로킹
보안 옵션HttpOnly, Secure, SameSite
사용 목적서버 통신, 인증영구 데이터 저장임시 데이터 저장

Cookie는 서버와의 통신이 필요할 때, LocalStorage는 영구적으로 저장해야 할 때, SessionStorage는 탭이 열려있는 동안만 필요할 때 사용하는 것이 적절해요.

실전 사용 예제

예제 1: 인증 토큰 관리

// Cookie 사용 (서버에서 접근 필요)
// 서버 사이드
res.cookie("authToken", token, {
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7일
});
 
// LocalStorage 사용 (클라이언트에서만 접근)
// 클라이언트 사이드
localStorage.setItem("authToken", token);
 
// 토큰 읽기
function getAuthToken(): string | null {
  // Cookie에서 읽기 (서버에서 설정한 경우)
  // 또는 LocalStorage에서 읽기
  return localStorage.getItem("authToken");
}

예제 2: 사용자 설정 저장

// LocalStorage 사용 (영구 저장)
interface UserSettings {
  theme: "light" | "dark";
  language: "ko" | "en";
  notifications: boolean;
}
 
function saveUserSettings(settings: UserSettings) {
  localStorage.setItem("userSettings", JSON.stringify(settings));
}
 
function getUserSettings(): UserSettings {
  const stored = localStorage.getItem("userSettings");
  return stored
    ? JSON.parse(stored)
    : { theme: "light", language: "ko", notifications: true };
}

예제 3: 폼 데이터 임시 저장

// SessionStorage 사용 (탭 닫으면 삭제)
function saveFormDraft(formData: FormData) {
  sessionStorage.setItem("formDraft", JSON.stringify(formData));
}
 
function loadFormDraft(): FormData | null {
  const draft = sessionStorage.getItem("formDraft");
  return draft ? JSON.parse(draft) : null;
}
 
// 페이지 언로드 시 자동 저장
window.addEventListener("beforeunload", () => {
  const formData = getFormData();
  saveFormDraft(formData);
});

예제 4: Next.js에서 사용하기

// hooks/useStorage.ts
"use client";
 
import { useState, useEffect } from "react";
 
type StorageType = "localStorage" | "sessionStorage";
 
function useStorage<T>(
  key: string,
  initialValue: T,
  storageType: StorageType = "localStorage"
) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
 
    try {
      const storage =
        storageType === "localStorage"
          ? window.localStorage
          : window.sessionStorage;
      const item = storage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
 
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
 
      setStoredValue(valueToStore);
 
      if (typeof window !== "undefined") {
        const storage =
          storageType === "localStorage"
            ? window.localStorage
            : window.sessionStorage;
        storage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.error(error);
    }
  };
 
  return [storedValue, setValue] as const;
}
 
export default useStorage;
// components/ThemeProvider.tsx
"use client";
 
import useStorage from "@/hooks/useStorage";
 
export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useStorage("theme", "light", "localStorage");
 
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", theme);
  }, [theme]);
 
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

보안 고려사항

1. 민감한 데이터 저장 금지

// ❌ 나쁜 예: 민감한 정보를 LocalStorage에 저장
localStorage.setItem("password", userPassword);
localStorage.setItem("creditCard", cardNumber);
 
// ✅ 좋은 예: 서버에서만 관리
// 인증 토큰은 HttpOnly Cookie 사용
res.cookie("token", token, { httpOnly: true, secure: true });

2. XSS 공격 방지

// LocalStorage/SessionStorage는 XSS 공격에 취약
// 사용자 입력을 저장하기 전에 검증 및 이스케이프 처리
 
function sanitizeInput(input: string): string {
  // HTML 태그 제거, 특수 문자 이스케이프
  return input.replace(/<[^>]*>/g, "").trim();
}
 
const userInput = sanitizeInput(userInput);
localStorage.setItem("userData", JSON.stringify({ input: userInput }));

3. Cookie 보안 설정

// ✅ 안전한 Cookie 설정
res.cookie("sessionId", sessionId, {
  httpOnly: true, // JavaScript 접근 차단
  secure: true, // HTTPS에서만 전송
  sameSite: "strict", // CSRF 공격 방지
  maxAge: 3600000, // 만료 시간 설정
  domain: ".example.com", // 도메인 제한
  path: "/", // 경로 제한
});

성능 최적화

1. 비동기 처리

// LocalStorage/SessionStorage는 동기 작업
// 큰 데이터를 저장할 때는 비동기로 처리
 
async function saveLargeData(key: string, data: any) {
  return new Promise<void>((resolve) => {
    // Web Worker나 requestIdleCallback 사용
    requestIdleCallback(() => {
      try {
        localStorage.setItem(key, JSON.stringify(data));
        resolve();
      } catch (error) {
        console.error("Storage quota exceeded", error);
        resolve();
      }
    });
  });
}

2. 용량 관리

// LocalStorage 용량 체크
function getStorageSize(storage: Storage): number {
  let total = 0;
  for (let key in storage) {
    if (storage.hasOwnProperty(key)) {
      total += storage[key].length + key.length;
    }
  }
  return total;
}
 
// 용량이 부족하면 오래된 데이터 삭제
function manageStorage() {
  const maxSize = 5 * 1024 * 1024; // 5MB
  const currentSize = getStorageSize(localStorage);
 
  if (currentSize > maxSize) {
    // 오래된 데이터 삭제 로직
    const keys = Object.keys(localStorage).sort(
      (a, b) =>
        parseInt(localStorage.getItem(a + "_timestamp") || "0") -
        parseInt(localStorage.getItem(b + "_timestamp") || "0")
    );
 
    // 가장 오래된 데이터 삭제
    keys.slice(0, Math.floor(keys.length / 4)).forEach((key) => {
      localStorage.removeItem(key);
    });
  }
}

베스트 프랙티스

✅ DO

1. 적절한 저장소 선택

  • 서버 통신 필요 → Cookie
  • 영구 저장 필요 → LocalStorage
  • 임시 데이터 → SessionStorage

2. 에러 처리

try {
  localStorage.setItem("key", value);
} catch (error) {
  if (error.name === "QuotaExceededError") {
    console.error("Storage quota exceeded");
    // 오래된 데이터 삭제 또는 사용자에게 알림
  }
}

3. 타입 안정성

interface StoredData {
  theme: "light" | "dark";
  userId: number;
}
 
function getStoredData(): StoredData {
  const stored = localStorage.getItem("data");
  if (!stored) {
    return { theme: "light", userId: 0 };
  }
  return JSON.parse(stored) as StoredData;
}

4. SSR 고려

// Next.js에서 사용 시
if (typeof window !== "undefined") {
  localStorage.setItem("key", value);
}

❌ DON'T

1. 민감한 정보 저장 금지

// ❌ 절대 하지 말 것
localStorage.setItem("password", password);
localStorage.setItem("creditCard", cardNumber);

2. 과도한 데이터 저장

// ❌ 큰 데이터를 저장하지 말 것
localStorage.setItem("largeData", JSON.stringify(hugeArray));

3. 동기 작업 남용

// ❌ 반복적인 동기 작업
for (let i = 0; i < 1000; i++) {
  localStorage.setItem(`key${i}`, data[i]);
}

마무리

Cookie, LocalStorage, SessionStorage는 각각 다른 목적과 특성을 가지고 있어요:

  • Cookie: 서버와의 통신, 인증 토큰, 작은 데이터
  • LocalStorage: 영구 저장, 사용자 설정, 캐시 데이터
  • SessionStorage: 임시 데이터, 탭별 독립 상태

각 저장소의 특징을 이해하고 상황에 맞게 선택하면, 보안과 성능을 모두 고려한 최적의 데이터 저장 전략을 수립할 수 있어요. 민감한 정보는 절대 클라이언트에 저장하지 않고, 적절한 보안 설정을 적용하는 것이 중요해요.

적절한 저장소를 선택하면 더 안전하고 효율적인 웹 애플리케이션을 만들 수 있어요. 앞으로도 브라우저 저장소를 활용한 실전 사례와 보안 팁을 계속 공유할 예정이니 기대해 주세요!