O pacote html/template da standard library do Go funciona, mas tem limitações que incomodam em projetos maiores: erros de template só aparecem em runtime, não há autocompletar no editor, e a composição de componentes é manual e frágil. Qualquer typo no nome de um campo ou uma struct errada só explode quando o usuário acessa a página.
O Templ resolve esses problemas com uma abordagem diferente: você escreve templates em arquivos .templ com uma sintaxe próxima do Go, e o compilador gera código Go type-safe. Erros aparecem no build, não em produção. O editor mostra autocompletar, refatoração funciona, e componentes se compõem como funções normais.
Se você já trabalha com Go para aplicações web ou está montando interfaces server-side, Templ é a ferramenta que faltava no ecossistema.
O problema com html/template
Considere um template tradicional em Go:
// handler.go
func handler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/home.html"))
data := PageData{
Title: "Minha Página",
Items: []string{"Go", "Templ", "htmx"},
}
tmpl.Execute(w, data)
}
<!-- templates/home.html -->
<h1>{{ .Title }}</h1>
<ul>
{{ range .Items }}
<li>{{ . }}</li>
{{ end }}
</ul>
Os problemas aparecem rápido:
- Se você renomear
TitleparaNamena struct, o template compila sem erro e falha silenciosamente em runtime - Sem autocompletar – o editor não sabe quais campos a struct tem dentro do template
- Composição de componentes exige
template.Block,defineetemplateaninhados, que ficam verbosos - Sem checagem de tipos – passar um
intonde esperastringsó dá erro quando o template renderiza
Instalando e configurando o Templ
Instale o CLI do Templ:
go install github.com/a-h/templ/cmd/templ@latest
Para desenvolvimento, o Templ tem um modo watch que regenera código automaticamente:
templ generate --watch
No VS Code, instale a extensão templ-vscode para syntax highlighting e autocompletar dentro de arquivos .templ.
Seu primeiro componente Templ
Crie um arquivo components/hello.templ:
package components
// Hello renderiza uma saudação personalizada.
// O parâmetro name é verificado em tempo de compilação.
templ Hello(name string) {
<div class="greeting">
<h1>Olá, { name }!</h1>
<p>Bem-vindo ao Golang Brasil.</p>
</div>
}
Rode templ generate para gerar o código Go correspondente. O resultado é um arquivo hello_templ.go com uma função tipada que implementa templ.Component:
// Gerado automaticamente pelo templ -- não edite
func Hello(name string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
// ... código de renderização
})
}
Agora use o componente no seu handler:
package main
import (
"net/http"
"meuapp/components"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Type-safe: se você passar um int, o compilador avisa
components.Hello("Maria").Render(r.Context(), w)
})
http.ListenAndServe(":8080", nil)
}
Se você tentar components.Hello(42), o compilador Go rejeita na hora. Sem surpresas em produção.
Composição de componentes
Templ brilha na composição. Você pode aninhar componentes como funções:
package components
// Layout é o template base com header, conteúdo e footer.
templ Layout(title string) {
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ title } | Meu App</title>
<link rel="stylesheet" href="/static/style.css"/>
</head>
<body>
@Header()
<main class="container">
{ children... }
</main>
@Footer()
</body>
</html>
}
// Header renderiza a navegação principal.
templ Header() {
<header class="site-header">
<nav>
<a href="/">Início</a>
<a href="/sobre">Sobre</a>
<a href="/contato">Contato</a>
</nav>
</header>
}
// Footer renderiza o rodapé do site.
templ Footer() {
<footer class="site-footer">
<p>© 2026 Meu App. Feito com Go e Templ.</p>
</footer>
}
E usar o layout em uma página:
package pages
import "meuapp/components"
// HomePage renderiza a página inicial com o layout padrão.
templ HomePage(userName string, posts []Post) {
@components.Layout("Início") {
<section class="hero">
<h1>Olá, { userName }</h1>
</section>
<section class="posts">
for _, post := range posts {
@PostCard(post)
}
</section>
}
}
// PostCard renderiza um card de post individual.
templ PostCard(post Post) {
<article class="card">
<h2>{ post.Title }</h2>
<p>{ post.Summary }</p>
<a href={ templ.SafeURL("/posts/" + post.Slug) }>Ler mais</a>
</article>
}
A composição @components.Layout("Início") { ... } funciona como children em React, mas com zero overhead de JavaScript.
Integração com htmx
Templ e htmx formam uma dupla poderosa para aplicações web interativas sem JavaScript pesado. O htmx faz requisições AJAX e atualiza o DOM parcialmente, enquanto Templ gera os fragmentos HTML no servidor.
package components
// SearchBar renderiza uma barra de busca com htmx.
templ SearchBar() {
<div class="search">
<input
type="search"
name="q"
placeholder="Buscar..."
hx-get="/api/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results"
/>
<div id="results"></div>
</div>
}
// SearchResults renderiza os resultados da busca.
// Retornado como fragmento HTML para o htmx.
templ SearchResults(results []SearchResult) {
if len(results) == 0 {
<p class="empty">Nenhum resultado encontrado.</p>
} else {
<ul class="results-list">
for _, r := range results {
<li>
<a href={ templ.SafeURL(r.URL) }>
<strong>{ r.Title }</strong>
<span>{ r.Description }</span>
</a>
</li>
}
</ul>
}
}
O handler no servidor retorna apenas o fragmento HTML:
func searchHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
results := searchService.Search(query)
// Retorna apenas o fragmento, sem o layout completo
components.SearchResults(results).Render(r.Context(), w)
}
O htmx recebe o HTML e substitui o conteúdo do #results automaticamente. Sem JSON, sem parsing no cliente, sem framework de frontend.
Condicionais e loops nativos
Templ usa a sintaxe do Go para controle de fluxo, sem inventar uma linguagem de template nova:
package components
// UserProfile renderiza o perfil com badges condicionais.
templ UserProfile(user User) {
<div class="profile">
<h2>{ user.Name }</h2>
if user.IsAdmin {
<span class="badge admin">Administrador</span>
} else if user.IsModerator {
<span class="badge mod">Moderador</span>
}
if len(user.Skills) > 0 {
<div class="skills">
<h3>Habilidades</h3>
<ul>
for _, skill := range user.Skills {
<li>{ skill }</li>
}
</ul>
</div>
}
switch user.Level {
case "junior":
<span class="level">Júnior</span>
case "pleno":
<span class="level">Pleno</span>
case "senior":
<span class="level">Sênior</span>
}
</div>
}
if, for, switch – tudo funciona como no Go. Sem aprender {{ if }}, {{ range }}, {{ with }} e suas peculiaridades.
CSS e JavaScript inline
Templ suporta CSS e JavaScript com escopo por componente:
package components
// StyledButton renderiza um botão com estilo isolado.
templ StyledButton(label string) {
<style>
.btn-primary {
background-color: #00ADD8;
color: white;
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s ease;
}
.btn-primary:hover {
background-color: #0097B9;
}
</style>
<button class="btn-primary" onclick="handleClick(this)">
{ label }
</button>
<script>
function handleClick(el) {
el.textContent = "Clicado!";
}
</script>
}
O CSS gerado é emitido uma única vez por componente, mesmo que você use o componente várias vezes na página.
Comparação com alternativas
| Recurso | html/template | Templ | React/Next.js |
|---|---|---|---|
| Type-safety | Não | Sim | Sim (TSX) |
| Checagem no build | Não | Sim | Sim |
| Autocompletar | Não | Sim | Sim |
| Tamanho do bundle JS | 0 KB | 0 KB | 100+ KB |
| Composição | Verbosa | Natural | Natural |
| Curva de aprendizado | Baixa | Baixa | Alta |
| Servidor necessário | Go | Go | Node.js |
Templ ocupa o espaço ideal: type-safety de frameworks modernos com a simplicidade e performance do Go server-side. Sem Node.js, sem build tools JavaScript, sem hydration.
Organizando um projeto com Templ
Uma estrutura recomendada para projetos Go com Templ:
meuapp/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handlers/
│ │ └── home.go
│ └── services/
│ └── user.go
├── components/
│ ├── layout.templ
│ ├── header.templ
│ ├── footer.templ
│ └── cards.templ
├── pages/
│ ├── home.templ
│ ├── about.templ
│ └── contact.templ
├── static/
│ └── style.css
├── go.mod
└── Makefile
No Makefile, adicione um target para gerar código:
generate:
templ generate
dev:
templ generate --watch &
go run ./cmd/server
build:
templ generate
go build -o bin/server ./cmd/server
Quando usar Templ
Templ faz sentido quando:
- Você quer aplicações web em Go sem depender de frameworks JavaScript
- Erros de template em produção já causaram problemas reais no seu time
- Você precisa de composição de componentes sem a verbosidade de
html/template - Sua stack já usa htmx ou quer server-side rendering puro
Se você está construindo uma SPA complexa com estado pesado no cliente, React ou Vue continuam sendo a melhor escolha. Mas para sites, dashboards, painéis administrativos e aplicações CRUD, Templ com Go elimina uma camada inteira de complexidade.
Para quem vem de outras linguagens, vale comparar como Kotlin aborda templating web e como Rust lida com frameworks web server-side. Go com Templ se posiciona entre os dois em simplicidade e performance.
Explore também nosso guia de Go para backend e o tutorial sobre APIs REST em Go para entender como Templ se integra em uma aplicação completa.