의존성 두 개로 정적 블로그 생성기 만들기
Hugo 나 Eleventy 대신 200줄짜리 Node 스크립트로 블로그를 빌드하는 이유와 구조.
이 블로그는 별도의 정적 사이트 생성기(SSG) 없이 짧은 Node 스크립트로 빌드됩니다.
의존성은 markdown-it 과 highlight.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
빌드 흐름
content/posts/의 파일을 읽어 프론트매터를 파싱한다.- Markdown 은
markdown-it으로, HTML 은 그대로 쓰되 코드 블록만 하이라이트한다. - 날짜 역순으로 정렬해 글 페이지, 홈, 카테고리 목록(페이지네이션)을 만든다.
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 을 가리킵니다. 중복 콘텐츠로 취급되지 않게 하기 위해서입니다.