바이브코딩으로 체크리스트나 메모 앱을 처음 만들면, 꽤 자주 같은 장면을 만납니다. 입력도 되고, 목록도 잘 보이고, 삭제도 됩니다. 그래서 속으로 생각합니다. “이제 됐다.” 그런데 새로고침을 하는 순간 방금 넣은 내용이 전부 사라집니다.

초보자 입장에서는 여기서 조금 허탈해집니다. 분명 아까까지는 잘 돌아갔는데, 왜 갑자기 처음 상태로 돌아갔는지 감이 잘 안 잡히기 때문입니다. 많은 경우 이건 기능이 망가진 문제가 아니라, 저장이 아직 붙지 않은 상태입니다. 다시 말해, 화면에서는 움직였지만 그 결과를 어딘가에 남겨두지는 못한 것입니다.

이 지점을 이해하면 바이브코딩이 한 단계 더 선명해집니다. “돌아가는 화면”과 “값이 유지되는 앱”은 같은 것이 아닙니다. 그리고 이 차이를 처음 배우기에 가장 가벼운 저장 방식이 바로 localStorage입니다. 이번 글에서는 왜 데이터가 사라지는지부터 시작해서, localStorage를 어떻게 이해하면 좋은지, 코드에는 어떤 순서로 붙는지, 어디까지는 충분하고 어디서부터는 부족한지까지 차근차근 정리하겠습니다.

이번 글의 핵심
새로고침 후 데이터가 사라진다는 건 대개 기능이 없는 게 아니라, 저장이 아직 없는 상태라는 뜻에 가깝다.


왜 새로고침하면 데이터가 사라질까

여기서 먼저 잡아야 할 개념은 브라우저 안에서 잠깐 살아 있는 값어딘가에 저장된 값의 차이입니다. 초보자가 처음 만드는 앱은 보통 JavaScript 변수나 배열에 데이터를 넣고 화면에 보여주는 방식으로 시작합니다. 이 단계에서는 눈앞에서 잘 작동합니다. 문제는 그 값이 브라우저 탭이 살아 있는 동안만 유지되는 경우가 많다는 점입니다.

예를 들어 아래처럼 할 일 목록을 배열에 담았다고 해보겠습니다.

let todos = [];

todos.push('장보기');
todos.push('운동하기');

이 코드는 그 순간에는 잘 동작합니다. 화면에도 목록이 보일 수 있습니다. 하지만 이 배열은 현재 열린 페이지의 실행 상태 안에만 존재하는 값입니다. 새로고침을 하면 페이지가 다시 시작되고, JavaScript도 처음부터 다시 실행됩니다. 그러면 let todos = [];가 다시 실행되면서 이전 값은 사라집니다.

상황 코드 입장에서 무슨 일이 일어나는가 사용자 눈에는 어떻게 보이는가
페이지를 처음 열었을 때 변수와 함수가 새로 만들어진다 빈 화면 또는 초기 상태가 보인다
값을 입력하고 버튼을 눌렀을 때 배열이나 변수 값이 바뀐다 목록이 생기고 화면이 바뀐다
새로고침했을 때 기존 실행 상태가 사라지고 처음부터 다시 시작된다 방금 넣은 데이터가 사라진 것처럼 보인다

이걸 이해하면 새로고침 문제는 갑자기 덜 신비롭게 느껴집니다. 앱이 나빠서가 아니라, 기억은 했지만 기록은 하지 않은 상태였던 것입니다. 입문자에게 여기서 필요한 건 거창한 데이터베이스 지식이 아니라, “값을 화면에만 올려두는 것”과 “브라우저 어딘가에 남겨두는 것”이 다르다는 감각입니다.

한 줄로 정리하면
변수에 들어간 값은 실행 중인 페이지가 기억하는 값이고, 저장된 값은 페이지를 다시 열어도 다시 불러올 수 있는 값이다.


저장되지 않은 값과 저장된 값은 무엇이 다를까

초보자는 저장이라고 하면 곧바로 서버나 데이터베이스를 떠올리기 쉽습니다. 물론 언젠가는 그쪽으로 가게 됩니다. 하지만 첫 프로젝트 단계에서는 그보다 훨씬 가벼운 저장 방식이 있습니다. 먼저 전체 그림을 가볍게 보는 편이 이해가 빠릅니다.

방식 새로고침 후 유지 브라우저를 닫아도 유지 다른 기기에서도 같은 값 사용 입문자 난도 주로 쓰는 상황
변수/배열 아니오 아니오 아니오 가장 쉬움 화면 동작 테스트, 임시 상태
sessionStorage 대체로 예 보통 아니오 아니오 쉬운 편 탭이 열려 있는 동안만 유지할 값
localStorage 아니오 초보자에게 적합 간단한 개인용 저장, 설정값, 작은 목록
서버/데이터베이스 상대적으로 어려움 실제 서비스, 로그인, 공유 기능, 여러 기기 동기화

이 표에서 초보자가 먼저 주목할 부분은 localStorage입니다. 새로고침해도 남고, 브라우저를 닫았다가 다시 열어도 남습니다. 그런데 서버를 만들 필요는 없습니다. 즉, 저장이라는 개념을 처음 체험하기에는 부담이 가장 낮은 편입니다.

물론 이 방식도 만능은 아닙니다. 같은 브라우저 안에 남는 저장이기 때문에, 다른 사람과 공유되거나 다른 기기와 자동으로 동기화되지는 않습니다. 그래도 처음 체크리스트, 메모, 설정값 저장, 다크 모드 여부 기억, 최근 입력값 유지 같은 기능을 붙일 때는 꽤 유용합니다.

여기서 중요한 포인트는 하나입니다. 저장은 한 종류가 아닙니다. 그래서 처음부터 데이터베이스로 뛰어들 필요도 없고, 반대로 localStorage 하나로 모든 문제를 해결하려고 해도 안 됩니다. 첫 단계에서는 지금 만드는 프로젝트가 어느 수준의 저장을 요구하는지를 구분하는 감각이 더 중요합니다.


초보자에게 가장 먼저 맞는 저장 방식, localStorage

localStorage는 브라우저 안에 간단한 값을 저장해두는 공간입니다. 이름만 보면 어려워 보이지만, 실제 느낌은 그리 복잡하지 않습니다. 내가 만든 웹앱이 “이 브라우저 안에서 다시 열렸을 때 기억해야 할 값”을 남겨두는 작은 서랍 정도로 생각하면 됩니다.

다만 여기에는 꼭 같이 알아야 할 특징이 있습니다. localStorage는 편하지만, 어디까지나 브라우저 안의 가벼운 저장소입니다. 그래서 아래처럼 생각하면 이해가 쉽습니다.

  • 잘 맞는 경우
    개인용 체크리스트, 간단한 메모, 최근 검색어, 다크 모드 설정, 폼 임시 저장처럼 작은 데이터
  • 잘 안 맞는 경우
    여러 기기에서 같은 값을 봐야 하는 서비스, 팀 공유, 중요한 사용자 데이터, 민감한 정보 저장

또 하나 중요한 특징이 있습니다. localStorage에는 값을 그대로 배열이나 객체 형태로 넣는 것이 아니라, 문자열 형태로 저장합니다. 여기서 초보자가 처음 만나는 개념이 JSON.stringifyJSON.parse입니다.

코드 쉽게 말하면 왜 필요한가
JSON.stringify() 배열이나 객체를 문자열로 바꾼다 localStorage는 문자열로 저장하기 때문이다
JSON.parse() 문자열을 다시 배열이나 객체로 돌린다 저장해둔 값을 다시 코드에서 다루기 위해 필요하다

이 부분에서 막히는 이유는 개념이 어려워서라기보다, 눈에 잘 안 보이기 때문입니다. 배열은 배열처럼 보이는데 왜 굳이 문자열로 바꾸나 싶습니다. 그런데 브라우저 저장소는 그 배열 자체를 바로 이해하지 못합니다. 그래서 저장할 때는 문자열로 바꾸고, 꺼낼 때는 다시 원래 형태로 되돌리는 과정이 필요합니다.

주의할 점
localStorage는 편한 첫 저장 방식이지만, 비밀번호나 결제 정보처럼 민감한 데이터를 가볍게 넣어두는 용도로 이해하면 곤란하다. 입문 단계에서는 개인용 간단 데이터 저장 정도로 생각하는 편이 안전하다.


localStorage는 코드에 어떻게 붙을까

처음 localStorage를 붙일 때 흐름은 생각보다 단순합니다. 크게 보면 두 순간만 기억하면 됩니다. 페이지를 열었을 때 읽어오기, 그리고 데이터가 바뀌었을 때 다시 저장하기입니다.

시점 해야 할 일 핵심 코드
페이지가 처음 열릴 때 저장된 값을 가져와서 화면에 보여준다 getItem + JSON.parse
항목이 추가, 수정, 삭제될 때 바뀐 값을 다시 저장한다 setItem + JSON.stringify

체크리스트를 예로 들면 흐름은 이렇습니다.

  1. 앱이 시작될 때 localStorage에서 기존 목록을 읽는다.
  2. 읽어온 값을 배열에 넣는다.
  3. 그 배열을 바탕으로 화면을 그린다.
  4. 사용자가 새 항목을 추가하거나 삭제하면 배열이 바뀐다.
  5. 바뀐 배열을 다시 localStorage에 저장한다.
  6. 다음에 새로고침해도 그 저장된 배열을 다시 불러온다.

아래는 아주 단순한 예시입니다. 입문자가 저장 흐름을 이해하는 데 초점을 맞춘 코드입니다.

<input id="todoInput" type="text" placeholder="할 일을 입력하세요" />

추가
    
    const STORAGE_KEY = 'vibe-todo-items';
    const input = document.getElementById('todoInput');
    const addBtn = document.getElementById('addBtn');
    const todoList = document.getElementById('todoList');
    
    let todos = [];
    
    function saveTodos() {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
    }
    
    function loadTodos() {
    const saved = localStorage.getItem(STORAGE_KEY);
    todos = saved ? JSON.parse(saved) : [];
    }
    
    function renderTodos() {
    todoList.innerHTML = '';
    
    todos.forEach((todo) => {
    const li = document.createElement('li');
    li.textContent = todo.text + ' ';
    
    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = '삭제';
    
    deleteBtn.addEventListener('click', () =&gt; {
      todos = todos.filter((item) =&gt; item.id !== todo.id);
      saveTodos();
      renderTodos();
    });
    
    li.appendChild(deleteBtn);
    todoList.appendChild(li);
    
    });
    }
    
    addBtn.addEventListener('click', () => {
    const text = input.value.trim();
    if (!text) return;
    
    todos.push({
    id: Date.now(),
    text: text
    });
    
    input.value = '';
    saveTodos();
    renderTodos();
    });
    
    loadTodos();
    renderTodos();
    

    이 코드에서 초보자가 꼭 눈여겨볼 부분은 세 군데입니다.

    • saveTodos()
      배열을 문자열로 바꿔서 저장하는 구간
    • loadTodos()
      저장된 문자열을 다시 배열로 되돌리는 구간
    • loadTodos(); renderTodos();
      페이지가 시작될 때 예전 데이터를 다시 화면에 올리는 구간

    여기서 많은 초보자가 놓치는 건 “저장만 하면 끝”이라고 생각하는 부분입니다. 하지만 실제로는 읽기와 그리기까지 같이 붙어야 합니다. 값을 저장해뒀더라도, 페이지를 열었을 때 그 값을 다시 읽어서 화면에 그리지 않으면 사용자 눈에는 여전히 빈 상태처럼 보이기 때문입니다.


    초보자가 자주 꼬이는 실수 6가지

    localStorage를 처음 붙일 때는 아주 비슷한 곳에서 자주 막힙니다. 아래 실수들은 바이브코딩으로 코드를 빠르게 만들수록 더 자주 보입니다. 코드가 길어 보이는데도 저장이 안 되는 이유가 대부분 이 안에 들어 있습니다.

    실수 겉으로 보이는 증상 실제로 빠진 부분
    저장은 했는데 불러오지 않음 새로고침 후 빈 화면처럼 보인다 앱 시작 시 getItemrender를 하지 않았다
    불러오기는 했는데 저장을 안 함 실행 중에는 보이지만 새로고침하면 사라진다 추가, 수정, 삭제 뒤 setItem이 빠졌다
    JSON.stringify를 빼먹음 저장값이 이상하게 보이거나 제대로 안 읽힌다 배열이나 객체를 문자열로 바꾸지 않았다
    JSON.parse를 빼먹음 불러온 값이 문자열이라 배열처럼 다루지 못한다 읽은 뒤 다시 원래 형태로 바꾸지 않았다
    저장 키 이름이 다름 분명 저장했는데 못 찾는다 setItemgetItem에서 다른 키를 썼다
    화면만 바꾸고 데이터 배열은 안 바꿈 그 순간에는 지워진 것 같지만 새로고침하면 다시 나타난다 실제 데이터가 아니라 화면만 수정했다

    이 중에서도 특히 마지막 실수는 초보자가 정말 자주 겪습니다. 예를 들어 삭제 버튼을 눌렀을 때 화면에서 목록 항목만 지우고, 정작 todos 배열에서는 그 값을 제거하지 않으면 그 순간에는 된 것처럼 보입니다. 하지만 저장된 데이터는 그대로 남아 있으니 새로고침하면 다시 나타납니다.

    즉, 저장 기능을 붙일 때는 항상 아래 순서를 같이 떠올리는 편이 좋습니다.

    1. 실제 데이터 배열이 바뀌었는가
    2. 바뀐 배열을 다시 저장했는가
    3. 그 바뀐 값을 기준으로 화면을 다시 그렸는가

    실전에서 가장 많이 헷갈리는 부분
    눈앞의 화면을 바꾸는 것과, 실제 저장할 데이터를 바꾸는 것은 같은 일이 아니다. 이 둘이 어긋나면 저장 문제는 거의 반드시 다시 터진다.


    어디까지는 localStorage로 충분하고, 어디서부터는 부족할까

    localStorage는 입문자에게 정말 유용합니다. 하지만 그 유용함이 모든 프로젝트에 그대로 이어지지는 않습니다. 여기서 선을 잘 그어두면, “왜 내 앱은 여기서 더 안 나가지?” 하는 답답함을 줄일 수 있습니다.

    상황 localStorage로 충분한 편 다른 방식이 필요한 경우
    개인 체크리스트 충분한 편 여러 기기 동기화가 필요하면 부족하다
    개인 메모, 최근 검색어 잘 맞는다 공유, 협업 기능이 붙으면 한계가 온다
    다크 모드 설정, UI 설정 저장 아주 잘 맞는다 특별한 서버 저장이 없어도 된다
    회원가입, 로그인 기반 서비스 부족하다 서버와 데이터베이스가 사실상 필요하다
    팀 공유, 여러 사람 동시 사용 부족하다 브라우저 한 곳에만 저장되는 방식으로는 어렵다
    큰 파일, 중요한 사용자 정보 권장하기 어렵다 안정성과 보안 관점에서 다른 구조가 필요하다

    결국 localStorage는 저장 개념을 처음 이해하기에 아주 좋은 첫 단계입니다. 새로고침 문제를 해결하고, 앱이 이전 상태를 기억하게 만들고, 사용자 입장에서 “진짜 도구 같다”는 느낌을 주는 데 충분합니다. 하지만 서비스가 커지거나, 여러 사람이 함께 쓰거나, 로그인과 공유가 붙기 시작하면 그다음 단계로 넘어가야 합니다.

    이 차이를 미리 알고 있으면 도움이 큽니다. 처음에는 localStorage만으로도 충분히 배울 수 있습니다. 다만 거기서 한 번 성공했다고 해서, 바로 실제 서비스 수준의 저장까지 한 번에 되는 것은 아닙니다. 입문 단계에서는 이 구분을 이해하는 것 자체가 꽤 큰 진전입니다.

    핵심 감각
    localStorage는 “가볍게 저장을 체험하는 첫 단계”로는 훌륭하지만, “여러 사람과 여러 기기에서 쓰는 서비스의 저장 방식”까지 대신해주지는 않는다.


    마무리

    새로고침했더니 데이터가 사라지는 문제는 초보자에게 꽤 충격적입니다. 하지만 이 문제를 한 번 이해하고 나면, 바이브코딩이 조금 다르게 보이기 시작합니다. 눈앞에서 돌아가는 화면만 만드는 것과, 상태를 기억하는 도구를 만드는 것은 분명히 다른 단계이기 때문입니다.


    이번 글에서 꼭 가져가면 좋은 건 세 가지입니다.
    첫째, 변수에 들어간 값은 실행 중인 페이지의 기억일 뿐이라는 점.
    둘째, localStorage는 그 기억을 브라우저 안에 남겨두는 가장 가벼운 저장 방식이라는 점.
    셋째, 저장은 바뀔 때 저장하고, 시작할 때 다시 읽어와서 화면에 그리는 흐름까지 포함해야 완성된다는 점입니다.


    여기까지 이해했다면 이제 저장은 막연한 주제가 아닙니다. 적어도 “왜 사라지는지”는 설명할 수 있게 됩니다. 그리고 이 설명이 가능해지는 순간부터, AI에게도 훨씬 덜 흔들리게 요청할 수 있습니다.

    다음 편에서는 저장한 데이터를 수정하고 삭제할 때 왜 꼬이기 쉬운지, 그리고 이때 왜 id가 필요해지는지를 본격적으로 다루겠습니다.