AI로 나만의 노트 앱 만들기 (2) 실제 웹서비스로 확장하기

브라우저에만 저장되던 개인용 노트 앱에 Cloudflare와 Firebase를 연결했다. 데이터베이스, 인증, 권한, 자동 배포와 동기화를 적용하며 운영 가능한 웹 앱으로 확장한 과정을 정리했다.

Share

1부에서 만든 것은 브라우저에서 실행되는 개인용 노트 도구였다.

AI로 나만의 노트 앱 만들기 (1) 브라우저 프로토타입
AI와 함께 사람 중심의 개인 노트 앱을 만들기 시작했다. HTML, CSS, JavaScript만으로 브라우저 프로토타입을 만들고, 반복적인 프롬프트 수정과 실제 사용을 통해 기능과 디자인을 다듬으며 얻은 경험을 정리했다.

연락처와 노트를 추가하고 수정하는 핵심 기능은 동작했지만, 아직은 프로토타입에 가까웠다. 브라우저의 로컬 데이터에 의존했고 다른 기기에서 같은 내용을 보기도 어려웠다.

다른 기기에서도 같은 노트를 사용하고 싶었고, 필요하면 팀원과 함께 사용할 수 있는 형태로 확장하고 싶었다. 데이터를 서버와 데이터베이스에 저장하고, 인증을 붙이고, 실제 URL로 배포할 수 있는 한 단계 더 완성된 앱을 만들어보고 싶었다.

처음에는 이 변화를 아주 간단하게 요청했다. 오히려 핵심 목표만 전달하면 AI가 알아서 잘 구현해주지 않을까 기대했다.

이제 이 노트를 여러 명이 같이 쓸 수 있게 해줘.

하지만 내가 의도한 것은 회사 계정으로 접근을 제어하는 사내용 서비스였는데, Codex는 일반적인 공동 편집 기능으로 이해해 구현했다.

공동 편집 가능한 워크스페이스 기능을 추가했습니다.
- 공유 버튼으로 현재 노트를 공유 링크가 있는 팀 공간으로 전환
- 링크를 연 사람은 노트·연락처·목록을 함께 수정
- 변경 사항은 자동 저장되고 약 3초마다 동기화
- 동시 수정 시 최신 변경을 기준으로 병합하고 상태를 표시
- Cloudflare Worker + D1 저장소와 마이그레이션 구성 추가

Codex가 코드 작성 후 알려준 내용

원하는 방향이 아니라는 것을 깨닫고, GPT와 대화하면서 요구사항을 다시 정리했다. 이번에는 구현 순서까지 함께 지정했다.

현재 1인용 노트 앱을 여러 팀원이 함께 사용하는 사내용 웹 앱으로 전환해줘.

기술 구성:

- 프론트엔드 및 배포: Cloudflare Pages
- API: Cloudflare Workers
- 데이터베이스: Cloudflare D1
- 인증: Firebase Authentication의 Google 로그인
- 허용된 회사 계정만 접속 가능

핵심 요구사항:

- 로그인한 팀원은 같은 노트와 연락처를 함께 조회·추가·수정할 수 있어야 함
- 노트와 연락처에 생성자, 최근 수정자, 생성일, 수정일을 기록
- 생성·수정·삭제·연락처 연결 변경 이력을 별도 테이블에 기록
- 사용자 식별 정보는 프론트에서 전달받지 말고 Worker가 검증한 Firebase 토큰에서 가져올 것
- 삭제는 soft delete로 처리
- 역할은 admin, member로 구분
- admin은 사용자 활성화 및 권한 관리 가능
- member는 노트와 연락처 조회·추가·수정 가능
- 기존 앱의 화면과 기능은 최대한 유지
- 동작하지 않는 임시 UI는 만들지 말 것
- 비밀키와 환경별 설정은 코드나 Git에 직접 포함하지 말 것

아래 순서대로 구현하고, 각 단계가 동작하는 것을 확인한 후 다음 단계로 진행해줘.

1. 현재 코드 구조와 기존 데이터 저장 방식을 분석
2. D1 데이터 모델과 migration 작성
3. Workers CRUD API 구현
4. 기존 프론트엔드를 API 기반으로 변경
5. 로컬 환경에서 CRUD 동작 확인
6. Cloudflare Pages, Workers, D1 배포 설정
7. Firebase Google 로그인 추가
8. Worker에서 Firebase ID Token 검증
9. 회사 계정 및 D1 members 테이블 기반 접근 제어
10. 생성자·수정자·변경 이력 표시
11. 기존 데이터가 있다면 D1으로 이전할 방법 제공
12. 설정 및 배포 방법을 README에 작성

먼저 전체를 한 번에 수정하지 말고, 현재 구조를 분석한 뒤 구현 계획과 변경될 파일 목록을 제시해줘. 이후 실제 구현까지 진행해줘.

한 번의 요청으로 구현이 끝나지는 않았다. HTML, CSS, JavaScript 화면을 수정하는 것과 인증, 서버, 데이터베이스, 배포 환경을 연결하는 일은 완전히 다른 문제였다. 이때부터 프로젝트의 난도가 크게 올라갔다.

Cloudflare와 Firebase를 연결하다

기술 구성은 다음과 같았다.

  • Cloudflare Pages: 웹 화면 배포
  • Cloudflare Workers: API
  • Cloudflare D1: 데이터베이스
  • Firebase Authentication: Google 로그인
  • GitHub: 코드 관리와 자동 배포

로컬 브라우저에만 저장되던 연락처와 노트를 D1에 저장하도록 바꿨다. 프론트엔드는 Workers API를 호출하고, Worker는 Firebase 토큰을 검증한 뒤 요청을 처리하도록 했다.

또한 생성자, 최근 수정자, 생성일과 수정일을 기록하고 삭제는 바로 지우지 않고 soft delete로 처리하도록 했다.

구조 자체는 이해할 수 있었지만 실제 환경을 설정하는 과정에서는 오류가 계속 발생했다. Firebase 설정값을 어디에서 찾아야 하는지, Cloudflare Variables와 Secrets는 어떻게 구분하는지, 로컬에서는 어떤 값을 사용해야 하는지 하나씩 확인해야 했다.

화면을 만드는 것보다 대시보드의 설정 하나를 맞추는 데 더 오래 걸리는 경우도 많았다.

로컬 테스트가 불편해졌다

서버와 인증을 붙인 뒤에는 작은 화면 수정도 번거로워졌다.

웹에 배포된 환경에서는 로그인이 필요했고 데이터도 D1에 저장됐다. 하지만 버튼 위치나 글자 크기 하나를 확인할 때마다 인증하고 원격 데이터를 사용하고 싶지는 않았다.

그래서 개발 편의를 위해 로컬 환경에서는 다른 방식으로 동작하도록 요청했다.

로컬 환경에서는 Firebase 인증 로그인 없이 바로 화면에 접근할 수 있게 해줘.

로컬 테스트에서는 데이터를 localStorage에 저장하게 해줘.

테스트는 편해졌지만 새로운 차이가 생겼다.

로컬에서는 localStorage 기반으로 동작했고, 실제 환경에서는 Workers API와 D1을 사용했다. 덕분에 화면 수정은 빨라졌지만 로컬에서는 잘 되는데 배포 환경에서만 인증이나 API 문제가 발생하는 경우도 생겼다.

개발을 편하게 만드는 설정이 실제 운영 환경과의 차이를 만들기도 했다.

인증 상태를 어떻게 보여줄 것인가

로그인 기능 자체는 동작했지만 새로고침할 때 사용자 경험이 어색했다.

저장된 세션을 확인하는 짧은 순간에도 로그인 화면이 먼저 나타나 순간 로그아웃된 것처럼 보였다.

로그인 화면

사용자는 실제로 로그아웃된 것인지, 인증 정보를 확인하는 중인지 알기 어려웠다.

세션을 확인하는 동안에는 로그인 화면이 아니라 로딩 상태를 보여주고, 토큰 갱신에 실패했을 때만 로그인 화면으로 이동해야 했다.

기능이 동작하는 것과 사용자에게 자연스럽게 보이는 것은 또 다른 문제였다.

Git과 비밀값

Cloudflare와 Firebase 설정이 늘어나면서 GitHub에 어떤 파일까지 올려도 되는지도 신경 써야 했다.

Firebase의 웹 클라이언트 설정은 브라우저에서 사용하는 값이었지만, 서비스 계정 키, Cloudflare API Token, 배포용 비밀값은 저장소에 포함하면 안 됐다.

.env, 로컬 데이터, 빌드 산출물과 키 파일이 .gitignore에 포함되어 있는지 직접 확인했다.

기능이 동작한다고 해서 안전한 것은 아니었다. 비밀값이 코드나 저장소에 포함되지 않았는지는 직접 확인해야 했다.

자동 배포도 별도의 기능이었다

웹은 GitHub에 커밋하면 Cloudflare Pages를 통해 자동으로 배포할 수 있었다.

하지만 Workers도 함께 자동으로 배포되는지는 별개의 문제였다.

커밋하고 웹이 배포될 때 Worker도 같이 자동으로 배포되게 하려면 어떻게 해야 할까?

GitHub Actions와 Cloudflare 설정을 연결하고, 배포용 토큰과 환경 변수를 설정했다.

처음에는 배포를 한 번 실행하는 명령 정도로 생각했지만, 실제로는 어떤 코드가 변경됐을 때 어떤 환경을 어떤 순서로 배포할 것인지 정하는 작업이었다.

새 노트 생성이 느려졌다

배포 후에는 새 노트를 생성하거나 페이지를 새로고침할 때 결과가 나타나기까지 시간이 걸렸다.

처음에는 사이트 전체가 느린 줄 알았다.

https://marketnote.pages.dev/ 이 사이트가 왜 이렇게 느릴까?

하지만 질문의 범위가 너무 넓어 원인을 정확히 좁히기 어려웠다.

그래서 새 노트 생성 문제 하나에 집중했다.

로컬 우선 구조를 고민하다

새 노트 생성 문제를 분석하다 보니 성능뿐 아니라 구조 자체도 다시 고민하게 되었다.

구현보다 먼저 운영 정책과 구조를 다시 정리해 보기로 했다.

새 노트 생성과 새로고침시 로딩 결과가 나오기까지 오래걸리는 이유를 파악하려면 어떻게 해야할까?

데스크탑 로컬이나 브라우저 환경에서 먼저 노트를 생성, 저장, 로드하고 나중에 클라우드에 싱크하는 방식 같이 구조를 바꾸는건 어떻게 생각하세요?

구조를 최대한 단순하게 가져가고 싶어요. 정책을 바꿔서라도. 예를 들면 "노트는 기본적으로 한사람이 작성하고 있을때 다른 사람은 편집할 수 없다." 같은 정책도 괜찮아요.

지금까지 논의한 내용을 바탕으로 할일 적어줘

cloudflare 쪽에 부담가지 않을까? 무료 플랜으로 충분하게 쓰고 싶어

audit log 너무 많이 보관할 필요 없어. 최종적으로 해야할일 정리해줘.

로컬 우선(Local First) 구조는 개발 편의를 위한 설정이 아니라 제품 구조 자체를 바꾸는 접근이었다.

사용자가 입력한 내용을 먼저 로컬에 저장하면 화면은 즉시 반응할 수 있다. 하지만 오프라인 상태, 여러 기기에서의 동시 수정, 삭제와 복구, 충돌 처리까지 고려해야 했다.

Cloudflare 무료 플랜으로 운영하는 것도 목표였기 때문에 저장량과 요청 수도 함께 고민했다.

Codex와 함께 해야 할 일을 정리하고 하나씩 검토했다.

## 할 일

### 정책

- [ ] 노트 작성자만 수정·삭제 가능
- [ ] 다른 사용자는 읽기 전용
- [ ] 관리자만 노트 소유자 변경 가능

### 로컬 우선 저장

- [ ] 브라우저: `localStorage` 대신 IndexedDB 사용
- [ ] 데스크톱: SQLite 기반 로컬 저장소 추가
- [ ] 로컬 DB에 노트·연락처·목록·동기화 cursor·outbox 저장
- [ ] 새 노트/수정/삭제를 먼저 로컬에 즉시 반영
- [ ] UI에 동기화 중·실패·오프라인 변경 개수 표시

### 동기화

- [ ] 클라이언트 로컬 DB에 outbox 구현
- [ ] 같은 노트의 미전송 수정은 마지막 변경 하나로 합치기
- [ ] 입력마다 서버 저장하지 않고, 2~5초 유휴 시·화면 이탈 시·앱 재연결 시 동기화
- [ ] 앱 시작 시 로컬 데이터를 먼저 표시
- [ ] 서버에는 마지막 동기화 cursor 이후 변경분만 요청하는 `/sync` API 추가
- [ ] 전체 `/bootstrap`은 최초 설치·복구 용도로만 유지
- [ ] 실패한 outbox 작업은 재시도하고 성공 시 제거

### Worker / D1 변경

- [ ] 노트 수정·삭제 API에 작성자 검증 추가
- [ ] 타인 수정 시 `403` 읽기 전용 응답 반환
- [ ] 관리자용 소유권 이전 API 추가
- [ ] 작업 ID를 받아 중복 동기화 요청도 안전하게 처리
- [ ] 매 API 호출의 `members` UPSERT 제거 또는 로그인/주기적 갱신으로 축소
- [ ] 목록 동기화에서는 본문 전체가 아닌 요약 데이터만 내려주기
- [ ] 삭제 항목은 기본 동기화에서 분리 또는 최소 정보만 전송

### 감사 로그

- [ ] 감사 로그 보존 기간: 7일
- [ ] 만료된 audit log를 정리하는 Cloudflare Cron Trigger 추가
- [ ] 본문 변경 이력은 필요 최소한만 저장: `before_json` 제거 또는 메타데이터만 기록
- [ ] 삭제·복구·소유권 이전 같은 중요 이벤트는 보존 기간 내 유지

### 성능 및 운영

- [ ] 새 노트 생성·동기화·초기 로드 시간을 측정하는 로그 추가
- [ ] D1의 일별 rows read/write, 저장소 사용량, Worker 요청 수 확인
- [ ] 노트·연락처 조회 시 반복 배열 탐색을 Map 기반 조회로 개선
- [ ] 오프라인 생성 → 재연결 → 동기화, 앱 종료 후 재시작, 타인 노트 편집 차단 시나리오 테스트

잘 안되는 부분이 있어서 마지막쯤에는 일부를 제외하기로 했다.

그런데 생각해보니까 이거 꼭 해야 할까?

"실제 브라우저/데스크톱 E2E 오프라인 시나리오 테스트와 Cloudflare 운영 지표 확인 자동화" 이거 둘다 버려도 되는거?

그럼 E2E 자동화만 남겨줘

기술적으로 구현할 수 있다는 사실과 지금 구현해야 한다는 판단은 달랐다. Codex가 추천해준 내용이라도 현재 프로젝트에 꼭 필요한 것만 남기기로 했다.

프로토타입을 운영 가능한 앱으로 만들면서

1부에서는 화면과 기능을 만드는 데 집중했다.

2부에서는 데이터 저장 방식, 인증, 권한, 배포, 비밀값 관리, 로컬 개발 환경까지 함께 고민해야 했다. 겉으로 보기에는 같은 노트 앱이었지만 내부 구조는 완전히 달라졌다.

특히 화면을 만드는 것보다 운영 환경을 구성하는 데 더 많은 시간을 쓰는 경우도 많았다. 설정 하나, 권한 하나, 환경 변수 하나가 서비스 전체에 영향을 줄 수 있다는 점을 직접 경험했다.

웹으로 배포하고 여러 기기에서 사용할 수 있게 되자 또 다른 불편도 보이기 시작했다. 브라우저 단축키의 제약이 있었고, 여러 창에서 수정한 내용이 바로 반영되지 않는 경우도 있었다. 설치된 앱처럼 실행하고 자동으로 업데이트할 수 있으면 좋겠다는 생각도 들었다.

다음 글에서는 이 프로젝트를 Tauri 기반의 macOS 데스크톱 앱으로 확장하면서 로그인, 동기화, 빌드, 자동 업데이트를 적용하는 과정을 정리해보려고 한다.

AI로 나만의 노트 앱 만들기 (3) 데스크탑 앱으로 확장
웹 노트 앱을 Tauri 기반 macOS 데스크톱 앱으로 확장했다. 로그인, 단축키, 실시간 동기화, DMG 배포와 자동 업데이트를 구현하며 웹과 데스크톱의 차이를 경험한 과정을 정리했다.