전자책을 한 권 팔고 있었습니다. atmbook.app, 판매 중인 사이트입니다. 결제가 붙어 있고 구매자가 있습니다.
여기에 두 번째 책과 웹소설 연재를 얹기로 했습니다. 문제는 하나였습니다. 팔고 있는 걸 멈출 수 없다는 것.

다시 짓기 전에, 안 건드릴 것부터 정했습니다
리뉴얼에서 제일 위험한 순간은 “어차피 바꾸는 김에”입니다. 그래서 반대로 시작했습니다. 바꾸지 않을 것의 목록을 먼저 만들고, 그게 안 바뀌었다는 걸 증명할 자를 먼저 만들었습니다.
작업 전에 지금 사이트가 내려주는 응답을 파일로 떠 놨습니다. 익명 상태, 구매자 상태, 한국어, 영어 — 다섯 가지. 이후 코드를 고칠 때마다 이 다섯 개와 비교했습니다. 판정 기준은 “비슷하다”가 아니라 “바이트가 같다”였습니다.
결과적으로 작업이 끝날 때까지 다섯 개 전부 동일했습니다. 기존 책의 34챕터는 글자 하나 바뀌지 않았고, 가격도 할인 뱃지도 결제 버튼도 그대로입니다.
진짜 문제는 화면이 아니라 잠금장치였습니다
작품이 하나일 때는 “샀다 / 안 샀다”만 있으면 됩니다. 그래서 잠금해제 토큰에 상품 정보가 없었습니다. 그냥 유효기간만 들어 있었습니다.
여기에 두 번째 유료 작품을 그냥 얹으면 어떻게 될까요. 첫 책을 산 사람이 신간을 공짜로 읽게 됩니다. 반대도 마찬가지고요. 화면을 아무리 예쁘게 만들어도 이건 안 고쳐집니다.
그래서 토큰에 “어떤 작품을 샀는가”를 넣되, 이미 발급된 토큰은 절대 무효화하지 않기로 했습니다. 규칙은 한 줄입니다. 작품 목록이 없는 옛날 토큰은 첫 책의 전권으로 인정한다. 기존 구매자는 아무것도 하지 않아도 어제와 똑같이 읽습니다.
한 가지 더 정한 규칙이 있습니다. 상품 매핑이 빠졌을 때는 항상 안전한 쪽으로 틀리게 만들었습니다. 등록 안 된 상품 ID가 들어오면 첫 책으로 떨어집니다. 제가 실수해서 기존 구매자가 잠기는 사고는 구조적으로 일어나지 않습니다.
웹소설을 4개 언어로 연재한다는 것
새로 얹은 것 중 하나는 웹소설입니다. 「초인 대전」, 시즌 1이 4부 120화 예정이고 지금 3화까지 공개했습니다. 전편 무료입니다.
한국어로 쓰고 영어·일본어·중국어로 옮겼습니다. 여기서 실무적으로 성가신 문제가 하나 생깁니다. 회차 중간에 티저 영상이 들어가는데, 번역을 하면 문장이 달라지니까 영상이 붙을 자리를 언어마다 다시 찾아야 합니다.
그래서 영상 위치를 한국어 원문에서 한 번만 확정하고, 번역본은 그 자리를 그대로 물려받게 했습니다. 대신 조건을 하나 걸었습니다. 번역본의 문단 수가 원문과 다르면 빌드를 실패시킵니다. 번역하다 문단을 합치거나 나누면 영상이 엉뚱한 데 붙는데, 그걸 사람 눈이 아니라 빌드가 막습니다.

번역을 마치고 다시 읽다가 중국어에서 문제를 여럿 찾았습니다. 대사 인용부호를 ASCII 따옴표로 써 놨더군요. 간체 중문 조판에서는 다른 부호를 씁니다. 282쌍을 전부 바꿨습니다. 등장인물 한 명의 한자 표기도 바꿨습니다. 제가 고른 글자가 중국 독자에게는 특정 인물의 성씨로 읽혀서, 작품과 상관없는 잡음을 만들 수 있었습니다.
읽는 화면은 기존 것을 건드리지 않고 새로 만들었습니다
전자책 리더와 연재 리더는 요구가 다릅니다. 책은 목차를 두고 쭉 읽지만, 연재는 한 회차씩 끊고 이전화·다음화로 움직입니다. 읽던 화도 기억해야 하고요.
기존 리더를 뜯어고칠 수도 있었지만 그러지 않았습니다. 그 파일은 구매 완료 후 자동으로 열리는 주소라서, 손대는 순간 결제 동선이 위험해집니다. 그래서 연재용 리더는 새 파일로 만들었습니다. 기존 리더는 스타일시트 한 줄도 바뀌지 않았습니다.

87MB 원고를 36챕터로 옮기는 일
두 번째 책은 워드 파일로 있었습니다. 87MB. 열어 보니 본문 2,379문단에 이미지가 238장이었습니다.
이미지가 문제였습니다. 그대로 올리면 79.9MB입니다. 폭을 1,200px로 줄이고 WebP로 바꿔 8.7MB로 만들었습니다. 중간에 끝이 잘린 JPEG 두 장이 변환을 막길래, 버리지 않고 있는 데까지 읽어서 살렸습니다.
작업하면서 놓칠 뻔한 것도 있었습니다. 워드는 문단 안의 줄바꿈을 따로 저장하는데, 처음 만든 변환기가 그걸 버리고 있었습니다. 문장들이 서로 붙어서 나왔죠. 285곳을 복원했습니다.

배포하자마자 사이트가 고장 났습니다
여기부터가 이 글에서 제일 쓸모 있는 부분일 겁니다.
푸시하고 확인하러 들어갔더니, 본문을 내려주는 주소가 모든 작품에 대해 404를 뱉고 있었습니다. 구매자의 리더가 동작하지 않는 상태였습니다.
원인은 한 줄이었습니다. 작품 데이터를 필요할 때만 읽으려고 파일 경로를 계산해서 불러오게 했는데, 배포 환경의 번들러는 코드를 정적으로 분석해서 필요한 파일을 골라 담습니다. 경로를 계산하는 방식은 추적이 안 되니 본문 데이터가 아예 안 실렸던 겁니다.
로컬에서는 재현되지 않았습니다. 로컬 서버는 파일 시스템을 그대로 읽으니까요. 로컬 검증만으로는 절대 못 잡는 종류의 문제였습니다.
경로를 고정 문자열로 되돌리는 핫픽스를 바로 올려 복구했고, 같은 계열을 다시 놓치지 않으려고 배포 후에 실제 서비스 주소를 두드려 보는 점검 스크립트를 만들었습니다. 페이지가 열리는지, 본문이 실제로 내려오는지, 가격과 결제 버튼이 살아 있는지를 확인합니다.
교훈은 단순합니다. 로컬에서 되는 것과 배포에서 되는 것은 다른 문제입니다. 배포 후 점검을 자동화하지 않으면, 결국 사용자가 먼저 발견합니다.
지금 읽을 수 있는 것
atmbook.app에 지금 세 가지가 올라가 있습니다.
나 혼자, AI 팀으로 글로벌 앱 만들기 — 코딩을 모르던 제가 AI 팀을 꾸려 앱을 출시하기까지의 34챕터입니다. 무료 1챕터를 열어 뒀습니다.
AI 아바타 유튜브 — 얼굴도 목소리도 공개하지 않고 채널을 여는 방법을 4부 36챕터로 정리했습니다. 앞의 3챕터를 무료로 공개했습니다.
초인 대전 — ATM이 세계관부터 직접 쌓아 올린 웹소설입니다. 시즌 1 전편 무료이고, 한국어·영어·일본어·중국어로 읽을 수 있습니다.
읽어 보시고 이상한 곳이 있으면 알려 주세요. 위에 적은 것처럼, 제가 못 보고 지나간 게 아직 있을 겁니다.