검색

1000PAGE 지식 저장소

Astro와 Cloudflare Pages로 만든 개인 AI 학습 지식 저장소 겸 포트폴리오 사이트

진행 중
  • Astro
  • TypeScript
  • Markdown
  • Pagefind
  • Cloudflare Pages

프로젝트 개요

AI·머신러닝 학습 내용이 ChatGPT 대화, 노션, PDF, 로컬 폴더에 흩어져 있어 다시 찾기 어려웠다. 이를 하나의 사이트로 모으고, 동시에 취업용 포트폴리오로도 쓸 수 있게 만든 정적 사이트다.

지금 보고 있는 이 사이트가 그 결과물이다.

문제 정의

학습 자료가 흩어지면 세 가지 문제가 생긴다.

  1. 다시 못 찾는다 — 어디에 적었는지 기억나지 않아 같은 내용을 반복해서 공부한다
  2. 설명할 수 없다 — 조각난 메모는 남에게 보여줄 수 없다
  3. 연결되지 않는다 — 개념과 실제 프로젝트가 따로 논다

단순한 블로그로는 3번이 해결되지 않는다. 문서끼리, 그리고 문서와 프로젝트가 서로 연결되는 구조가 필요했다.

주요 사용자

사이트 운영자 본인이 1차 사용자다. 2차 사용자는 채용 담당자와 면접관이다. 이 둘의 요구가 다르다.

사용자필요한 것
본인빠른 검색, 카테고리 탐색, 쉬운 글쓰기
채용 담당자프로젝트 구조, 기술 선택 이유, 문제 해결 과정

요구사항

기능

  • Markdown 파일을 넣으면 자동으로 페이지 생성
  • 전체 문서 검색
  • 카테고리·태그 기반 탐색
  • 브라우저에서 글 작성(CMS)
  • 다크모드, 모바일 대응

제약

  • 운영 비용 0원
  • 콘텐츠가 특정 서비스에 종속되지 않을 것
  • 서버 관리를 하지 않을 것

기술 스택

기술 선택의 이유가 이 프로젝트의 핵심이다.

기술용도선택 이유
Astro사이트 프레임워크기본이 정적 HTML이라 JS가 거의 나가지 않는다. Content Collections가 프론트매터를 타입 검증해준다
Markdown콘텐츠Git으로 버전관리되고 서비스 종속이 없다. 이전 비용이 사실상 0
Pagefind검색서버도 API 키도 필요 없다. 빌드 결과물을 훑어 인덱스를 만든다
Cloudflare Pages배포무료 대역폭 제한이 사실상 없고, 향후 Workers/D1로 백엔드를 붙이기 쉽다
Shiki코드 하이라이트빌드 타임에 처리되어 런타임 JS가 0

React를 쓰지 않았다. 화면에 상태 관리가 필요한 부분이 없어 프레임워크를 넣으면 JS 번들만 늘어난다.

시스템 아키텍처

flowchart TD
    Admin[관리자] --> CMS[Git 기반 CMS]
    CMS --> GH[GitHub Repository]
    GH --> CF[Cloudflare Pages]
    CF --> Build[Astro 빌드 + Pagefind 색인]
    Build --> Site[정적 사이트]
    Visitor[방문자] --> Site

서버가 없다. 글을 쓰면 GitHub에 커밋되고, 그것이 빌드를 트리거해 정적 파일이 배포된다.

데이터 흐름

Markdown 파일
  → Content Collections (zod 스키마 검증)
  → getStaticPaths()로 라우트 생성
  → 정적 HTML
  → Pagefind가 HTML을 훑어 검색 인덱스 생성

스키마 검증이 빌드 단계에 있어, 프론트매터가 잘못되면 배포 전에 잡힌다.

핵심 기능

  • 자동 페이지 생성src/content/에 md를 넣으면 목록과 상세가 자동 생성
  • 카테고리 관리 — 카테고리가 코드가 아니라 파일이라 CMS에서 추가·순서변경 가능
  • 정적 검색 — 서버 없이 전문 검색
  • 다크모드 — FOUC 없이 즉시 전환
  • 문서 연결 — 태그·카테고리 기반 자동 추천 + 수동 지정

주요 코드

카테고리를 코드가 아닌 데이터로 뺀 부분이 설계의 핵심이다. 처음에는 z.enum으로 카테고리를 고정했는데, 그러면 카테고리를 추가할 때마다 코드를 고쳐야 했다.

// 카테고리 1개 = 파일 1개. 파일을 추가하면 사이트에 자동 반영된다.
const categories = defineCollection({
  loader: glob({ pattern: '*.{md,mdx}', base: './src/content/categories' }),
  schema: z.object({
    title: z.string(),
    order: z.number().default(99),  // 이 값으로 순서를 바꾼다
    icon: z.string().optional(),
  }),
});

대신 문서의 categoryz.enum을 쓸 수 없게 되어 오타 검증을 잃었다. 이를 보완하려고 빌드 시 경고를 내는 검증 함수를 넣었다.

실행 결과

지금 보고 있는 사이트 전체가 실행 결과다. 홈, 문서 목록, 카테고리, 태그, 검색이 모두 동작한다.

검증 방법

항목방법결과
빌드npm run build0 errors
배포GitHub push → 자동 빌드2~3분 내 반영
검색npm run preview 후 검색한국어 검색 동작
접근성키보드만으로 탐색전체 도달 가능

발생한 문제

glob 로더에 exclude 옵션이 있다고 가정하고 카테고리 폴더를 제외했는데, 빌드가 실패했다.

InvalidContentEntryDataError: docs → categories/agents
  category: Required

카테고리 파일이 문서 컬렉션으로 들어와 문서 스키마로 검증된 것이다.

해결 과정

  1. 오류 메시지의 docs → categories/agents를 보고 컬렉션이 겹쳤음을 파악
  2. node_modulesglob.d.ts를 직접 열어 타입 정의를 확인
  3. GlobOptionsexclude아예 없다는 것을 확인. pattern, base, generateId, retainBody, deferRender만 존재
  4. pattern이 배열을 받는 것을 이용해 부정 패턴으로 교체
pattern: ['**/*.{md,mdx}', '!categories/**', '!projects/**']

문서를 추측하지 않고 설치된 타입 정의를 직접 확인한 것이 해결의 핵심이었다.

한계

  • 카테고리를 CMS에서 관리하기 위해 문서 category의 스키마 검증을 느슨하게 했다. 오타가 빌드를 깨뜨리지 않고 경고로만 남는다
  • 관련 문서 추천이 O(N²)이라 문서가 1,000개를 넘으면 빌드가 느려진다
  • 한국어 검색에서 조사 처리가 안 된다. “임베딩을”과 “임베딩”의 결과가 다를 수 있다
  • 모바일에서 사이드바가 서랍이 아니라 본문 아래로 내려간다

향후 개선

  • 문서가 쌓이면 한국어 검색 품질을 실측하고, 부족하면 검색어 정규화를 추가
  • 관련 문서 계산에 태그 역인덱스를 도입해 O(N)으로 개선
  • AI 기반 자연어 검색(Cloudflare Workers + 벡터 DB)

GitHub

저장소는 비공개다. 초안이 커밋 히스토리에 남기 때문이다.

데모

이 사이트 자체가 데모다.

발표 자료

없음.