토스터 프로젝트에서 네컷 사진을 촬영한 후, 사용자가 새로고침하게 되면 사진이 날아가는 이슈가 있었다.
찍은 사진이 저장도 전에 날아가면 너무 치명적이니 이 사진이 그대로 유지하도록 작업하기로 했다.
단순한 기능처럼 보였지만, 이 과정에서 꽤 다양한 삽질과 학습이 있었다.
🧨 문제 발생: 새로고침하면 사진이 사라진다!
사진을 4컷 다 찍은 후 실수로 새로고침을 누르면...
😱 "아놔.. 사진 다 날아갔어..."
처음에는 이 상태를 React Context API로 관리하고 있었기 때문에, 새로고침 시 상태가 초기화되는 건 너무나 당연한 결과였다.
그래서 가장 먼저 떠오른 해결책은 바로...
💾 시도 1: localStorage에 저장하자!
useEffect(() => {
localStorage.setItem('toast_photos', JSON.stringify(photos));
}, [photos]);
사진 데이터(base64)를 문자열로 저장하고 복원하는 코드를 간단히 짰다.
처음에는 잘 되는 것처럼 보였지만, 사진을 2장 찍는 순간 벌써..
Uncaught QuotaExceededError:
Failed to execute 'setItem' on 'Storage':
Setting the value of 'toast_photos' exceeded the quota.
😮💨 결국 브라우저가 허용하는 localStorage 용량(약 5MB)을 초과해버렸다.
base64 형식으로 저장하는 이미지 데이터는 용량이 꽤 컸기 때문이당.. 하
🧪 시도 2: sessionStorage? → 결과는 동일
sessionStorage도 시도했지만 용량 제한은 거의 동일했다.
사진이 많아지면 무조건 터지는 구조였다.
📦 근본적인 해결책: IndexedDB
그래서 최종적으로 선택한 저장 방식은 바로 IndexedDB였다.
IndexedDB란?
- 브라우저에 내장된 비동기 데이터베이스
- localStorage보다 훨씬 많은 데이터를 저장 가능 (수백 MB~GB 수준)
- 구조화된 데이터와 바이너리 저장에도 적합 (이미지 포함)
- 로컬스토리지나 세션 스토리지보다는 사용법이 조금 복잡하지만, idb-keyval 같은 라이브러리 덕분에 쉽게 다룰 수 있다!
🔧 적용 방식
yarn add idb-keyval
사진을 저장하고 불러오는 유틸을 따로 분리했다:
// photoStorage.ts
import { get, set } from 'idb-keyval';
const STORAGE_KEY = 'toast_photos';
export const savePhotos = async (photos: string[]) => {
await set(STORAGE_KEY, photos);
};
export const loadPhotos = async (): Promise<string[] | null> => {
return await get(STORAGE_KEY);
};
PhotoProvider 컴포넌트에서는 아래와 같은 로직을 사용했다:
const [photos, setPhotos] = useState<string[]>([]);
const [isRestored, setIsRestored] = useState(false);
// 복원
useEffect(() => {
loadPhotos().then((restored) => {
if (restored) setPhotos(restored);
setIsRestored(true);
});
}, []);
// 저장
useEffect(() => {
if (isRestored) {
savePhotos(photos);
}
}, [photos, isRestored]);
휴.. 헤치웠나...?
🧩 여기서 또 한 번의 삽질!
IndexedDB 저장까지 완료하고 새로고침을 해봤더니,
🤔 "잠깐 사진이 보였다가 바로 사라진다?"
알고 보니 이건 복원 직후 상태(photos: [])가 저장되면서 이전 데이터를 덮어써버린 문제였다.
해결책? 👉 복원이 완료되기 전에는 저장을 하지 않는 조건 추가 !
const [isRestored, setIsRestored] = useState(false);
이렇게 간단한 boolean 플래그 하나로 문제를 깔끔하게 해결할 수 있었다.
✅ 결과
- 새로고침 후에도 촬영한 사진이 그대로 유지됨! 오예 ~ !!
- 용량 초과 문제 해결
- 상태 관리 코드도 깔끔하게 분리
💡 마무리하며
이번 작업을 통해 "간단해 보이는 기능도 예상치 못한 제약과 한계에 부딪힐 수 있다"는 걸 다시금 느꼈다.
그리고 "웹 앱에서 상태를 오래 유지하는 방법"에 대한 감도 더 잘 잡힌 것 같다.
지금처럼 로컬에서 모든 걸 처리하는 경우에도 적절한 저장 방식을 선택하는 건 정말 중요하다는 걸 배웠다.
토스터 부스 파이팅 ~ !!
🍞 토스터 부스 구경 가기 🍞
TOASTER BOOTH
toaster; 나만의 귀여운 토스트 네컷 사진관 📸
toaster-booth.vercel.app
'개발자의 성장 도파민 기록' 카테고리의 다른 글
🍞 Toaster Booth — 토스터기로 네 컷을 찍어보세요! (3) | 2025.04.14 |
---|---|
🥐 toaster: Rive를 사용해서 귀엽고 인터랙티브한 서비스 만들기 (5) | 2025.04.08 |
🍞 toaster: 헤일리의 웹캠네컷의 시작 ! ; 브라우저에서 카메라 사용부터 컴포넌트를 이미지로 변환 후 저장까지! (7) | 2025.04.02 |
🏆 마일리지 장학금 신청 서비스 개발기: 한달간의 성장 일지 (11) | 2025.03.20 |
👀 개발자가 건강을 챙기는 방법: "EYE CARE" 익스텐션 프로젝트 (1) (6) | 2025.02.15 |