birchholt 직접 해보고 남기는 기록

Astro로 만든 사이트를 하루 만에 Hugo로 갈아엎었습니다

이 사이트는 처음에 Astro로 만들었습니다. 그리고 같은 날 걷어내고 Hugo로 다시 만들었습니다.

반쯤 해체된 헛간에서 새로 지은 옆 헛간으로 판자를 나르는 사람

부끄러운 쪽부터 적습니다. 갈아엎은 이유는 Astro가 나빠서가 아니라 제가 고를 때 대안을 나란히 놓고 따져보지 않았기 때문입니다.

처음 든 근거 셋, 전부 결정적이지 않았습니다

양쪽 접시에 같은 추 세 개씩 올라가 수평을 이룬 저울과 추를 놓는 손

Astro를 고르면서 제가 든 이유는 세 개였습니다.

  1. Node가 이미 깔려 있다
  2. 글 앞머리에 적는 항목의 형식을 미리 정해두고 어긋나면 잡아준다
  3. Cloudflare가 공식 지원한다

“다른 정적 생성기도 많지 않나"라는 말을 듣고서야 비교를 해봤는데 셋 중에 결정적인 것이 하나도 없었습니다.

처음 든 근거따져보니
Node가 이미 있다있으면 편한 것이지, 고를 이유는 아닙니다
스키마 검증Hugo도 빌드 스크립트로 됩니다. 직접 짜야 할 뿐입니다
Cloudflare 공식 지원Hugo도 마찬가지입니다

“Node가 이미 있다"는 특히 이상한 근거였습니다. 이미 가진 것을 이유로 대면 뭘 골라도 그 이유가 성립합니다.

나란히 놓고 재보니

소포 더미와 상자 하나, 모래시계 두 개를 가운데서 나란히 비교하는 사람

말로 따지는 것보다 재는 게 빨랐습니다. 같은 글 3편, 같은 내용으로 두 번 만들어봤습니다.

AstroHugo
설치되는 npm 패키지278개0개 (단일 바이너리)
빌드 시간877ms10ms

측정: 2026-09-01, 같은 맥(Apple Silicon), 같은 글 3편. 패키지 수는 package-lock.json의 의존성 항목, 빌드 시간은 각 생성기의 빌드 명령 출력값. 기계와 글 편수가 바뀌면 값도 바뀝니다.

빌드 속도는 사실 신경 쓰지 않았습니다. 글 서너 편에서 무슨 차이가 나겠나 싶었는데 88배가 날 줄은 몰랐습니다. 물론 877ms도 기다릴 만한 시간이라 이게 결정타는 아니었지만 “차이 없을 것"이라는 제 짐작은 틀린 걸로 확인됐습니다.

덤으로 나온 실수 하나. astro@^5로 깔았더니 5.18.2가 설치됐는데 그때 최신은 7.2.7이었습니다. 튜토리얼에 적힌 버전 범위를 그대로 복사했다가 두 메이저 뒤처진 걸 깔아놓고 시작한 겁니다.

“이거 계속 관리될까요”

굵은 기둥 하나에 선 다리와 한 받침돌 위 네 기둥에 선 다리

무엇보다 이 질문을 했어야 했습니다. 블로그는 몇 년을 굴릴 물건이니까요.

GitHub API로 두 프로젝트의 커밋을 기여자별로 집계해봤습니다.

Hugo

기여자커밋비중
bep5,85565.4%
dependabot[bot]6437.2%
spf13 (창시자)4665.2%
jmooring4124.6%

Astro

기여자커밋비중
matthewp2,11415.8%
astrobot-houston1,63812.3%
ematipico1,1908.9%
FredKSchott1,0848.1%

측정: 2026-09-21, GET /repos/{owner}/{repo}/contributors?per_page=100&anon=0. 비중의 분모는 그 응답에 담긴 기여자들의 커밋 합계입니다(Hugo 8,946 · Astro 13,338). 저장소 전체 커밋 수가 아니라 상위 100명 기준이라는 뜻이고, 두 저장소를 같은 자로 잰 값입니다. 2026-09-01 첫 측정치와 순위·구도는 같았고 절댓값만 늘었습니다.

이 표는 Hugo에 유리하지 않습니다. 오히려 반대입니다. 한 사람이 65%를 쓰고 있고 2위는 봇이고 창시자는 손을 뗐습니다(마지막 커밋 2016-10-07). 이 프로젝트는 사실상 한 사람에게 매달려 있습니다. Astro는 넷이 고르게 나눠 쓰고 있어서 이 축으로는 Astro가 낫습니다.

다만 이 넷이 서로 독립적이지는 않습니다. 2위는 봇이고, 사람 셋 중 둘(matthewp·FredKSchott)이 같은 회사 The Astro Technology Company 소속입니다. ematipico 만 프로필에 다른 회사를 적어뒀습니다. 분산돼 보여도 절반은 회사 하나에 묶여 있으니, 리스크는 이쪽에도 있고 종류만 다릅니다.

소속은 2026-09-21 GitHub 프로필의 company 필드입니다 — matthewp·FredKSchott 「The Astro Technology Company」, astrobot-houston 「@withastro」(봇), ematipico 「@Cloudflare」. 처음에는 넷 다 회사 소속이라고 적었는데 재보니 아니었습니다.

릴리스 패턴도 같이 봤습니다.

시작버전(2026-09-01)읽히는 것
Hugo2013-07v0.165.013년째 0.x. 버전 번호로 변화 크기를 알리는 방식을 안 쓰고 조금씩 갑니다
Astro2021v7.2.75년에 메이저 7개 = 연 1~1.5회 기존 설정이 깨지는 변경

연수는 저장소 생성일 기준입니다 — Hugo 2013-07-04(GitHub API created_at), 2026-09 기준 13년 2개월. 버전은 둘 다 2026-09-01 시점 값입니다. 이후 최신은 Hugo v0.166.0(2026-09-22 확인), Astro 7.3.3(2026-09-21 확인)입니다.

“중간에 갈아엎을 일이 생기지 않을까"가 원래 Astro 쪽 걱정이 아니라 Hugo 쪽 걱정이었는데 숫자로는 반대였습니다.

정작 안 본 것: 이 사이트가 무엇을 쓰는가

여기까지는 도구끼리 비교했습니다. 정작 먼저 봤어야 할 질문은 제가 무엇을 만들려고 하는가였습니다.

Astro가 값을 하는 자리는 화면을 조각으로 나눠 만들고 그중 필요한 조각에만 JS를 실어 보내는 기능입니다. React든 Svelte든 섞어 쓰는데 그 조각들이 맞물려 도는 것을 빌드가 관리해 줍니다. 좋은 기능인데 이 사이트를 열어 세어보면 이렇습니다.

이 사이트가 쓰는 것수
JS 프레임워크(React·Vue·Svelte…)0
JS로 움직이는 화면 조각0
직접 만든 .js 파일0
<script> 태그광고·웹 분석·JSON-LD 셋뿐 (전부 외부 스니펫)

Astro가 의존성 278개와 연 1회 이상의 메이저 변경을 받아 가며 주는 것을 이 사이트는 하나도 쓰지 않습니다. 글을 쓰면 HTML이 나오면 되는 물건이었습니다.

처음에 저는 이 자리를 못 봤습니다. 어느 도구가 더 나은가를 따지느라 내가 쓸 기능에 값을 치르고 있는가를 묻지 못했습니다. “Node가 이미 있다"가 이상한 근거였던 것과 같은 종류의 실수를 또 했습니다.

나중에 상호작용이 필요해지면요? 정적 페이지에 스크립트 한 장 얹는 일은 어느 생성기에서든 되니 그때 붙이면 됩니다. 반대로 쓰지도 않을 기능의 유지비는 매달 나갑니다 — 의존성 업데이트, 메이저 변경 따라가기, 빌드가 깨졌을 때의 추적.

남은 리스크는 종류로 골랐습니다

열쇠를 담은 유리병을 선반에 두는 사람과 금 간 고리가 섞인 긴 사슬

적합도로 한쪽이 기울었어도 유지보수 리스크는 남습니다. 앞에서 봤듯 그 축은 Astro가 낫고, Hugo는 핵심 개발자 한 사람이 손을 떼면 멈추는 구조입니다.

그래서 질문을 바꿨습니다. “그 리스크가 터지면 나는 무엇을 할 수 있나.”

개발이 멈추면내가 할 수 있는 것
Hugo새 기능이 안 나옵니다받아둔 바이너리로 계속 빌드됩니다. 인터넷도 필요 없습니다
Astro의존성 갱신이 멈춥니다npm install이 깨지면 빌드 자체가 안 됩니다. 278개 중 하나만 사라져도

같은 멈춤인데 남는 게 다릅니다. 한쪽은 기능이 멈추고 다른 쪽은 사이트를 다시 만들 수 없게 됩니다.

정직하게 붙여둘 조건이 하나 있습니다. 이 비교는 「새 기능을 계속 받고 싶다」가 요구사항이 아닐 때만 성립합니다. 빠르게 발전하는 기능이 필요한 사이트라면 한 사람에게 매달린 구조가 그대로 결격 사유가 됩니다. 저는 그걸 요구사항으로 두지 않았습니다.

그런데 이 결정은 되돌리기 쌉니다

화분째 들어 다른 모양의 화단으로 옮기는 정원사와 화분 실은 손수레

마지막으로 틀렸을 때 얼마나 손해인가도 확인했습니다. 이건 Hugo를 고른 이유가 아닙니다. 고민을 어디서 끊을지 정한 근거입니다.

글이 전부 마크다운이라 어느 생성기로든 그대로 옮겨갑니다. 글 맨 위 설정 블록의 항목 이름만 맞추면 됩니다. 실제로 이번에 옮기는 데 든 건 글 1편 + CSS 1개 이식 + 레이아웃 다시 짜기 30분이었습니다. 글이 50편이어도 마크다운은 그대로 갑니다.

되돌리기 싼 결정에 오래 고민하는 게 더 손해라 여기서 확정하고 넘어갔습니다. 순서를 정리하면 이렇습니다 — ① 내가 쓸 기능이 무엇인지 보고(적합도) ② 남는 리스크는 터졌을 때 뭐가 남는지로 고르고 ③ 되돌리는 비용이 싸니 거기서 끊었습니다. 마크다운이라 이사가 쉽다는 건 ③이지 ①이 아닙니다.

옮기고 나서 밟은 것 넷

옛 지도를 든 채 방향이 바뀐 갈림길 표지판 앞에 선 등산객

Hugo가 13년째 0.x라는 건, 오래된 튜토리얼이 그대로 안 먹는다는 뜻이기도 합니다. 검색해서 나온 대로 따라 하다 걸린 것들입니다.

1. v0.146.0에서 템플릿 시스템이 통째로 바뀌었습니다. layouts/_default/가 없어지고 partials/ → _partials/, shortcodes/ → _shortcodes/로 바뀌었는데 검색 상위에 뜨는 글들은 대부분 이 이전 것이라 구조부터 안 맞습니다.

2. languageCode는 v0.158.0에서 locale로 바뀌었습니다. 경고만 뜨고 빌드는 되기 때문에 한동안 모르고 지나갑니다.

둘 다 릴리스 노트에서 확인했습니다(2026-09-22 확인) — v0.146.0 「fully refreshed template system」, v0.158.0 「languageCode → Use locale instead」(제거가 아니라 deprecation 목록입니다).

3. 글 본문에 HTML 태그를 직접 쓰려면 설정을 켜야 합니다.

[markup.goldmark.renderer]
  unsafe = true

이 글에 있는 회색 박스가 그것 때문에 필요했습니다. 안 켜면 태그가 조용히 사라집니다.

4. disableKinds는 루트 레벨에 둬야 합니다.

이게 제일 오래 헤맸습니다. 태그 페이지를 끄려고 disableKinds를 넣었는데 태그 페이지가 계속 생성됐습니다. 오타도 아니고 오류도 없었습니다. [params] 같은 섹션 안에 들어가 있으면 에러 없이 그냥 무시됩니다.

# 이건 동작합니다 — 루트 레벨
disableKinds = ['taxonomy', 'term']

[params]
  description = '...'

왜 태그 페이지를 껐는가: 글이 적을 때 태그 분류를 켜두면 글 한 편짜리 페이지가 태그 수만큼 생깁니다. 목록에 제목 하나만 있는 페이지들이 사이트의 절반이 됩니다. 글이 쌓이면 그때 켜려고 합니다.

정리

두 나침반을 나란히 놓고 돋보기로 살피는 사람과 가지런한 공책들