bible-viewer
개역한글·개역개정을 나란히 놓고 읽는 성경 뷰어
개인기획 · 개발 · 배포 단독2026.06 — 현재
ReactTypeScriptViteTailwindFirebaseFuse.js
왜 만들었나
개역한글과 개역개정을 같이 펴놓고 대조하는 일이 잦은데, 기존 앱들은 번역본 전환이 탭 이동이었다. 동시에 보려면 창을 두 개 띄워야 했다. 두 본문을 같은 절 기준으로 나란히 놓는 뷰어가 필요했다.
내가 한 역할
기획부터 배포까지 단독. 본문 데이터 파이프라인 설계, 검색 인덱싱, 칼럼 레이아웃, Firestore 동기화까지 전부.
데이터 파이프라인
두 역본의 출처가 서로 달라서 수집 방식도 나뉜다. 개역한글은 Bolls.life 가 절 번호까지 명시된 JSON API 로 제공하지만, 개역개정은 Bolls 에 없어서 holybible.or.kr 을 EUC-KR HTML 그대로 크롤링해 <ol start> 값으로 절 번호를 직접 파싱해야 했다. 두 역본 모두 같은 개역 계열 versification 을 공유해서 절 정렬은 대부분 일치하지만, 개역개정이 개역한글의 일부 절을 합치는 경우가 있어 특정 절들은 빈 문자열로 채워 참조 키만 맞춰줬다.
66권·1,189장 전체를 스크레이핑한 뒤 장 단위로 캐시해 중단해도 이어받을 수 있게 했고, 별도 검증 스크립트로 권 수·장 수·절 수·두 역본 간 정렬이 어긋나지 않았는지 자동으로 확인한다.
배운 점
30MB 짜리 성경 본문 JSON 을 resolveJsonModule 로 import 하면 tsc 가 heap OOM 으로 죽는다.
타입 체커가 JSON 리터럴 전체를 타입으로 펼치기 때문이다. ?url 로 경로만 받아 런타임에 fetch 하는 쪽으로 바꿨다.
번역본을 정적 JSON 으로 둘지 Firestore 에 넣을지도 오래 고민했다. 본문은 불변이고 용량이 크니 정적 JSON, 사용자별 상태(북마크·읽은 위치)만 Firestore 로 갈랐다. Firestore 의 last-write-wins 특성 때문에 삭제를 tombstone 으로 처리하지 않으면 다른 기기의 오래된 쓰기가 삭제를 되살린다는 것도 이때 알았다.
기술적으로 재밌었던 것
칼럼 순서 변경을 dnd-kit 없이 Pointer Events 만으로 구현했다.
행 기반 그리드라 “칼럼” 이 단일 DOM 노드가 아닌 게 문제였는데,
투명한 셀 뒤에 absolute 기둥 카드를 한 장 깔고 CSS 변수(--drag-x)를 조상에 직접 write 해서
매 pointermove 마다 리렌더가 0회 나도록 만들었다. 나머지 칼럼은 translateX(±pitch) 로 자리를 양보한다.
아이폰 홈스크린 아이콘 재배치와 같은 감각이 나온다.
아쉬운 점
브라우저 없이 통합 테스트를 돌리려고 Vite 의 ssrLoadModule 로 Node 에서 Firestore 로직을 실행했는데,
이 방식이 정직하긴 해도 실제 브라우저 환경의 IndexedDB persistence 는 여전히 커버하지 못한다.