ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Free Coders Books 개발기: 개발 무료 도서
    Projects 2026. 2. 18. 14:39
    반응형

    Github 35만 stars 프로젝트를 모던 웹앱으로 만들기에 도전했다.

    사실 이렇게 한 이유는 Github에 있는 것을 보려니 잘 보이지도 않고, 보기도 어려워서 UI/UX로 풀어보고 싶어서였다.

     

     

     

    Github에서 가장 많은 스타를 받은 레포지토리 중 하나인 free-programming-books 라는 것이 있다.

    35만개 이상의 stars를 보여한 이 프로젝트는 전 세계 개발자들이 기며하는 무료 프로그래밍 도서 목록이다.

    한 가지 아쉬운 점이 있다면 이 방대한 자료를 탐색하기가 어렵다는 것이다.

    Github의 긴 Markdown 파일을 스크롤 해야하고, 보기도 어렵다. 

    즉, UI/UX가 좋지 않다는 것이었다.

     

    그래서 Free Coders Books를 만들었다.

    Github의 원본 데이터를 실시가능로 가져와서 검색, 필터링, 북마크가 간으한 웹 어플리케이션으로 재탄생시켰다.

     

    FREE-CODERS-BOOKS

     

    Free Coders Books - Free Programming Resources

    Discover thousands of free programming books, courses, and resources in 50+ languages. Built for developers, by the community.

    free-coders-books.vercel.app

     

    free-coders-books

     


     

    최신 기술의 조합으로 스택을 정하자

    기술스택은 의도적으로 최신 기술을 선택했고, 무료로 사용할 수 있는 것 위주로 선택했다.

    주요 스택을 보자면 다음과 같다.

    분류 기술 선택 이유
    Framework Next.js 16 (App Router) ISR, Server Component, Routing
    Style Tailwind CSS 4, Shacdcn 빠른 UI 개발
    Database Turso (Edge SQLite) 글로벌 엣지 배포, 무료 티어
    ORM Drizzle ORM 타입 안전한 쿼리, 경량
    Authorization NextAuth.js v5 Github OAuth 통합
    Deploy Vercel Next.js 최적 배포 환경

     

    모든 기술이 최신 버전인 이유는 해당 기술들이 어떻게 조합되는지 직접 검증해볼 수 있는 기회이기도 했다.

     


     

    데이터베이스 없이 4000여 권의 책을 어떻게 서빙할까?

    해당 프로젝트에서 가장 핵심적인 설계 결정은 책 데이터를 데이터베이스에 저장하지 않는 것이었다.

     

    외부 데이터를 실시간 데이터 소스로 활용하자!

    4000권이 넘는 도서 데이터는 EbookFoundation의 Github 레포지토리에서 제공하는 JSON 파일을 직접 가져온다.

    이 프로젝트에서 아마 궁금한 것 중 하나가 "Github Repo가 업데이트 되면 이 사이트에서도 자동으로 반영되는가?" 일텐데 답은 "재배포 없이도 자동으로 반영된다" 이다.

     

    전체 데이터 파이프 라인을 그림으로 그려보면 다음과 같다.

    [ 단계 1 ] [ 단계 2 ] [ 단계 3 ]
    커뮤니티가 free-programming-books에 새 책을 PR/Commit EbookFoundation의 CI/CD가 Markdown을 fpb.json으로 변환 Free Corders Books가 fpb.json을 fetch
    즉시 ~ 수 시간 (PR Review/Merge 후) 자동 (외부 파이프라인)
    - 프로젝트가 제어 불가
    최대 1시간 (ISR + Cache)
    - 프로젝트가 제어

     

    핵심은 내 프로젝트가 데이터를 복사해서 저장하지 않는다는 것이다.

    매번 Github의 raw 파일을 직접 가져오되, 2중 캐시 레이어로 성능과 최신성의 균형을 잡고 있다.

     

    2중 캐시 아키텍쳐?

    1차는 앱 프로세스 레벨의 인메모리 캐시를 사용하고, 2차적으로는 서버 레벨에서 Next.js ISR 캐시를 사용한다.

    코드로 하나씩 보자면 다음과 같다.

    // 1차: 인메모리 캐시 (앱 프로세스 레벨)
    const CACHE_DURATION = 60 * 60 * 1000; // 1시간
    
    if (cachedData && cacheTimestamp && now - cacheTimestamp < CACHE_DURATION) {
    	return cachedData; // Github에 요청하지 않고 즉시 반환
    }
    
    
    // 2차: Next.js ISR 캐시 (서버 레벨)
    const response = await fetch(FPB_JSON_URL, {
    	next: { revalidate: 3600 } // 1시간마다 백그라운드 재검증
    })

     

    이 두 캐시는 직렬로 연결되어 동작한다.

     

    1. 유저 요청이 들어오면 먼저 인메모리 캐시를 확인
    2. 인메모리 캐시가 만료되었으면 fetch 호출
    3. fetch는 Next.js ISR Cache를 먼저 확인하고, 만료되었으면 Github에서 새 데이터를 가져옴
    4. ISR의 stale-while-revalidate 전략에 따라, 기존 캐시를 즉시 반환하고 백그라운드에서 갱신

     

    유저가 새 데이터를 보기까지 걸리는 시간은?

    시나리오 소요시간
    최선 - 캐시만료직후 + fpb.json 이미 갱신됨 수 초
    일반적 - fpb.json 갱신 후 캐시 사이클 1회 대기 약 1시간
    최악 - fpb.json 변환 지연 + 캐시 갓 갱신된 직후 fpb.json 변환 시간 + 약 1시간
    Vercel 콜드 스타트 (서버 재시작) 즉시 최신 데이터 fetch

     

    중요한 점은 이 프로젝트가 직접 제어할 수 있는 지연은 최대 1시간이라는 것이다.

    그 앞단의 "마크다운 → fpb.json 변환"은 EbookFoundation 측의 CI/CD 파이프라인에 달려 있어서 해당 프로젝트의 제어 범위 밖이다.

     

    이 방식의 장점은 다음과 같다.

     

    자동 동기화: 원본이 업데이트 되면 재배포 없이 최대 1시간 내에 반영된다

    데이터베이스 비용 절감: 수천 건의 레코드를 저장하고 관리할 필요가 없다

    빌드 시간 단축: 정적 생성 시 모든 책을 미리 가져올 필요 없다

    장애 내성: Github 장애 시에도 인메모리 캐시가 살아있다면 정상 서비스 된다

     

    원본 데이터는 깊이 중첩된 트리구조로 되어있어서 이를 재귀적으로 순회하며 평탄한 배열로 변환한다.

     


     

    국제화 시키기: 3개 UI 언어, 40개 콘텐츠 언어

    UI 언어를 영어, 한국어, 스페인어로 기본적으로 정해놓았다. 내가 생각했을 때 많은 유저가 사용하는 언어라고 생각해서이다.

    콘텐츠 언어는 약 40개 이상을 사용하고 있다.

     

    [locale] 동적 세그먼트를 이용해서 UI 라우팅을 처리하고, 도서 필터에서 콘텐츠 언어를 선택할 수 있게 했다.

    44개 언어를 국기 이모지와 해당 언어의 원어 이름으로 표시하는 LanguageSelector 컴포넌트가 이를 직관적으로 풀어준다.

     

     

    SEO: 정적 사이트 수준의 검색 최적화 시키기

    개발자 자료 사이트인만큼 검색 엔진 노출이 중요하다고 생각했다.

     

    • 구조화된 데이터: WebSite, WebApplication, Organization 세 가지 JSON-LD 스키마
    • 동적 사이트맵: 모든 카테고리와 언어 조합으로 수백 개의 URL 자동 생성
    • OG 이미지 자동 생성: 소셜 미디어 공유 시 카드 이미지를 동적으로 생성하게 만들었다.
    • 다국어 alternate 태그: en, ko, es hreflang 설정
    • SearchAction: Google 검색에서 직접 사이트 내 검색이 가능하도록 구조화

     

    빌드 시 243개의 정적 페이지가 생성되어 크롤러가 효율적으로 인덱싱할 수 있게 했다.

     

     

    성능 최적화 시키기

    Lazy Database 초기화

    Drizzle ORM 클라이언트를 Proxy Pattern으로 지연 초기화를 시켰다.

    DB Module은 API Handler 내부에서 동적 import(await impot("@/lib/db"))로 불러오기 때문에, 빌드 시점이나 DB가 필요없는 코드 경로에서는 Turso 클라이언트가 절대 인스턴스화 되지 않는다.

    클라이언트 사이드 페이지네이션

    "더보기" 기능은 추가 네트워크 요청 없이 순수 클라이언트 상태로 구현했다.

    전체 데이터를 이미 가지고 있으므로 slice 시키는 것만으로도 충분하다고 판단했다.

    광고 로딩 최적화

    Kakao Adfit을 사용하고 있다. strategy="lazyOnload"로 로등하여 페이지 렌더링을 차단하지 않고, DOM에 미리 공간을 확보하여 CLS(Cumulative Layout Shift)를 방지하도록 했다.

     

     


     

     

    free-coders-books

    회고

    이건 잘했다! 👍

    • 데이터베이스 없는 핵심 기능 설계: 외부 데이터를 ISR로 가져오는 패턴은 비용과 복잡도를 크게 줄일 수 있었다.
    • Graceful Degradation: DB 없이도 작동하는 설계 덕분에 개발과 배포가 유연해졌다.
    • 보안 우선 설계: 처음부터 Rate Lmiting, Zod 검증, XSS 방지를 적용

    도전적이었다.... 😅

    • 최신기술스택 고집: 최신 기술 스택을 사용하다보니 Breaking Change에 대응해야했다.
      (카카오 애드핏을 붙일 때 자꾸 에러가 났다...)
    • 외부 데이터 의존성: Github 서버 장애 시 데이터를 가져올 수 없는 리스크가 있었고, 이를 인메모리 캐시로 완화했다.
    • Vitest 빌드 충돌: 테스트 파일이 프로덕션 빌드에 포함되어 vi글로벌을 찾을 수 없다는 에러가 발생했고, tsconfig.json에서 테스트 파일을 exclude하여 해결했다.

     

     


     

     

    FREE-CODERS-BOOKS 프로젝트를 보다가 버그가 있거나 기능 추가가 필요하다면 언제든지 알려주세요!

    반응형
Designed by Tistory.