ntsapp.dev는 낚시 기록 앱부터 도시 건설 게임까지, 혼자 만든 앱 일곱 개를 소개하는 허브입니다. 이 글은 그 허브 자체를 만들며 내린 결정들의 기록입니다. 화려한 스택 이야기는 아니고, 작은 정적 사이트가 오래 정확하게 살아남게 하는 종류의 결정들입니다.
덧붙임 (2026-08-11). 위의 "일곱 개"는 이 글을 쓴 2026년 7월 30일 기준입니다. 그 뒤로 계속 늘었으니 지금 몇 개인지는 앱 목록에서 보시는 편이 정확합니다. 제목과 본문은 그날의 기록이라 그대로 뒀습니다. 남겨 둘 만한 건 숫자가 아니라 이 글이 스스로 어긴 규칙입니다. 같은 "일곱 개"가 사이트 소개 문구에도 손으로 박혀 있었습니다. 그쪽은 기록이 아니라 현재를 말하는 문장인데, 앱이 열둘이 되도록 링크 미리보기는 계속 일곱이라고 떠들고 있었습니다. 뒤에 붙은 앱 나열은 미리보기에서 중간에 잘려 문장이 끊긴 것처럼도 보였습니다. 아래 「하나로 모으면」에 적은 "어긋날 수 있는 두 벌을 만들지 말 것"이 바로 이 경우입니다. 앱 목록이라는 진짜 출처가 있는데 개수를 사람이 한 번 더 적어 둔 것이 두 벌이었습니다. 게다가 개수를 세는 헬퍼가 이미 있었고 "개수가 또 늘어도 문구가 낡지 않도록"이라는 주석까지 달려 있었는데, 정작 소개 문구만 그걸 안 쓰고 있었습니다. 지금은 개수를 앱 목록에서 세고, 소개 문구에서 앱 이름 나열을 뺐습니다. 하나가 늘어도 문장이 틀릴 수 없고, 미리보기에서 잘릴 꼬리도 없습니다.
마크업은 한 벌만 존재한다
이 사이트는 한때 마크업이 두 벌이었습니다. React 컴포넌트가 한 벌, 정적 HTML 생성기가 한 벌. 생성기는 심지어 React 소스에서 한국어 문자열을 위치 기반으로 뽑아 썼습니다. 문장 하나를 끼워 넣으면 그 뒤 문자열이 전부 한 칸씩 밀렸고, React 쪽만 고치면 실제 사이트에는 아무 변화가 없었습니다. 조용히 어긋나는 이중 구현의 전형입니다.
지금은 페이지 마크업과 CSS가 전부 문자열 빌더 함수 하나의 모듈에 있습니다. 정적 생성기와 React 라우트가 같은 함수를 부르니까 고칠 곳이 한 곳이고, 어긋날 방법이 없습니다. 콘텐츠(앱 목록, 개발 기록)는 별도 데이터 파일로 빼서 함수에 주입합니다.
한글 웹폰트, 전체가 아니라 쓰인 글자만
한글 웹폰트는 음절 블록을 통째로 실으면 서체당 3~8MB입니다. 이 사이트가 실제로 쓰는 글자는 수백 자뿐이라, 빌드된 HTML에서 글자 집합을 뽑아 그 글자만 굽습니다. 결과는 서체당 수십 KB.
여기에는 라이선스 결정이 하나 붙어 있습니다. 본문 서체인 IBM Plex는 SIL OFL로 공개돼 있지만 Reserved Font Name이 걸려 있고, 서브셋은 수정본(Modified Version)에 해당합니다. 그래서 서브셋의 내부 이름을 다른 이름으로 바꿔 싣고 라이선스 전문을 함께 배포합니다. 제목 서체인 Gothic A1은 예약 이름 선언이 없어 제 이름을 그대로 씁니다.
대가도 있습니다. 눈에 보이는 문구가 바뀔 때마다 서브셋을 다시 구워야 합니다. 잊으면 새 글자만 시스템 폰트로 떨어지는 조용한 열화가 생기죠. 그래서 이 절차는 빌드 문서에 체크리스트 항목으로 박아 뒀습니다.
파일 이름에 내용 해시를 넣는 이유
이미지와 폰트는 전부 내용 해시가 붙은 파일명으로 배포됩니다. 취향이 아니라 두 번 데인 결과입니다.
- 스크린샷을 교체했는데 파일명이 같아서 CDN 캐시가 이전 이미지를 계속 서빙했습니다.
- 배포가 전파되는 도중에 도착한 요청이 HTML 폴백을 받아 갔고, 그 응답이 폰트 URL 아래에
캐시로 굳었습니다. 파일명을 바꾸는 게 오염된 캐시 항목에서 걸어 나오는 가장 확실한 방법이었습니다.
내용이 바뀌면 이름이 바뀌고, 이름이 바뀌면 캐시는 알아서 무효화됩니다.
디자인은 계약 문서로 강제한다
다크 캔버스에 앱마다 고유 액센트를 주는 지금의 디자인은 DESIGN.md라는 계약 문서로 관리합니다. 색, 타이포, 간격, 모션 값이 전부 토큰으로 선언돼 있고 토큰 밖의 raw 값은 결함으로 취급합니다. 앱 액센트가 맡는 역할도 딱 셋입니다. 아이콘 글로, 카드 상단 룰, 마키의 이름 색. 규칙이 문서에 있으면 반년 뒤의 내가 일관성을 지킬 수 있습니다.
게임은 같은 도메인의 별채에 산다
Tiny Knight Rush 소개 페이지, Little City Story, 도트 일병 구하기의 웹 빌드는 각각 경로 단위로 독립 배포됩니다. 허브를 다시 배포해도 게임에는 영향이 없고, 게임을 갱신해도 허브는 그대로입니다. 한 도메인 아래 있지만 서로의 배포 사고에 물리지 않는 구조라, 혼자 여러 개를 운영할 때 실수의 반경이 줄어듭니다.
하나로 모으면
여기 쓰인 결정들은 결국 한 문장으로 모입니다. 어긋날 수 있는 두 벌을 만들지 말고, 어긋나면 눈에 보이게 만들 것. 마크업 단일화도 해시 파일명도 디자인 계약도 전부 그 원칙의 다른 표현입니다. 사이트는 앞으로도 계속 바뀔 텐데, 이 원칙이 지켜지는 한에서만 빨리 바뀔 수 있습니다.