의존성 두 개로 정적 블로그 생성기 만들기

Hugo 나 Eleventy 대신 200줄짜리 Node 스크립트로 블로그를 빌드하는 이유와 구조.

이 블로그는 별도의 정적 사이트 생성기(SSG) 없이 짧은 Node 스크립트로 빌드됩니다. 의존성은 markdown-ithighlight.js 둘뿐입니다.

왜 직접 만드는가

Hugo, Eleventy, Astro 모두 훌륭한 도구입니다. 하지만 원고가 HTML 이나 Markdown 이고 필요한 출력이 "글 페이지 + 카테고리 목록 + RSS + 사이트맵" 정도라면, 도구의 설정 체계를 배우는 비용이 직접 짜는 비용보다 큽니다. 무엇보다 구조를 완전히 통제할 수 있어서, 나중에 자동화된 파이프라인이 글을 밀어 넣을 때 다루기 쉽습니다.

구조

content/
  posts/     글 (.md 또는 .html, 프론트매터 필수)
  pages/     소개·개인정보처리방침 같은 고정 페이지
templates/   레이아웃 (JS 템플릿 리터럴)
static/      CSS, 파비콘 등 그대로 복사되는 파일
scripts/
  build.mjs  빌드
  serve.mjs  로컬 미리보기
  new.mjs    새 글 스캐폴드
site.config.json

빌드 흐름

  1. content/posts/ 의 파일을 읽어 프론트매터를 파싱한다.
  2. Markdown 은 markdown-it 으로, HTML 은 그대로 쓰되 코드 블록만 하이라이트한다.
  3. 날짜 역순으로 정렬해 글 페이지, 홈, 카테고리 목록(페이지네이션)을 만든다.
  4. feed.xml, sitemap.xml, robots.txt, ads.txt 를 생성한다.

프론트매터 파서는 YAML 전체가 아니라 필요한 부분집합만 구현했습니다.

export function parseFrontmatter(raw) {
  const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
  if (!m) return { data: {}, body: raw };
  const data = {};
  for (const line of m[1].split(/\r?\n/)) {
    const i = line.indexOf(':');
    data[line.slice(0, i).trim()] = parseValue(line.slice(i + 1).trim());
  }
  return { data, body: raw.slice(m[0].length) };
}

배포 대상이 둘인 이유

같은 원고를 공개 서버(도메인 루트)와 내부 서버(/a/blog-ai-runner/ 하위 경로)에 동시에 올립니다. 정적 파일이라 배포처를 늘리는 건 어렵지 않지만, 링크가 루트 기준이면 하위 경로에서 깨집니다. 그래서 빌드가 --target 에 따라 base path 를 바꿔 냅니다.

항목 public internal
base / /a/blog-ai-runner/
광고 끔 (자기 클릭 방지)
robots 허용 noindex
canonical 공개 URL 공개 URL (동일)

내부 빌드에서도 canonical 은 공개 URL 을 가리킵니다. 중복 콘텐츠로 취급되지 않게 하기 위해서입니다.