TECH 으로 돌아가기
TECH HACKER NEWS 어제 8분 읽기 28 READS

Zig에서 컴파일 타임에 검증되는 HTML 템플릿, zt가 던지는 질문

저수준 시스템 언어로 알려진 Zig에서 웹 프런트엔드용 HTML 템플릿을 다룬다는 발상은 낯설다. 그러나 2000년 PHP 3.0 시절부터 raw PHP, Python 문자열 연결, Smarty, Perl의 Template Toolkit, Jinja/Django 등 온갖 방식으로 HTML을 만들어 온 한 개발자는, Zig로 자신의 백엔드 생태계(zio, Dusty)를 구축하던 중 이 언어로도 웹 개발이 충분히 현실적이라는 결론에 도달했다. 그 결과물이 Go의 Templ을 Zig로 옮긴 템플릿 라이브러리 zt다. 이 글은 zt가 어떤 기술적 선택을 했고, 그것이 한국의 서버·백엔드 실무자에게 어떤 의미가 있는지 짚어본다.

아레나 할당자와 comptime이 연 가능성

zt가 성립하는 전제는 두 가지 Zig 기능이다. 하나는 아레나(arena) 할당자다. 웹 응답을 만드는 과정에는 수많은 임시 문자열이 생기는데, 요청 단위로 메모리를 통째로 잡았다가 응답이 끝나면 한꺼번에 해제하는 아레나 방식을 쓰면 개별 문자열의 해제를 일일이 챙길 필요가 없다. 수동 메모리 관리 언어에서 웹 개발이 부담스러웠던 이유 하나가 이렇게 줄어든다. 다른 하나는 comptime, 즉 컴파일 시점 실행이다. 이를 통해 런타임 성능을 희생하지 않으면서도 고수준 인터페이스를 만들 수 있다. 저자는 여기에 htmx나 Datastar를 결합하면 JavaScript나 TypeScript를 건드리지 않고도 SPA 형태의 앱을 구성할 수 있다는 점을 강조한다.

빠진 조각은 템플릿이었다. 문자열 연결은 선택지가 아니었고, 저자는 자신의 요구를 모두 만족하는 기존 해법을 찾지 못했다. 그 과정에서 JavaScript 바깥에서도 JSX식 접근이 가능하다는 것을 보여준 Zig 프로젝트 Ziex(Nurul Huda)와, Go 진영의 Templ을 접하면서 만들고 싶은 모습이 분명해졌다고 한다.

템플릿을 Zig 코드로 트랜스파일한다는 설계

zt의 핵심은 템플릿을 런타임에 해석하지 않는다는 점이다. zig build 단계에서 템플릿을 파싱해 Zig 코드로 트랜스파일한다. 그래서 앱 코드에서 템플릿을 직접 import할 수 있을 뿐 아니라, 반대로 템플릿이 앱 코드를 import할 수도 있다. 이 양방향 연결 덕분에 템플릿에 넘기는 모든 파라미터가 완전히 타입이 붙는다. 렌더링할 수 없는 값이 있으면 Zig 컴파일러가 그 자리에서 알려준다. 생성되는 코드 자체는 단순해서, 결국 std.Io.Writer에 쓰는 일련의 쓰기 동작으로 귀결된다. 출력을 소켓에 바로 쓸지, 버퍼에 모을지는 사용하는 쪽이 정한다.

이 설계가 주는 실질적 이점은 오류의 성격이 바뀐다는 데 있다. 템플릿에서 {u.name}을 {u.nmae}로 잘못 적으면 런타임에 빈 값이 찍히거나 조용히 넘어가는 대신, zig build가 컴파일 오류를 낸다. 오류는 생성된 코드를 가리키지만 각 줄에 원본 템플릿의 위치가 주석으로 남아 추적이 가능하다. 호출하는 쪽도 마찬가지여서, ?User를 기대하는 템플릿에 문자열을 넘기면 컴파일되지 않는다. 템플릿 오타와 타입 불일치가 배포 후 장애가 아니라 빌드 실패로 앞당겨진다는 것은, Jinja나 ERB처럼 런타임에 평가되는 템플릿에 익숙한 개발자에게는 분명한 전환이다.

기본값이 안전한 방향으로 설정된 이스케이프

보안 측면에서 zt는 모든 동적 값을 기본적으로 HTML 이스케이프한다. 본문뿐 아니라 속성값에도 적용되며, href나 src 같은 URL 속성은 위험한 스킴이 들어왔는지 추가로 검사한다. 날것의 HTML을 내보내려면 {!value}처럼 명시적으로 요청해야 하므로, 위험한 지점이 템플릿에서 눈에 띄게 드러난다. 또 파서가 모든 요소를 제대로 닫도록 강제하기 때문에 실수로 깨진 마크업을 만들기 어렵다. 저자 스스로 '모든 것을 막아주지는 않는다'고 선을 긋지만, XSS의 가장 흔한 원천을 제거한다는 점은 실무에서 의미가 있다. 안전한 쪽이 기본이고 위험한 쪽이 명시적이어야 한다는 설계 원칙에 부합한다.

기능적으로는 기본기 이상을 지원한다. 재사용 가능한 컴포넌트를 값처럼 주고받을 수 있고, @children을 쓰면 다른 템플릿을 감싸는 레이아웃을 만들 수 있어 다른 언어의 템플릿 상속이 하던 역할을 대체한다. 제어 흐름은 그냥 Zig이므로 if, for, switch가 페이로드 캡처까지 포함해 그대로 동작하며, 이 모든 것이 할당 없이 쓰기 동작의 나열로 컴파일된다.

남은 과제와 받아들일 때의 유의점

다만 문법에는 아직 열려 있는 지점이 있다. 블록 수준 제어 흐름을 Templ처럼 평범한 Zig로 적다 보니, if·for·switch로 시작하는 줄은 코드로 파싱된다. 그래서 "비밀번호를 잊으셨다면…"처럼 if로 시작하는 문장을 한 줄에 그냥 두면 파싱 오류가 난다. 요소로 감싸거나 문자열 표현식으로 써야 한다. 저자는 Razor나 Twirl을 참고해 @if, @for, @switch로 바꿔 모호함을 없애는 방안을 고민 중이며, 대신 템플릿이 다소 번잡해지는 비용을 언급하면서 GitHub 이슈에서 의견을 받고 있다.

현실적으로 zt는 아직 성숙한 프로젝트가 아니다. 이미 여러 사람이 쓰고 있다고는 하지만 저자 본인이 성숙도가 낮다며 피드백을 요청하는 단계다. htmx 기반의 TODO 예제가 Dusty에 포함돼 동작을 확인할 수 있는 수준이라, 당장 기존 Python·Go 스택을 대체할 후보라기보다는 Zig로 백엔드와 간단한 서버 렌더링을 함께 가져가려는 이들이 눈여겨볼 선택지에 가깝다. 그럼에도 '템플릿 오류를 컴파일 오류로 끌어올린다'는 방향성은, 언어를 떠나 서버 사이드 렌더링에서 타입 안전성을 어디까지 밀어붙일 수 있는지에 대한 구체적인 사례로서 참고할 가치가 있다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://lalinsky.com/2026/10/11/type-safe-html-templating-in...
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...