templ & htmx — interaktif tanpa SPA
Untuk situs bertrafik baca tinggi, halaman yang dirender server dan bisa disimpan di CDN sulit dikalahkan. templ membuat template-nya bertipe; htmx menambahkan interaktivitas tanpa membangun SPA.
Intisari
- templ mengompilasi template jadi fungsi Go — salah nama field jadi error kompilasi, bukan halaman rusak di produksi.
- Keluarannya di-escape secara bawaan, sama seperti
html/template. - htmx membuat elemen HTML biasa bisa mengirim permintaan dan mengganti sebagian halaman — server tetap mengembalikan HTML.
- Kombinasi ini menghapus lapisan API JSON dan state ganda untuk seluruh kelas aplikasi.
- Halaman publik tetap bisa di-cache penuh di CloudFront; hanya fragmen interaktif yang dinamis.
templ: template yang dikompilasi
// web/produk.templ
package web
templ DaftarProduk(produk []Produk, halaman int) {
@Tata("Katalog") {
<h1>Katalog</h1>
<div id="daftar">
for _, p := range produk {
@KartuProduk(p)
}
</div>
if len(produk) == 20 {
<button
hx-get={ fmt.Sprintf("/produk?halaman=%d", halaman+1) }
hx-target="#daftar"
hx-swap="beforeend"
hx-indicator="#memuat">
Muat lebih banyak
</button>
}
}
}
templ KartuProduk(p Produk) {
<article class="kartu">
<h3>{ p.Nama }</h3>
<p>{ rupiah(p.Harga) }</p>
</article>
}
go get -tool github.com/a-h/templ/cmd/templ
go tool templ generate # .templ → _templ.go
go build ./...
func (h *Handler) DaftarProduk(w http.ResponseWriter, r *http.Request) {
produk, err := h.produk.Daftar(r.Context(), halaman)
if err != nil {
h.tulisError(w, r, err)
return
}
// Permintaan htmx: kirim FRAGMEN saja, bukan halaman penuh.
if r.Header.Get("HX-Request") == "true" {
_ = web.PotonganProduk(produk).Render(r.Context(), w)
return
}
w.Header().Set("Cache-Control", "public, s-maxage=60, stale-while-revalidate=600")
_ = web.DaftarProduk(produk, halaman).Render(r.Context(), w)
}
html/template | templ | |
|---|---|---|
| Salah nama field | Error saat runtime | Error kompilasi |
| Pelengkapan otomatis di editor | Tidak | Ya |
| Refactor nama | Manual | Ikut alat Go |
| Escaping | Sadar konteks | Sadar konteks |
| Langkah build | Tidak ada | templ generate |
| Bagian pustaka standar | Ya | Tidak |
htmx: interaktivitas dari atribut HTML
<!-- Pencarian langsung: server mengembalikan HTML, bukan JSON -->
<input
type="search" name="q"
hx-get="/cari"
hx-trigger="keyup changed delay:300ms"
hx-target="#hasil"
hx-indicator="#memuat">
<div id="hasil"></div>
<!-- Hapus dengan konfirmasi, tanpa satu baris JavaScript pun -->
<button
hx-delete="/produk/42"
hx-confirm="Yakin hapus produk ini?"
hx-target="closest article"
hx-swap="outerHTML swap:0.3s">
Hapus
</button>
| Atribut | Fungsinya |
|---|---|
hx-get, hx-post, hx-delete | Kirim permintaan saat elemen dipicu |
hx-target | Elemen mana yang diganti responsnya |
hx-swap | Bagaimana menggantinya: innerHTML, outerHTML, beforeend |
hx-trigger | Peristiwa pemicu, lengkap dengan debounce |
hx-indicator | Elemen yang ditampilkan selama menunggu |
Yang hilang dengan pendekatan ini: lapisan API JSON, kode serialisasi, kode pengambilan data di klien, dan state yang harus dijaga sinkron di dua tempat. Yang tersisa: server mengembalikan HTML, browser menaruhnya. Untuk aplikasi CRUD, panel admin, dan situs konten, itu penghapusan kompleksitas yang besar — dan bonus terbesarnya adalah tidak ada lagi kemungkinan API dan UI berbeda pendapat tentang keadaan data.
Keamanan dengan htmx
- Fragmen HTML yang dikembalikan tetap harus di-escape — templ dan
html/templatemelakukannya. - Endpoint fragmen adalah endpoint sungguhan: butuh autentikasi, otorisasi, dan rate limit yang sama.
- Token CSRF ikut lewat
hx-headersuntuk permintaan yang mengubah data (Fase 6). - Jangan mengembalikan fragmen berisi data pengguna dengan
Cache-Control: public.
<body hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
Kapan pendekatan ini tepat
| Aplikasi | Pendekatan |
|---|---|
| Situs konten, katalog, blog | templ + CDN — hampir tanpa JavaScript |
| CRUD, panel admin, dasbor internal | templ + htmx |
| Formulir bertahap, filter, pencarian langsung | templ + htmx |
| Editor kolaboratif, kanvas, aplikasi offline | SPA sungguhan (React/Vue) + API JSON |
| Aplikasi mobile | API JSON — HTML tidak membantu di sana |
Untuk sasaran roadmap ini — situs skala enterprise dengan trafik baca tinggi — kombinasi paling sulit dikalahkan adalah: halaman publik dirender templ dan disajikan penuh dari CloudFront, dengan htmx hanya pada bagian yang memang interaktif. Sebagian besar permintaan tidak pernah menyentuh proses Go sama sekali (Fase 9), dan yang menyentuh cukup mengembalikan potongan HTML kecil.
Latihan: bangun halaman daftar produk dengan templ, lalu tambahkan tombol "muat lebih banyak"
memakai htmx yang meminta fragmen dan menempelkannya. Pastikan permintaan htmx dikenali lewat header
HX-Request dan hanya mengembalikan potongan. Lalu ganti nama satu field di struct
Produk dan lihat go build menolak — itu yang tidak bisa dilakukan
html/template.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.