개인 소설 서재를 다듬은 개발 기록

2026년 10월 24일 업데이트 — 현재는 VelinOne의 서재

이 글을 처음 쓸 때 설명했던 개인 소설 서재는 지금 VelinOne의 ‘서재’ 서비스로 통합했다. 현재 시작 경로는 /books이며 공통 로그인과 서재 이용 권한을 사용한다. 아래의 개발 기록은 당시 독립 서재를 만들며 정리한 내용이고 지금 이용하는 화면과 관리 방식은 이 업데이트를 기준으로 보면 된다.

현재 웹 서재에서는 내 도서와 공유받은 도서를 함께 보며 썸네일과 목록 보기를 전환할 수 있다. 제목과 작가뿐 아니라 독서 기록으로도 검색하고 분류별로 좁혀 볼 수 있다. 개별 도서와 연재를 구분하며 작품 화면에서 본문 또는 회차 목록을 거쳐 읽기로 이동한다. 예전 설명처럼 개별 도서를 저장하면 언제나 바로 리더로 이동하는 것으로 안내하지 않는다.

현재 VelinOne 서재 — 로컬 예시 도서
2026년 10월 24일 실제 로컬 실행 화면. 도서·계정은 설명용 예시이며 개인 서재 자료가 아니다.

위 이미지는 현재 로컬 앱을 격리된 임시 DB로 실행한 서재 화면이다. 책 세 권과 본문은 촬영을 위한 예시이며 개인 서재나 다른 사람의 작품을 공개한 것이 아니다. 이번에는 실제 앱의 도서 생성·로그인·목록 표시를 사용해 촬영했고 운영 계정의 자료는 읽지 않았다.

연재는 회차 관리에서 순서를 바꾸거나 선택한 회차를 개별 도서로 분리할 수 있다. ‘내 독서 기록’에서는 책갈피와 댓글을 모아 보고 해당 위치로 이동한다. 다른 회원과 작품을 공유하는 기능과 그룹 구성원에게 자료 소유권을 넘기는 기능도 서로 구분한다. 자료를 넘겼다는 이유로 개인별 읽기 위치나 책갈피까지 같은 값이 되는 것은 아니다.

기존 글에서 다룬 텍스트·PDF·만화·연재와 읽기 기록의 목적은 이어진다. 다만 과거 코드 구조와 화면 설명을 현재 서비스 구조로 오해하지 않도록 아래에는 당시의 개발 과정을 별도로 남긴다.


이전 개발 기록

아래는 최초 작성 당시의 개발 기록이다. 현재 기능과 화면은 위 업데이트 내용을 우선한다.

개인 소설 서재는 작품을 저장하는 기능만으로 완성되기 어렵습니다. 여러 형식의 콘텐츠를 편하게 등록하고, 연재 작품을 관리하며, 어떤 기기에서도 읽던 위치로 자연스럽게 돌아갈 수 있어야 합니다.

이번 개발은 그 흐름을 하나씩 연결하는 방향으로 진행됐습니다. 최근 Git 이력과 현재 구현을 살펴보니, 변화의 중심은 콘텐츠 수집, 작품 관리, 읽기 경험이라는 세 가지 축으로 정리할 수 있었습니다.

프론트엔드와 백엔드는 어떻게 나뉘어 있을까

프로젝트는 공개 웹 요청을 받는 프론트엔드와 실제 애플리케이션 로직을 담당하는 백엔드로 분리돼 있습니다. 두 영역 모두 별도의 README는 없었기 때문에, 진입점과 컨트롤러, 저장소 계층, 화면 템플릿, 최근 변경 이력을 함께 따라가며 구조를 확인했습니다.

브라우저 요청
  → 프론트엔드 진입점과 경로 분기
  → 백엔드 컨트롤러
  → 입력 검증과 서비스 로직
  → 데이터베이스 및 콘텐츠 저장소
  → 프론트엔드 화면 렌더링

프론트엔드는 로그인, 서재, 작품 등록, 연재 관리, 리더, 공유 설정, 브라우저 연동 설정 같은 요청을 구분합니다. 화면은 Tabler 기반 컴포넌트를 사용하며, 리더와 서재에는 별도의 CSS와 JavaScript가 적용됩니다.

백엔드는 PHP로 작성돼 있고 컨트롤러, 도메인 서비스, 입력 처리기, 저장소 클래스로 책임이 나뉩니다. 관계형 데이터베이스에는 사용자와 작품 메타데이터, 회차, 공유 관계, 읽기 진도가 저장됩니다. 본문과 이미지는 별도 콘텐츠 저장소에서 관리하고, 데이터베이스가 해당 콘텐츠를 참조하는 방식입니다.

최근 개발은 어떤 순서로 진행됐나

다양한 콘텐츠를 담을 수 있는 서재 만들기

초기 구현에서는 로그인과 사용자 관리, 완결본과 연재형 작품, 공유, 읽기 진도 저장을 포함한 기본 서재 구조가 마련됐습니다. 이후 입력 방식이 확장되면서 일반 텍스트뿐 아니라 PDF, 삽화가 포함된 본문, 여러 장의 만화 이미지, ZIP·CBZ 묶음도 등록할 수 있게 됐습니다.

완결본과 연재형은 저장 흐름도 다릅니다. 완결본은 본문을 등록하면 바로 리더로 이동하고, 연재형은 작품 공간을 먼저 만든 뒤 첫 회차 등록 화면으로 이어집니다. 한 화면에 모든 로직을 몰아넣기보다는 콘텐츠 종류별 입력 처리기가 파일 형식과 용량을 검증하도록 구성돼 있습니다.

작품이 늘어난 뒤를 고려한 관리 기능

작품 수가 많아지면 등록보다 정리가 더 중요해집니다. 최근 변경에서는 제목이나 작가 검색, 장르 필터, 여러 완결본을 하나의 목록으로 묶는 수동 그룹 기능이 추가됐습니다. 묶음을 만들거나 해제해도 원래 본문과 책갈피는 유지됩니다.

이미 등록한 완결본을 연재형 작품의 첫 회차로 전환하거나, 다른 연재 작품의 새 회차로 편입하는 기능도 마련됐습니다. 이 과정은 데이터베이스 트랜잭션 안에서 본문, 삽화, 읽기 진도를 함께 이동합니다. 공유 중인 작품은 기존 독자가 대상 연재 작품에도 접근할 수 있는지 확인한 뒤 편입하기 때문에, 변환 이후 갑자기 읽을 수 없게 되는 상황도 방지합니다.

설치 가능한 웹앱으로 확장하기

웹앱 매니페스트와 서비스 워커가 추가되면서 지원되는 브라우저에서는 홈 화면이나 앱 목록에 설치할 수 있게 됐습니다. 정적 자산은 캐시를 우선 사용하면서 백그라운드에서 갱신하고, 네트워크가 끊긴 상태에서 페이지 이동을 시도하면 오프라인 안내 화면을 제공합니다.

현재 오프라인 기능은 앱의 기본 화면과 정적 자산을 위한 안전망에 가깝습니다. 작품 본문 전체를 내려받아 오프라인으로 읽는 기능까지 구현된 것은 아닙니다.

리더는 ‘마지막 페이지까지 자연스럽게’에 집중했다

최근 프론트엔드 변경에서 가장 눈에 띄는 부분은 페이지 모드입니다. 사용자는 세로 스크롤 방식과 책처럼 넘기는 페이지 방식을 선택할 수 있습니다. 넓은 화면에서는 두 단, 모바일에서는 한 단으로 본문을 배치하며 선택한 모드와 글자 크기, 종이·세피아·야간 테마는 브라우저에 보관됩니다.

  • 화면 좌우 영역 클릭 또는 터치로 이전·다음 페이지 이동
  • 모바일 좌우 스와이프, 마우스 휠, 방향키와 스페이스 키 지원
  • 중앙 영역 더블 터치로 상단 도구와 하단 내비게이션 표시 전환
  • 현재 페이지와 전체 페이지 표시 및 페이지 번호 직접 입력
  • 이전 작품·목록·다음 작품으로 이어지는 하단 이동 영역

페이지 수 계산에서는 마지막 장이 실제 스크롤 가능한 범위보다 멀리 배치되는 경우를 보정하기 위해 뒤쪽 여백을 동적으로 추가합니다. 단순히 페이지 수만 표시하는 데서 끝나지 않고, 마지막 페이지로 실제 이동할 수 있는 좌표까지 맞춘 점이 구현의 핵심입니다.

읽기 위치는 문단 단위로 계산합니다. 사용자가 이동한 뒤 잠시 기다렸다가 자동으로 저장하고, 페이지를 닫는 순간에도 마지막 위치를 한 번 더 전송합니다. 삽화형 콘텐츠는 저장된 위치 이전의 이미지를 먼저 준비한 뒤 복원해, 이미지 로딩 때문에 책갈피 위치가 어긋날 가능성을 줄였습니다.

브라우저 수집은 서버 다운로드에서 로컬 전송으로 바뀌었다

브라우저 확장 기능은 웹페이지의 선택한 본문을 새 완결본으로 저장하거나, 기존 완결본 뒤에 이어 붙이거나, 연재 작품의 새 회차로 추가할 수 있습니다.

최근 변경에서 가장 중요한 부분은 이미지 처리 방식입니다. 이전처럼 서버가 외부 이미지 주소를 다시 방문하는 흐름에 의존하지 않고, 브라우저가 현재 페이지에서 접근 가능한 이미지를 직접 수집해 본문과 함께 전송합니다. 로그인 세션이나 지연 로딩 때문에 서버에서 원본 이미지를 가져오기 어려운 페이지를 고려한 변화입니다.

이미지 여러 장은 하나의 바이너리 묶음으로 전송되고, 각 이미지의 위치와 길이를 담은 메타데이터가 함께 전달됩니다. 백엔드는 다음 항목을 차례로 확인합니다.

  • 이미지 메타데이터의 순서와 바이트 구간이 정확히 이어지는지
  • 개별 이미지와 전체 요청이 허용 용량을 넘지 않는지
  • 선언된 데이터 길이와 실제 수신한 길이가 일치하는지
  • 허용된 이미지 형식인지, 실제로 해석 가능한 이미지인지

확장 기능에서 전달된 HTML은 텍스트와 이미지 블록으로 다시 분석됩니다. 이 경로에서는 임의의 원격 이미지 주소를 서버가 추가로 내려받지 않도록 막고, 브라우저가 전달한 이미지 식별자와 실제 파일 데이터가 연결된 경우만 저장합니다.

여러 기기의 토큰을 따로 관리한다

브라우저 연동용 인증도 단일 토큰에서 다중 토큰 방식으로 확장됐습니다. 관리자는 집 PC나 노트북처럼 기기를 알아볼 수 있는 이름으로 토큰을 발급하고, 발급일과 마지막 사용 시점을 확인하거나 특정 토큰만 폐기할 수 있습니다.

원본 토큰은 발급 직후 한 번만 표시되고 데이터베이스에는 단방향 해시가 저장됩니다. 한 기기의 연결을 해제해야 할 때 다른 기기의 토큰까지 다시 설정할 필요가 없다는 점도 실사용에서 꽤 편리한 변화입니다.

현재 기능을 사용하는 방법

  1. 서재에 로그인합니다. 작품 검색과 장르 필터, 소설 추가 메뉴를 사용할 수 있습니다.
  2. 완결본 또는 연재형을 선택합니다. 완결본은 TXT, PDF, 이미지, ZIP·CBZ 또는 붙여 넣기로 등록합니다. 연재형은 작품 정보를 먼저 저장한 뒤 회차를 추가합니다.
  3. 리더 설정을 맞춥니다. 스크롤과 페이지 모드 중 하나를 고르고 글자 크기와 테마를 조정합니다. 이후에는 저장된 설정과 책갈피가 자동으로 복원됩니다.
  4. 브라우저 연동이 필요하면 관리 화면에서 기기별 토큰을 발급합니다. 토큰은 다시 표시되지 않으므로 발급 직후 확장 기능 설정에 저장합니다.
  5. 웹페이지 본문을 전송합니다. 새 작품, 기존 완결본에 추가, 연재 회차 추가 중 원하는 저장 방식을 선택합니다.
  6. 필요하면 웹앱으로 설치합니다. 지원되는 브라우저에서는 일반 앱처럼 실행할 수 있습니다.

현재 작업 상태에서 확인할 점

백엔드 영역은 최근 커밋 이후 분석 대상 범위 안에 별도의 미커밋 변경이 없습니다. 프론트엔드에는 페이지 넘김 애니메이션 스타일, 하단 내비게이션 접기 스크립트, 마지막 페이지 검증 화면이 아직 추적되지 않은 상태로 남아 있습니다.

다만 앞의 두 자산은 현재 리더 레이아웃에서 불러오지 않으므로, 실제 서비스에 적용된 기능으로 보기는 어렵습니다. 마지막 페이지 검증 화면 역시 독립적인 테스트용 화면입니다. 따라서 현재 확정된 결과는 페이지 번호 이동과 마지막 페이지 여백 보정이며, 애니메이션이나 내비게이션 접기 효과는 후속 실험 단계로 구분하는 편이 정확합니다.

마무리

이번 개발 과정은 기능을 무작정 늘리기보다 콘텐츠가 들어오고, 정리되고, 읽히는 전체 흐름을 차근차근 다듬는 작업에 가까웠습니다. 특히 브라우저가 이미지를 직접 수집하도록 바꾼 부분과 마지막 페이지 이동을 실제 레이아웃 기준으로 보정한 부분은 작은 구현 차이가 사용 경험을 크게 바꿀 수 있다는 점을 잘 보여줍니다.

현재 구조라면 콘텐츠 형식과 리더 동작을 계속 확장하면서도 프론트엔드의 표현 책임과 백엔드의 검증·저장 책임을 비교적 선명하게 유지할 수 있습니다. 개인 서재라는 목적에 맞게, 매일 쓰는 과정에서 생기는 불편을 하나씩 줄여 온 개발 기록이라고 정리할 수 있겠습니다.

#기록 #개발 #데이터베이스 #이미지 처리 #독서 앱 #UI/UX #웹 개발 #검색 #문서 편집

댓글 0

    댓글 남기기