GITHUB DEEP DIVE · TRENDSHIFT #18 · 2026.05.27

1000개 마크다운으로 만든
24,071권 검색 엔진

jbiaojerry/ebook-treasure-chest — 별 11.8k짜리 큐레이션 리포가 어떻게 "정적 사이트 + Python 파이프라인"만으로 풀 텍스트 검색을 제공하는지 뜯어본다.

0먼저, 이 레포가 뭔가?

표면은 큐레이션 리스트, 속은 데이터 파이프라인.

겉으로 보면 그냥 중국어 전자책 다운로드 링크를 모아둔 깃허브 저장소다. 樊登读书(판덩 독서), 微信读书(위챗 독서), 京东读书(징둥 독서), 喜马拉雅(시마라야) 같은 중국 독서 앱에 있는 책들의 epub·mobi·azw3 파일을 카테고리별 마크다운에 정리해둔 것. 별 11,813개, 포크 1,677개가 붙은 인기 큐레이션 리포다.

그런데 한 꺼풀 벗기면 이야기가 달라진다. 이 리포는 단순한 README + 링크 모음이 아니라 1000개 마크다운 파일 → JSON 데이터셋 → 정적 검색 사이트로 이어지는 작은 파이프라인이다. 별을 모은 진짜 이유도 이 자동화 구조 덕분에 24,071권을 GitHub Pages에서 실시간 검색할 수 있어서다.

용어
큐레이션 리포 (Curation Repository)
코드를 배포하는 게 아니라 링크·자료·체크리스트를 분류해 모아두는 깃허브 저장소. awesome-*로 시작하는 리포들이 대표적인 예다. 별 수가 많아 GitHub 트렌딩 상위권에 자주 오른다.
용어
epub / mobi / azw3
전자책 파일 포맷 3종. epub은 업계 표준(애플 북스·구글 플레이북 등 거의 모든 리더), mobi는 옛 킨들 포맷, azw3는 신형 킨들 전용. 같은 책을 세 포맷으로 함께 제공하면 어떤 기기에서도 열 수 있다.

1한 줄 요약

왜 이 리포가 트렌딩 18위에 올랐는가.

핵심 메시지

"DB 서버도, 백엔드 API도 없이
마크다운 1000장만으로 만든 전문 검색."

모든 데이터를 빌드 타임에 JSON 한 파일로 합쳐서 브라우저에 통째로 던지고, 검색은 클라이언트 JavaScript에서 한다. 인프라 비용은 0원, 서버 다운타임도 없다. "서버 없는 검색 엔진"이라는 발상이 도서 큐레이션과 만나면서 별 1만 개를 끌어모은 것.

2왜 주목받는가

트렌딩 이유, 그리고 경쟁 제품과의 차이.

전자책 검색 사이트는 인터넷에 많다. Z-Library, Anna's Archive, Library Genesis 같은 큰 플랫폼이 이미 있는데도 이 작은 깃허브 리포가 별 1만 개를 넘긴 데에는 이유가 있다.

차별점 1

"앱에서 막 꺼낸 책" 컬렉션

樊登读书·微信读书 같은 중국 독서 구독 앱 전용 책을 epub·mobi·azw3로 변환해 모았다. 일반 도서관 사이트엔 잘 없는 컬렉션이라 중국어 사용자에게 희소가치가 크다.

차별점 2

가입·로그인·광고 없음

대형 도서관 사이트는 가입 강제 / VIP 결제 / 광고 팝업이 끼지만, 이 리포는 GitHub Pages에 호스팅된 정적 페이지라 즉시 검색 → 클릭 → 다운로드로 끝난다. 마찰이 거의 없다.

차별점 3

24,071권을 0.3초 디바운스로 검색

키워드를 입력하면 search.js가 메모리에 올라간 24,071권 배열을 필터링한다. 네트워크 왕복이 없으니 사실상 입력과 동시에 결과가 뜬다. 백엔드 API에 의존하지 않는 구조의 가장 큰 장점이다.

차별점 4

저자가 자동화로 직접 관리

리포 안의 max_book_id_found.txt 값은 64,970. 책 ID를 6만 4천까지 순차 스캔하며 새 책을 발견할 때마다 마크다운에 추가한 흔적이다. 큐레이션이 수작업이 아니라 자동 동기화로 굴러간다.

단, 주의
저작권 회색지대

이 리포가 모아둔 책의 상당수는 원래 유료 구독 앱의 콘텐츠다. 학습 자료로 구조와 파이프라인을 뜯어보는 건 자유지만, 실제로 책을 내려받아 재배포하는 건 본인 책임 영역. 트렌드를 분석할 때는 "구현은 멋지지만 운영 모델은 따라 하면 안 된다"는 점을 기억하자.

3기술 스택 전체 지도

단순해 보이지만 세 층이 또렷하다.

이 프로젝트는 거대한 프레임워크 없이 Python + 마크다운 + 바닐라 JavaScript만으로 굴러간다. 풀스택이라기보단 "파이프라인 + 정적 사이트"에 가까운 구조다. 각 층을 따로 보자.

데이터 층 (Source of Truth)

DATA

마크다운 1,000개 — md/ 폴더

카테고리 하나당 .md 파일 하나. 예: md/문학.md(2,711권), md/历史.md(1,748권), md/Python.md(28권). 각 파일 안엔 "책 이름 | 저자 | [다운로드](링크)" 표 한 장이 있다. 사람도 깃허브에서 그대로 읽을 수 있고, 동시에 기계도 파싱 가능한 이중 용도 문서다.

DATA

책 ID 추적기 — max_book_id_found.txt

파일 내용은 숫자 한 줄(64970)뿐. "여기까지 ID를 훑었음" 표시기로, 자동 동기화 스크립트(scripts/sync/)가 다음번에 다시 돌 때 이 ID부터 이어서 확인하라는 신호다. 단순하지만 증분 업데이트의 본질이다.

처리 층 (Python 파이프라인)

PYTHON

scripts/parse_md_to_json.py (약 200줄)

핵심 ETL. 1,000개 .md를 모두 읽어 표를 정규식으로 파싱하고, 각 책에 {title, author, link, category, formats} JSON 객체를 만들어 docs/all-books.json으로 합친다. 결과는 약 7MB짜리 JSON 한 덩어리.

pattern = r'\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*\[下载\]\((.+?)\)\s*\|' — 마크다운 표 한 줄을 통째로 잡아내는 정규식.
PYTHON

scripts/generate_index.py (약 30KB)

all-books.json을 읽어 GitHub Pages용 docs/index.html(검색 UI 포함 완성 HTML)과 docs/books.json을 자동 생성한다. 스크립트 실행 결과는 GitHub Actions generate-site.yml이 main 브랜치에 자동 커밋한다.

PYTHON

scripts/sync/ — 데이터 수집기

외부 사이트에서 신간을 가져와 md/에 자동 추가하는 동기화 모듈(레포에 폴더만 노출). 이 모듈이 max_book_id_found.txt를 읽고 그다음 ID부터 다시 스캔한다.

프레젠테이션 층 (GitHub Pages)

FRONTEND

docs/index.html (78KB) + docs/search.js (8KB)

바닐라 자바스크립트 단일 페이지. React·Vue·번들러·빌드 시스템이 전혀 없다. 페이지를 열면 fetch("all-books.json")으로 책 7MB를 가져오고, 입력창의 oninput에 300ms 디바운스를 걸어 메모리 배열을 필터링해 보여준다.

호스팅 / 자동화

용어
GitHub Pages (깃허브 페이지)
깃허브 저장소의 docs/ 또는 gh-pages 브랜치를 그대로 정적 호스팅해주는 무료 서비스. 서버 비용 0원으로 YOUR-NAME.github.io/REPO/ 주소를 받는다. 이 리포의 검색 사이트가 여기서 돌아간다.
용어
.nojekyll 파일
깃허브 페이지는 기본적으로 Jekyll이라는 정적 사이트 빌더를 한 번 거친다. .nojekyll이라는 빈 파일을 두면 "그 단계 건너뛰고 내 파일 그대로 서비스해"라는 신호. 이 리포는 자체 검색 사이트가 있으니 Jekyll이 손대지 못하게 막아둔 것.

4아키텍처 심화 분석

"빌드 타임 ETL + 런타임 클라이언트 검색" 패턴.

이 리포의 진짜 가치는 아키텍처 패턴이다. 같은 발상으로 도서뿐 아니라 강의·노트·논문·맛집·툴 디렉터리 사이트를 다 만들 수 있다. 핵심 패턴을 그림으로 보자.

┌──────────────── 사람이 손대는 영역 ────────────────┐
│                                                  │
│   md/문학.md   md/历史.md   md/Python.md  ...     │   ← 카테고리당 1개
│   (마크다운 표: 제목 | 저자 | [다운로드](URL))    │      = 사람도 기계도 읽음
│                                                  │
└──────────────────────────┬───────────────────────┘
                           │
                           ▼  (사람이 PR로 갱신)
┌──────────── Python 파이프라인 (빌드 타임) ────────┐
│                                                  │
│   scripts/sync/             ← 신간 자동 수집      │
│        │                                         │
│        ▼                                         │
│   max_book_id_found.txt     ← 진행률 체크포인트   │
│        │                                         │
│        ▼                                         │
│   scripts/generate_index.py ← README 색인 갱신    │
│        │                                         │
│        ▼                                         │
│   scripts/parse_md_to_json.py                    │
│        │   1) md 1,000장 읽기                    │
│        │   2) 정규식으로 표 파싱                  │
│        │   3) 카테고리/포맷 메타 부착            │
│        ▼                                         │
│   docs/all-books.json (7 MB)   ← 단일 데이터셋    │
│   docs/parse-stats.json        ← 통계 스냅샷      │
│                                                  │
└──────────────────────────┬───────────────────────┘
                           │
                           ▼  (git push → 즉시 배포)
┌──────────── GitHub Pages (런타임) ───────────────┐
│                                                  │
│   브라우저 ─ fetch("all-books.json") ─▶ 메모리   │
│                                              │   │
│                                              ▼   │
│                                  search.js 필터  │
│                                  (디바운스 300ms)│
│                                              │   │
│                                              ▼   │
│                                  하이라이트 렌더 │
│                                                  │
│   백엔드 서버: 없음.  DB: 없음.  API: 없음.       │
│                                                  │
└──────────────────────────────────────────────────┘

이 구조에 깔린 4가지 설계 결정

설계 1

"마크다운이 곧 데이터베이스"

SQLite도, Postgres도, JSON 파일도 원본이 아니다. 사람이 읽고 쓸 수 있는 마크다운 표가 단일 출처(single source of truth)다. Python 스크립트가 거기서 JSON을 빌드한다. 이렇게 하면 일반인이 깃허브 웹 UI에서 책을 추가하는 PR을 그대로 보낼 수 있다.

설계 2

"검색을 클라이언트로 밀어버린다"

전형적인 풀스택이라면 ElasticSearch나 PostgreSQL FTS로 검색 API를 만들었을 것. 이 리포는 그 모든 걸 "한 번에 다운로드시키고 브라우저에서 필터링"으로 대체했다. 7MB는 한 번만 받으면 캐시되고, 이후 검색은 빛의 속도.

books.filter(b => keywords.every(k => b.title.includes(k) || b.author.includes(k) || b.category.includes(k))).slice(0, 100);
설계 3

"폴백 데이터셋 2단 구조"

search.js는 먼저 all-books.json(풀 데이터)를 시도하고, 실패하면 books.json으로 폴백한다. books.jsonall-books.json의 동일 내용 사본(generate_index.py가 같은 데이터를 두 경로에 씀)으로 크기도 동일(약 6.8MB)하다. '가벼운 메타데이터'가 아닌 풀 데이터 복사본이며, all-books.json fetch 실패 시 같은 데이터를 다른 경로로 재시도하는 이중 보험 역할이다. Graceful Degradation이라는 클래식 패턴. 빌드가 실패해도 사이트는 살아 있다.

설계 4

"보안은 클라이언트 코드에 박아둔다"

서버가 없으니 입력 검증도 브라우저에서. search.js를 보면 HTML 이스케이프(escapeHtml), 정규식 인젝션 방어(escapeRegex), URL 프로토콜 화이트리스트(http/https만 허용)가 박혀 있다. javascript:로 시작하는 악성 링크를 차단하는 XSS 방어다.

비유

이 구조는 도서관 vs. 책방 카탈로그의 차이와 같다. 도서관(전통 풀스택)은 사서(서버)가 책장에서 책을 찾아 갖다 준다. 이 리포는 카탈로그 책 한 권을 통째로 손에 쥐여주고, 손님이 직접 색인을 넘기게 한다. 사서를 고용할 필요가 없으니 운영비가 0원이다.

한계
데이터셋이 커지면 무너지는 패턴

이 구조는 데이터가 10MB 안쪽일 때만 깔끔하다. 책이 50만 권으로 늘어 JSON이 100MB가 되면 모바일에서 초기 로딩이 망가진다. 그땐 lunr.js 같은 클라이언트 검색 인덱스를 도입하거나, 서버리스 함수로 검색을 분리해야 한다.

5디렉토리 구조 해부

어느 폴더가 사람용이고 어느 폴더가 기계용인지.

ebook-treasure-chest/
├── README.md              ← 35KB, 색인 목차 (자동 생성)
├── max_book_id_found.txt  ← 5바이트 (숫자 64970만 들어있음)
├── sponsors.md            ← 후원자 명단
│
├── .github/               ← 로고·검색 데모 GIF·후원 QR 이미지
│
├── md/                    ← 카테고리별 마크다운 1,000개
│   ├── 文学.md            ← 2,711권
│   ├── 历史.md            ← 1,748권
│   ├── Python.md          ← 28권
│   └── ... (1000개)
│
├── scripts/               ← Python 파이프라인
│   ├── parse_md_to_json.py        ← md → JSON 변환 ETL
│   ├── generate_index.py          ← README 색인 자동 생성
│   ├── generate_search_demo_gif.py← 검색 데모 GIF 만들기
│   └── sync/                       ← 신간 자동 수집기
│
└── docs/                  ← GitHub Pages 호스팅 루트
    ├── .nojekyll          ← Jekyll 빌드 끄기
    ├── index.html         ← 검색 UI (78KB)
    ├── search.js          ← 클라이언트 검색 로직 (8KB)
    ├── all-books.json     ← 전체 데이터셋 (7MB, 24,071권)
    ├── books.json         ← 폴백용 메타데이터
    └── parse-stats.json   ← 빌드 통계 스냅샷

폴더별 역할

md/

사람이 손대는 원본 데이터

구조의 핵심. PR이 여기로 들어온다. 파일명이 곧 카테고리 이름이고, 파일 안 표 한 장이 그 카테고리의 책 목록 전부. 일반 깃허브 사용자가 코드 한 줄 안 보고도 책을 추가할 수 있다는 게 이 폴더의 미덕.

scripts/

마크다운을 데이터로 바꾸는 공장

이 폴더는 사용자가 직접 만질 일이 거의 없다. 리포 관리자가 PR 머지 후 또는 정기적으로 돌리는 빌드 도구. parse_md_to_json.py가 매번 1,000개 마크다운을 다시 다 읽어 JSON을 새로 만든다. 증분 빌드 아님이라는 점이 단순함의 비결.

docs/

배포 산출물 (사용자가 보는 모든 것)

GitHub Pages는 이 폴더 안의 파일들을 그대로 정적 서빙한다. all-books.json빌드 산출물이지만 git에 그대로 커밋해서 같이 배포한다. 흔히 데이터의 형상 관리(versioning data)라 부르는 방식.

.github/

리포 메타 자원

주로 README에서 참조하는 이미지(로고, 검색 데모 GIF, 후원 QR)가 들어있다. GitHub Actions 워크플로 3개가 .github/workflows/에 공개돼 있다 — generate-site.yml(md 변경 시 index.html 자동 빌드), incremental-sync.yml(신간 증분 수집), full-sync.yml(전체 재동기화). 자동화 파이프라인이 레포 안에서 완결된다.

6기술별 학습 포인트

이 레포에서 진짜로 배울 만한 것들.

Python — 마크다운 ETL 패턴

parse_md_to_json.py는 길지 않은데 현업에서 자주 쓰는 패턴이 다 들어있다. 정규식, pathlib, defaultdict로 카운팅, JSON 직렬화, 통계 부산물 저장, 에러 파일 추적까지. 처음 데이터 파이프라인을 작성하는 사람이 베껴 가기 좋은 골격.

실습 아이디어
"내 노트 모음 → JSON 변환기" 만들어보기

옵시디언(Obsidian)이나 노션에서 내려받은 마크다운 폴더에 같은 패턴을 적용. 표 대신 YAML frontmatter를 파싱하도록 바꾸면 즉시 자기 데이터셋이 된다.

바닐라 JavaScript — 디바운스 + 메모리 필터

search.js가 가르쳐주는 건 "리액트가 없을 때 어떻게 검색 UX를 만드는가". setTimeout으로 300ms 디바운스, filter + every로 다중 키워드 AND 매칭, <mark> 태그로 하이라이트. 모든 패턴이 50줄 안에서 끝난다.

실습 아이디어
"가벼운 검색 컴포넌트" 직접 짜보기

리액트 프로젝트에 검색 박스를 만들 때 일단 useDebounce 훅부터 찾는데, 이 레포의 코드를 보면 라이브러리 없이도 6줄로 끝난다는 걸 알 수 있다.

보안 — XSS 방어 3종 세트

용어
XSS (Cross-Site Scripting, 사이트 간 스크립트 공격)
사용자가 입력한 HTML/JS가 그대로 렌더링되어 다른 사용자 브라우저에서 실행되는 취약점. 댓글창에 <script>…</script>를 박았는데 그게 다른 사람한테도 동작하면 그게 XSS다.

search.js의 방어막을 그대로 가져갈 만하다. (1) escapeHtml로 모든 사용자 출력을 escape, (2) escapeRegex로 정규식 메타문자를 살균, (3) URL 프로토콜 화이트리스트javascript:·data:를 차단. 정적 사이트라고 보안을 무시할 수 없다는 좋은 사례.

GitHub Pages + 정적 사이트 운영

.nojekyll 한 줄로 빌드 단계를 끄고, docs/ 폴더에 JSON·JS·HTML을 그대로 두면 끝. 도메인 없이도 무료로 배포되는 가장 단순한 방식. 데이터를 깃에 커밋해 함께 배포한다는 점도 핵심 — CDN, S3, 별도 DB가 필요 없다.

증분 동기화 패턴

max_book_id_found.txt는 한 줄짜리 파일이지만 의미는 크다. "어디까지 했는지 외부에 적어두기"는 모든 크롤링·동기화·배치 시스템의 기본기다. 데이터베이스 없이 텍스트 파일 하나로 체크포인트를 관리하는 가장 미니멀한 형태.

7시스템 요구사항

개인 노트북이면 충분.

이 프로젝트는 특별한 하드웨어가 필요 없다. Python 표준 라이브러리(json, re, pathlib, collections)만 쓰기 때문에 외부 의존성도 거의 없다.

요구사항

로컬에서 돌리기

Python 3.8 이상이면 scripts/parse_md_to_json.py가 그대로 돈다. 메모리는 1GB 정도면 여유 있고, JSON 7MB를 메모리에 올리는 정도라 노트북 어디서든 무리 없이 돈다.

요구사항

검색 사이트 띄우기

docs/ 폴더에 들어가서 python -m http.server 8000만 치면 즉시 로컬 호스팅. 브라우저는 모던 브라우저(크롬 90+, 사파리 14+)면 모두 가능. fetch APIES6 화살표 함수만 쓰니까 호환성도 문제없다.

요구사항

모바일에서 검색하기

모바일에서 처음 한 번은 7MB 다운로드가 있다. 와이파이면 1~2초, LTE면 4~5초. 그 뒤로는 브라우저 캐시에 들어가 즉시 검색된다. 데이터 요금이 신경 쓰이면 첫 방문은 와이파이에서 권장.

8직접 해볼 수 있는 실습 과제

난이도별로 5단계. 1번부터 차례대로.

레벨 1 · 워밍업

리포 클론하고 검색 사이트 로컬에서 띄우기

git clonecd docspython -m http.server 8000 → 브라우저에서 localhost:8000 열기. 10분 안에 24,071권짜리 검색 엔진이 내 노트북에서 돌아가는 경험이 첫 단추.

레벨 2 · 데이터 다루기

마크다운 파일 1개를 골라 JSON으로 직접 파싱

md/Python.md(28권)를 텍스트로 읽고, 직접 정규식을 짜서 {title, author, link} 배열을 만들어본다. 그 뒤 parse_md_to_json.py의 정규식과 비교해 누가 더 견고한지 확인. "파싱은 한 줄로 안 끝난다"는 걸 체감하는 단계.

레벨 3 · 응용

검색에 "포맷 필터" 추가하기

현재 search.js는 제목·저자·카테고리만 본다. epub만 검색 / mobi만 검색 같은 필터 체크박스를 UI에 추가하고, books.filter() 안에서 포맷이 일치하는지 확인하도록 바꿔본다. 디바운스 로직은 그대로 두고 필터 조건만 늘리는 연습.

레벨 4 · 구조 바꾸기

"내 노트 큐레이션 리포" 만들기

이 리포 구조를 그대로 베껴 나만의 마크다운 큐레이션 사이트를 만든다. 책 대신 강의·툴·논문·맛집·뮤직비디오 등 본인이 관심 있는 주제로. md/·scripts/·docs/ 3폴더 구조 + GitHub Pages 활성화 + 자기 도메인 연결까지.

레벨 5 · 한계 돌파

클라이언트 검색을 lunr.js로 업그레이드

현재 구조는 substring 매칭만 한다("자기 계발"을 입력하면 "계발" 없는 책은 못 찾음). lunr.js(가벼운 풀텍스트 검색 라이브러리)로 인덱스를 빌드해서 형태소 단위 매칭으로 바꿔본다. 7MB JSON 대신 압축된 검색 인덱스로 데이터셋도 줄어든다.

9관련 기술 심화 로드맵

4주 코스 — 한 주에 하나씩.

1주차

Python 데이터 파이프라인 기초

학습 주제: pathlib, 정규식, JSON 직렬화, defaultdict로 카운팅, argparse로 CLI 만들기. 실습: parse_md_to_json.py를 한 줄씩 따라 쳐보고, 카테고리별 평균 책 권수 같은 통계 한 가지를 추가로 출력해보기. 추천 자료: 파이썬 공식 튜토리얼 "Brief Tour of the Standard Library".

2주차

바닐라 JS UX — 디바운스·필터·하이라이트

학습 주제: 이벤트 디바운스 vs. 쓰로틀, Array.prototype.filter, String.prototype.replace로 하이라이트, DOMParser 안전 사용. 실습: 텍스트 입력 → 디바운스 → 외부 API 호출 → 결과 하이라이트까지 50줄로 끝내는 검색 컴포넌트 작성. React 없는 UX 디자인의 기본기.

3주차

웹 보안 — XSS와 입력 검증

학습 주제: 반사형·저장형 XSS의 차이, innerHTML 함정, Content Security Policy(CSP) 헤더, URL 프로토콜 스푸핑. 실습: 일부러 취약한 댓글 폼을 만들고 <script>·onerror=…·javascript:로 우회 시도 → escapeHtmlURL 화이트리스트로 막기. 추천 자료: OWASP XSS Cheat Sheet.

4주차

정적 사이트 운영 + GitHub Actions

학습 주제: GitHub Pages 설정, .nojekyll의 의미, GitHub Actions로 빌드 자동화, 시크릿 관리, 캐시 헤더. 실습: 본인 큐레이션 리포에 .github/workflows/build.yml을 추가해서 PR이 들어올 때마다 parse_md_to_json.py가 자동으로 돌고, main에 머지되면 GitHub Pages가 자동 갱신되도록.

10핵심 키워드 사전

이 리포 주변에서 자주 등장하는 용어 정리.

키워드
SSG (Static Site Generator, 정적 사이트 생성기)
마크다운·JSON 같은 소스를 빌드 타임에 HTML로 미리 굽는 도구. Jekyll, Hugo, Next.js의 output: export, Astro 등이 SSG다. 이 리포는 SSG를 안 쓰고 직접 Python 스크립트로 같은 일을 한다는 점이 특이.
키워드
ETL (Extract, Transform, Load)
데이터 파이프라인의 3단계 — 추출(읽기) → 변환(가공) → 적재(저장). parse_md_to_json.py는 교과서적 ETL이다: md 1000장 추출 → 정규식 변환 → all-books.json 적재.
키워드
JAMstack
JavaScript + APIs + Markup의 약자. 정적 HTML을 CDN에 올리고, 동적 기능은 클라이언트 JS와 외부 API로만 처리하는 아키텍처. 이 리포는 API조차 없는 "JAMstack 미니멀 버전".
키워드
디바운스 (Debounce)
짧은 시간 안에 같은 이벤트가 여러 번 일어나면 마지막 한 번만 실행하도록 묶는 기법. "엘리베이터 버튼을 백 번 눌러도 출발은 한 번"이 디바운스의 비유. 검색창에서 키 한 번 칠 때마다 API를 때리지 않게 막을 때 필수.
키워드
Graceful Degradation (점진적 기능 저하)
최고급 기능이 실패하면 한 단계 낮은 기능으로 자동 폴백하는 설계. search.jsall-books.json → books.json 폴백이 정확히 이 패턴.
키워드
Single Source of Truth (단일 진실 공급원)
같은 정보를 여러 곳에 중복 저장하지 말고 한 곳만 진짜로 두기. 이 리포에선 md/ 폴더가 단일 진실. docs/all-books.json은 거기서 파생된 산출물일 뿐이다.
키워드
증분 동기화 (Incremental Sync)
매번 전체 데이터를 다시 가져오지 않고 "지난번 이후 새로 생긴 것만" 가져오는 방식. max_book_id_found.txt가 그 체크포인트 역할을 한다.
키워드
Form Action / target="_blank" rel="noopener"
target="_blank"로 새 창을 열 때 rel="noopener"를 빼먹으면 새 창에서 window.opener로 원래 페이지를 조작할 수 있는 보안 구멍이 생긴다. search.js는 다운로드 링크에 이걸 정확히 박아뒀다.
실용 가이드

지금 시도해볼 만한 것들

  1. "내 큐레이션 리포" 24시간 안에 띄우기. 책 대신 본인이 좋아하는 주제(맛집, 강의, 도구, 페이퍼)로 카테고리 마크다운 5장을 만들고, parse_md_to_json.py를 그대로 베껴 JSON을 굽고, GitHub Pages에 올린다. "스택 없는 검색 사이트"가 어떻게 굴러가는지 체감하는 가장 빠른 방법.
  2. 마크다운을 데이터로 보는 습관. 옵시디언, 노션, GitHub Wiki 등에서 쓰는 노트도 잠재적 JSON 데이터셋이다. YAML frontmatter나 표를 한 번 파싱 가능한 형태로 정리해두면, 나중에 이걸로 검색 사이트·통계 페이지·대시보드를 손쉽게 만들 수 있다.
  3. "백엔드 없이 가능한가?"를 먼저 묻기. 새 프로젝트를 시작할 때마다 React·Node·Postgres 풀스택부터 떠올리지 말고, 이 리포처럼 정적 + 클라이언트 검색으로 충분한지 먼저 점검. 10MB 안쪽 데이터에선 이게 가장 단순하고 안정적이다.
  4. XSS 방어 3종 세트 외워두기. 사용자 입력을 그대로 렌더링할 때마다 (1) escapeHtml, (2) escapeRegex, (3) URL 프로토콜 화이트리스트 — 이 셋을 자동 반사로 떠올리는 게 정적 사이트 개발자의 최소 보안 기본기.
  5. 증분 체크포인트를 텍스트 파일로. 크롤러나 동기화 스크립트를 짤 때 DB 안 만들고 그냥 last_processed.txt 한 줄로 진행률 기록하기. 가장 단순한 형태가 가장 잘 무너지지 않는다.
원문 · jbiaojerry/ebook-treasure-chest, 2026.05.27 기준 · github.com/jbiaojerry/ebook-treasure-chest
참고 · 검색 데모 (GitHub Pages) · TrendShift 트렌딩 #18
분석 시점 · ⭐ 11,813 · 🍴 1,677 · 카테고리 1,000개 · 책 24,071권 · max_book_id 64,970