글쓰기 UX 개선: 자동 저장 기능 도입기
1. 도입 목적: 불안정한 글쓰기 경험 개선
블로그에 글을 작성하다 의도하지 않은 상황으로 내용을 모두 잃는 경우가 발생했었다. 특히 장소 이동으로 네트워크 환경 변경으로 세션이 끊기거나, 브라우저가 갑자기 종료되거나, 실수로 새로고침(F5)이나 뒤로 가기 버튼을 누르는 등의 실수가 대표적이었다.
특히 최근 작성한 Polly 관련의 장문의 글도 이러한 경험으로 인해 작성을 처음부터 다시 하게 된 경우도 있었다.
이러한 경험은 블로그 포스팅에 있어서 치명적인 결함으로 판단됐다. 이를 해결하고, 어떤 상황에서든 작성 중인 내용을 안전하게 보호하기 위해 '자동 저장(Autosave)' 기능을 도입하기로 결정했다. 목표는 명확하다. '저장' 버튼을 누르기 전까지의 모든 변경 사항을 시스템이 자동으로 백업하여 데이터 유실의 위험을 원천적으로 차단하는 것이다.
2. 구현 전략: localStorage와 Debouncing
자동 저장 기능의 핵심 저장소로는 브라우저의 localStorage를 선택했다. 서버에 부담을 주지 않고, 오프라인 상태에서도 작동하며, 구현이 비교적 간단하기 때문이다.
다만, 사용자의 모든 키 입력마다 localStorage에 접근하는 것은 불필요한 디스크 쓰기(Write) 작업을 유발하여 성능 저하의 원인이 될 수 있다. 이를 방지하기 위해 디바운싱(Debouncing) 기법을 핵심 전략으로 채택했다.
- 디바운싱(Debouncing): 특정 이벤트가 연속적으로 발생할 때, 마지막 이벤트가 발생한 후 일정 시간(delay)이 지날 때까지 기다렸다가 콜백 함수를 딱 한 번만 실행하는 기법이다.
이 전략을 통해 "사용자의 타이핑이 2초 이상 멈췄을 때"만 저장 로직이 동작하도록 하여, 효율성과 안정성 두 마리 토끼를 모두 잡고자 했다.
3. 구현 과정
구현은 세 단계로 나누어 진행했다.
3.1. safeLocalStorage 유틸리티 생성
Next.js는 서버 사이드 렌더링(SSR)을 지원하므로, 서버 환경에는 존재하지 않는 window.localStorage에 직접 접근하면 에러가 발생한다. 이를 방지하기 위해 localStorage를 안전하게 감싸는(wrapping) 유틸리티 객체를 먼저 생성했다.
// 파일 위치: /utils/storage.ts export const safeLocalStorage = { get<T>(key: string, defaultValue: T): T { if (typeof window === 'undefined') { return defaultValue; } // ... (try-catch로 JSON.parse 처리) }, set<T>(key: string, value: T): void { if (typeof window === 'undefined') { return; } // ... (try-catch로 JSON.stringify 및 setItem 처리) }, remove(key: string): void { // ... }, };
typeof window === 'undefined' 조건문으로 브라우저 환경인지 먼저 확인하여 서버 사이드 에러를 방지하고, try...catch 구문으로 localStorage 접근 시 발생할 수 있는 예외를 처리하여 안정성을 높였다.
3.2. useAutosave 커스텀 훅 구현
자동 저장 로직의 재사용성을 높이고 컴포넌트로부터 로직을 분리하기 위해 React 커스텀 훅을 생성했다.
// 파일 위치: /hooks/useAutosave.ts import { useEffect, useRef } from 'react'; import { safeLocalStorage } from '@/utils/storage'; export interface AutosaveData { title: string; content: string; } const AUTOSAVE_KEY = 'post-autosave'; export function useAutosave(data: AutosaveData, delay = 2000) { const savedData = useRef(data); savedData.current = data; useEffect(() => { const save = () => { if (savedData.current.title.trim() || savedData.current.content.trim()) { safeLocalStorage.set(AUTOSAVE_KEY, savedData.current); } }; const timeoutId = setTimeout(save, delay); return () => clearTimeout(timeoutId); }, [data, delay]); // data가 변경될 때마다 effect 재실행 } // ... (autosaveManager: load, clear 함수)
이 훅은 data prop이 변경될 때마다 useEffect를 재실행한다. 내부에서는 setTimeout으로 저장 작업을 예약하고, 새로운 변경이 감지되면 clearTimeout으로 이전 예약을 취소한다. 이것이 디바운싱의 핵심 원리다.
3.3. Editor 페이지 컴포넌트에 적용
마지막으로, 생성한 훅과 매니저를 실제 새 글 작성 페이지 컴포넌트에 적용했다.
// 파일 위치: /app/posts/new/page.tsx function NewPostForm() { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); // 1. 자동 저장 훅 호출 useAutosave({ title, content }); // 2. 페이지 진입 시 임시 저장 글 불러오기 useEffect(() => { const savedData = autosaveManager.load(); if (savedData && confirm('임시 저장된 글이 있습니다. 불러오시겠습니까?')) { setTitle(savedData.title); setContent(savedData.content); } }, []); const handleSubmit = async (e: React.FormEvent) => { // ... (글 생성 API 호출) // 3. 발행 성공 후 임시 저장 데이터 삭제 autosaveManager.clear(); // ... (페이지 이동) }; // ... JSX }
단 몇 줄의 코드로 자동 저장, 불러오기, 삭제의 전체 라이프사이클을 컴포넌트에 통합할 수 있었다.
4. 성능 저하 트레이드오프 검토
새로운 기능을 도입할 때는 항상 성능에 미치는 영향을 고려해야 한다. 이번 자동 저장 기능의 경우, 성능 저하는 사용자가 체감하기 어려운 무시할 수 있는 수준이라고 판단했다.
- CPU 부하: 키 입력마다 발생하는
setTimeout/clearTimeout은 매우 가벼운 작업이다. 실제 무거운 작업인JSON.stringify와 디스크 쓰기는 디바운싱 덕분에 사용자가 입력을 멈췄을 때만 실행되므로 CPU에 거의 영향을 주지 않는다. - 메모리 사용량:
localStorage의 용량은 5~10MB로, 수십 KB 수준인 게시물 텍스트를 저장하기에는 충분하고도 남아 메모리 부담이 없다. - 디스크 I/O: 가장 비용이 큰 디스크 쓰기 작업이 디바운싱을 통해 최소화(2초에 최대 1번)되므로, 브라우저 반응성에 미치는 영향이 거의 없다.
결론적으로, "무시할 수 있는 수준의 성능 비용"을 지불하고 "데이터 유실 방지"라는 높은 가치를 얻는 성공적인 트레이드오프라고 할 수 있다.
네, 알겠습니다. '결과' 섹션을 확장하여 실제 E2E 테스트 시나리오를 담을 수 있도록 개선하겠습니다. 스크린샷 주석을 통해 각 단계를 명확히 보여줄 수 있도록 구성하겠습니다.
5. 최종 결과
기능 구현 완료 후, 로컬 환경에서 테스트를 통해 확인했다.
테스트 시나리오는 다음과 같다.
시나리오 1: 자동 저장 기능 검증
/posts/new페이지에 접속하여 제목과 본문에 테스트 내용을 입력한다.- 약 2~3초 후, 브라우저 개발자 도구의
Application > Local Storage탭을 확인한다. post-autosave키에title과content가 포함된 JSON 데이터가 정상적으로 저장된 것을 확인한다.

시나리오 2: 임시 저장 데이터 복원 검증
-
자동 저장이 완료된 상태에서 페이지를 새로고침(F5)한다.
-
페이지가 로드되자마자 "임시 저장된 글이 있습니다. 불러오시겠습니까?" 라는 브라우저 확인(confirm) 창이 나타나는 것을 확인한다.
-
'확인' 버튼을 클릭하자, 이전에 입력했던 제목과 본문 내용이 에디터에 완벽하게 복원되는 것을 확인한다.

시나리오 3: 임시 저장 데이터 삭제 검증
- 복원된 내용으로 '글 저장하기' 버튼을 클릭하여 게시물 발행을 완료한다.
- 게시물 상세 페이지로 성공적으로 이동한 후, 다시 개발자 도구의
Local Storage탭을 확인한다. post-autosave키가 깨끗하게 삭제된 것을 확인한다.

4 . 다시 /posts/new 페이지로 이동했을 때, 더 이상 임시 저장 데이터를 불러오는 확인 창이 나타나지 않는 것을 최종 확인한다.
6. 결론
이제 카페에서 글을 작성하다가 노트북 화면을 닫고, 로그인 세션이 만료되어도 AutoSave 기능을 통해 글을 통째로 날리는 일은 없어졌다. 또한 네트워크가 끊기거나 브라우저가 갑자기 종료되어도, 다시 글쓰기 페이지에 접속하면
confirm창을 통해 작업하던 내용을 안전하게 복원할 수 있게 되었다.
마음 놓고 글쓰기 작업에 집중할 수 있는 환경을 완벽히 마련하였다! 😀
