HTMX와 함께 사용할 Go 템플릿 기술 비교: html/template vs. Templ
이 글은 Claude Opus 4.6 을 이용해 초안이 작성되었으며, 이후 퇴고를 거쳤습니다.
Go로 SSR 웹 앱을 만들 때 HTMX와 함께 사용할 템플릿 기술로 두 가지 선택지가 있습니다.
- Go 표준
html/template+ 커스텀 PageRenderer (페이지별 독립 템플릿 세트) - Templ
이 글에서는 두 방식의 장단점을 실무 관점에서 비교합니다.
타입 안전성#
가장 큰 갈림길입니다.
html/template은 런타임에 템플릿을 파싱하고 데이터를 바인딩합니다. {{ .Titl }}이라고 써도 빌드는 통과합니다. 런타임에 빈 문자열이 나오거나 에러가 발생하고 나서야 문제를 알 수 있습니다.
Templ은 .templ 파일이 Go 코드로 생성됩니다. 컴포넌트의 파라미터가 Go 함수 시그니처가 됩니다.
templ TodoItem(todo *model.Todo) {
<li>{ todo.Title }</li>
}
이렇게 선언하면 컴파일러가 타입을 체크합니다. 필드명 오타는 빌드 실패로 잡히고, IDE 자동완성도 동작합니다.
컴포넌트 모델과 조합성#
html/template에서 재사용 가능한 파셜을 만들려면 {{ define "todo-item" }}과 {{ template "todo-item" . }}을 사용합니다. 여기서 .으로 넘기는 데이터가 interface{}이기 때문에, 어떤 구조체가 들어오는지 템플릿 파일만 봐서는 알 수 없습니다.
커스텀 PageRenderer를 만들어서 페이지별로 독립 템플릿 세트를 구성하면 관리가 나아지지만, 데이터 흐름이 불투명한 근본적인 문제는 그대로입니다.
Templ에서는 Go 함수가 곧 컴포넌트입니다.
// 컴포넌트 호출
@TodoItem(todo)
// 슬롯 패턴
templ Layout() {
<main>
{ children... }
</main>
}
컴포넌트 간 데이터 흐름이 함수 호출과 동일하므로, 코드를 읽는 것만으로 의존관계가 명확합니다.
개발 경험 (DX)#
html/template의 장점: 추가 도구 불필요#
Go 표준 라이브러리만으로 동작합니다. 코드 생성 단계가 없고, go build만 하면 됩니다. 템플릿 파일을 embed.FS로 바이너리에 포함시키면 배포도 단순합니다.
Templ의 장점: 우수한 IDE 지원#
templ generate라는 코드 생성 단계가 추가됩니다. .templ → _templ.go 변환이 필요하고, air 같은 워치 도구에서 이 단계를 빌드 파이프라인에 넣어야 합니다. 외부 의존성이 하나 더 생기는 셈입니다.
다만 LSP 서버가 제공되어 VS Code에서의 자동완성, 에러 하이라이팅, 포매팅은 Templ이 훨씬 우수합니다.
HTMX와의 궁합#
HTMX 파셜 응답을 보낼 때 차이가 뚜렷합니다.
html/template + PageRenderer#
파셜 렌더링을 위해 보통 이런 패턴을 씁니다:
// 전체 페이지
tmpl.ExecuteTemplate(w, "page.html", data)
// HTMX 파셜 — 동일 템플릿의 특정 block만 렌더링
tmpl.ExecuteTemplate(w, "todo-list", data)
이 방식이 동작하려면 {{ define "todo-list" }}으로 명명된 블록이 미리 파싱된 템플릿 세트에 포함되어 있어야 합니다. 페이지별로 템플릿 세트를 관리하는 PageRenderer 패턴은 여기서 나오는 건데, 결국 “어떤 템플릿 세트에 어떤 block이 들어있는가"를 개발자가 머릿속에 들고 있어야 합니다.
Templ#
컴포넌트 자체가 독립적으로 렌더링 가능합니다:
// 전체 페이지
templates.Index(todos).Render(ctx, w)
// HTMX 파셜 — 동일 컴포넌트를 그대로 호출
templates.TodoList(todos).Render(ctx, w)
컴포넌트가 자기 완결적이라 파셜 렌더링에 별도 설정이 필요 없습니다. HTMX와의 조합에서는 Templ이 확실히 자연스럽습니다.
성능#
html/template은 초기 파싱 후 *Template을 재사용하면 성능이 괜찮지만, 내부적으로 reflection 기반 데이터 바인딩을 사용합니다.
Templ은 생성된 Go 코드가 io.Writer에 직접 쓰기 때문에 reflection이 없고, 벤치마크에서 일반적으로 더 빠릅니다.
다만 대부분의 웹 앱에서 템플릿 렌더링이 병목이 되는 경우는 드물어서, 이 차이가 기술 선택의 결정적 요인이 되지는 않습니다.
비교 정리#
| 기준 | html/template + PageRenderer |
Templ |
|---|---|---|
| 타입 안전성 | 런타임 (interface{}) |
컴파일 타임 (Go 함수 시그니처) |
| 외부 의존성 | 없음 (표준 라이브러리) | templ CLI 필요 |
| 코드 생성 | 없음 | templ generate 단계 필요 |
| IDE 지원 | 제한적 (Go 템플릿 문법) | LSP 서버, 자동완성, 포매팅 |
| HTMX 파셜 | ExecuteTemplate으로 named block 렌더링 |
컴포넌트 자체가 독립 렌더링 가능 |
| 컴포넌트 조합 | define/template (데이터 흐름 불투명) |
Go 함수 호출 (타입 명시적) |
| 학습 곡선 | Go 개발자라면 즉시 사용 가능 | Templ 문법 학습 필요 (30분~1시간) |
| 생태계 성숙도 | 10년+ (표준 라이브러리) | 비교적 신생 (2023~) |
어떤 걸 선택할 것인가#
프로젝트 규모가 작고 팀원 모두가 Go에 익숙하다면 html/template으로 충분합니다. 표준 라이브러리만으로 동작한다는 것은 의존성 관리와 빌드 파이프라인 측면에서 분명한 장점입니다.
하지만 다음 조건에 해당한다면 Templ을 고려할 가치가 있습니다:
- 템플릿이 20~30개 이상으로 늘어나는 규모
- HTMX 파셜 응답이 많아지는 구조
- 타입 안전성과 컴파일 타임 검증을 중시하는 팀
Templ의 타입 안전성과 컴포넌트 모델은 프로젝트 규모가 커질수록 실수를 줄여줍니다. 특히 HTMX와의 조합에서 컴포넌트가 독립적으로 렌더링 가능하다는 점은 파셜 응답이 많은 앱에서 코드 관리를 크게 단순화합니다.