왜 또 하나의 '메모 앱'을 만들었냐면
들어가며 책을 읽다가 좋은 문장을 만나면 나는 항상 똑같은 짓을 한다. 사진을 찍는다. 그리고 그 사진은 카메라 롤 어딘가에서 영원히 묻힌다. (지난 몇 년치 카메라 롤을 뒤지면 내 인생 문장 몇백 개가 잠들어 있을 거다. 다시는 못 찾는다는 게 함정이지만.) 그래서 앱을 하나 만들기로…
들어가며
책을 읽다가 좋은 문장을 만나면 나는 항상 똑같은 짓을 한다. 사진을 찍는다. 그리고 그 사진은 카메라 롤 어딘가에서 영원히 묻힌다. (지난 몇 년치 카메라 롤을 뒤지면 내 인생 문장 몇백 개가 잠들어 있을 거다. 다시는 못 찾는다는 게 함정이지만.)
그래서 앱을 하나 만들기로 했다. 이름은 Quote. 이름부터 창의성이 좀 부족하다는 건 나도 안다. 하지만 이름 짓는 데 3일을 쓸 바엔 코드를 3일 더 쓰는 게 낫다고 판단했다. (이게 얼마나 순진한 판단이었는지는 나중에 다시 나온다. 여러 번 나온다.)
뭘 만들었나
한 줄로 하면 이렇다. 책 읽다가 좋은 문장이 나오면, 카메라로 찍고, OCR이 그 문장을 텍스트로 뽑아주고, 그걸 내 서재에 저장하는 앱.
말은 쉬운데 조건이 세 개 붙는다.
- 오프라인에서도 일단 저장은 돼야 한다. 지하철에서 책 읽는 사람한테 "와이파이 잡힐 때까지 기다리세요"라고 할 순 없으니까.
- 로그인하면 여러 기기에서 동기화가 돼야 한다. 폰에서 저장한 문장을 나중에 태블릿에서도 보고 싶은 게 사람 마음이다.
- iOS, Android, 웹 세 군데서 다 돌아가야 한다. 나 혼자 만드는데 세 개 앱을 따로 만들 재간은 없다.
셋 다 "당연히 되겠지" 하고 넘어가기 딱 좋은 조건들이다. 실제로는 이 세 줄이 이후 몇 달의 삽질을 예약해놓은 셈이었는데, 그건 아직 모르고 있었다.
기획은 생각보다 오래 걸렸다
사실대로 말하면 이 앱, 코드보다 기획에 시간을 더 썼다. 회사에서 문서 쓰는 게 일인 사람이라 그런지, 뭘 만들기 전에 PRD 비슷한 걸 혼자 쓰는 버릇이 있다. (아무도 안 시켰는데 혼자 이러고 있으면 좀 웃기다. 리뷰해줄 팀장도 없는데 말이다.)
문제는 이걸 한 번에 못 썼다는 거다. 썼다가 지우고, 다시 썼다가 또 지웠다. 오프라인/동기화/멀티플랫폼이라는 조건 세 개를 어떤 순서로 풀 것인지, 로그인은 언제 넣을지, OCR을 네이티브로 할지 웹으로 할지 — 이런 걸 정리하다가 며칠이 그냥 지나갔다. "이 정도면 됐다" 싶어서 코드를 열었다가, 다시 문서로 돌아가서 처음부터 고쳐 쓴 게 한두 번이 아니다.
지나고 보니 이 시간이 아깝지 않았다. 코드는 지우고 다시 짜면 그만이지만, 뭘 만들지 안 정하고 코드부터 짜면 그 코드를 통째로 지우고 다시 짜야 하는 일이 훨씬 자주 생긴다는 걸 이제는 안다. (이걸 몸으로 배우는 데 회사에서는 몇 년이 걸렸는데, 사이드 프로젝트에서는 며칠이면 충분했다.)
스택은 왜 이렇게 골랐나
Vite, React, TypeScript, Tailwind, shadcn-ui, Capacitor, Supabase, IndexedDB. 이렇게 나열해놓고 보니 채용 공고 기술 스택 칸 같다. (실은 이 리스트, 최근에 이력서에도 거의 그대로 붙여넣었다.)
참고로 이 리스트, 순도 100% 내 판단은 아니다. 몇 개는 커서(Cursor)의 컴포저한테 물어보고 정했다. "이 정도 규모 앱이면 상태 관리를 따로 둬야 하나, 그냥 Context로 될까" 같은 질문을 던지고 답을 들으면서 후보를 좁혔다. 최종 결정은 내가 내렸다고 우기고 싶지만, 고민의 상당 부분을 대신 해준 건 인정해야겠다.
이유는 단순하다. 혼자 만드니까, 코드베이스 하나로 iOS/Android/웹을 다 커버하는 게 제일 중요했다. Capacitor는 웹 개발자가 스위프트나 코틀린을 새로 안 배우고도 네이티브 앱을 만들 수 있게 해주는 물건이다. 브라우저 안의 앱을 껍데기만 씌워서 앱스토어에 올리는 셈인데, 처음엔 "이게 되나?" 싶었고 지금도 반쯤은 신기하다.
Supabase는 좀 더 솔직히 말하면, 백엔드 서버를 직접 관리하기 싫어서 고른 것도 있다. Postgres, 인증, 스토리지가 한 세트로 들어있는데, "Firebase냐 Supabase냐"를 고민하는 시간 자체가 사실 진짜 어려운 결정을 미루는 도피처였다. 진짜 어려운 건 따로 있었다. 오프라인에서 저장한 데이터를 나중에 서버랑 어떻게 맞출 것인가. 이건 그냥 라이브러리 하나 고른다고 풀리는 문제가 아니다. (다음 편 예고 같은 문장인데, 실제로 다음 편 예고 맞다.)
로그인은 이메일 매직링크 하나만 붙였다. 심플한 UX를 추구해서... 라고 말하고 싶지만, 회원가입 폼 만들기가 귀찮았던 진심이 한 70%쯤 섞여 있다. 비밀번호 찾기 플로우 안 만들어도 된다는 게 생각보다 큰 해방감을 줬다.
그래서 나는 뭘 얻으려던 거냐
명분은 있었다. "좋은 문장을 안 잃어버리는 앱이 필요하다." 틀린 말은 아니다.
근데 솔직히 명분과 실리를 분리해서 보면, 내가 진짜 얻고 싶었던 건 따로 있었다. 회사에서는 기획하고, 설득하고, 남의 승인을 받아야 뭔가가 세상에 나간다. 그런데 이건 내가 코드를 짜서, 내가 버튼을 누르면, 그날 밤 안에 내 폰에 앱이 깔린다. 이 즉각성이 좋았다. 퇴근하고 저녁에 짜는 코드가 낮에 하는 일보다 재밌다는 건 좀 민망하지만 사실이다.
마치며
지금까지는 계획대로 흘러갔다. Capacitor 얹고, Supabase 붙이고, 로그인 만들었다. 여기까지만 보면 꽤 순조로운 시작처럼 보인다.
문제는 다음 편이다. "오프라인에서도 일단 저장은 돼야 한다"는 저 위의 조건 1번, 그거 만들다가 한동안 고생을 좀 했다. 말로는 한 줄이지만 실제로는 로컬 저장소와 서버 사이에서 뭐가 최신 데이터인지 정하는 문제, 즉 동기화 엔진을 통째로 하나 만들어야 하는 일이었다. 이게 앱 개발에서 제일 골치 아픈 축에 든다는 걸, 그때는 아직 몰랐다.