Go
このガイドは Hypermedia Components を Go の標準ライブラリ
(net/http + html/template)と htmx に組み合わせます。パターンは
chi、gorilla/mux、および標準の http.ResponseWriter /
*http.Request ペアを公開するあらゆるルーターでも機能します。
アセット読み込み
Section titled “アセット読み込み”HC の dist ファイルを、http.FileServer で配信する static/
ディレクトリ配下に置きます。
static/ assets/ hc/ hc.css hc.behaviors.min.js macros/ index.min.js htmx.min.jstemplates/ layout.html items/ list.html row.htmlpackage main
import ( "net/http")
func main() { fs := http.FileServer(http.Dir("static")) http.Handle("/static/", http.StripPrefix("/static/", fs)) http.HandleFunc("/items", listItems) http.HandleFunc("/items/", itemHandler) // /items/{id} http.ListenAndServe(":8080", nil)}テンプレート
Section titled “テンプレート”html/template は属性値を自動エスケープします — これはまさに
Hypermedia Components が必要とするものです。レイアウト + 名前付き
ブロックからページを構成します。
{{define "layout"}}<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <title>{{.Title}}</title> <link rel="stylesheet" href="/static/assets/hc/hc.css"> <script defer src="/static/assets/htmx.min.js"></script> <script type="module" src="/static/assets/hc/hc.behaviors.min.js"></script> <script type="module" src="/static/assets/hc/macros/index.min.js"></script></head><body> {{template "content" .}}
<div class="hc-toast-region" data-hc-toast-region role="region" aria-label="Notifications"></div></body></html>{{end}}フルのリストと行単位スワップの両方で再利用される行パーシャル:
{{define "row"}}<tr id="item-{{.ID}}"> <td>{{.Name}}</td> <td><span class="hc-badge" data-variant="{{.Status}}">{{.StatusLabel}}</span></td> <td> <span class="hc-action"> <button class="hc-button" data-size="sm" data-variant="error" type="button" data-hc-confirm="Delete {{.Name}}?" data-hx-delete="/items/{{.ID}}" data-hx-trigger="hc:confirmed" data-hx-target="closest tr" data-hx-swap="outerHTML" data-hx-disabled-elt="this" data-hx-indicator="closest .hc-action"> Delete </button> <span class="hc-spinner htmx-indicator" aria-hidden="true"></span> </span> </td></tr>{{end}}html/template の文脈依存エスケープにより、{{.Name}} は属性値の中
でも安全です — 手動エスケープは不要です。
ハンドラとフラグメント
Section titled “ハンドラとフラグメント”テンプレートは起動時に一度だけパースします:
var tpl = template.Must(template.ParseGlob("templates/**/*.html"))htmx の POST に対して 1 行だけ返すハンドラ:
func createItem(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } name := strings.TrimSpace(r.FormValue("name")) if name == "" { w.WriteHeader(http.StatusUnprocessableEntity) fmt.Fprintf(w, `<tr><td colspan="3"><p class="hc-field__message" style="color: var(--hc-color-error);">Name is required.</p></td></tr>`) return } item := store.Create(name)
w.Header().Set("HX-Trigger", `{"hc:toast":{"message":"Added.","variant":"success"}}`) w.WriteHeader(http.StatusCreated) _ = tpl.ExecuteTemplate(w, "row", item)}何も返さずトーストだけを発火するハンドラ:
func deleteItem(w http.ResponseWriter, r *http.Request) { id := pathID(r) // your router's id extraction item, ok := store.Delete(id) if !ok { http.NotFound(w, r) return } trigger := map[string]any{ "hc:toast": map[string]string{ "message": fmt.Sprintf("Deleted %q.", item.Name), "variant": "success", }, } payload, _ := json.Marshal(trigger) w.Header().Set("HX-Trigger", string(payload)) // empty body — htmx removes the row via outerHTML swap}HX-Trigger によるトースト
Section titled “HX-Trigger によるトースト”小さなヘルパーで JSON ペイロードを安全に組み立てます:
func HxTrigger(w http.ResponseWriter, events map[string]any) { if existing := w.Header().Get("HX-Trigger"); existing != "" { var current map[string]any if json.Unmarshal([]byte(existing), ¤t) == nil { for k, v := range events { current[k] = v } events = current } } if b, err := json.Marshal(events); err == nil { w.Header().Set("HX-Trigger", string(b)) }}ハンドラからは:
HxTrigger(w, map[string]any{ "hc:toast": map[string]string{"message": "Saved.", "variant": "success"},})htmx リクエストの検知
Section titled “htmx リクエストの検知”func isHTMX(r *http.Request) bool { return r.Header.Get("HX-Request") == "true"}
func listItems(w http.ResponseWriter, r *http.Request) { items := store.All() if isHTMX(r) { _ = tpl.ExecuteTemplate(w, "rows", items) return } _ = tpl.ExecuteTemplate(w, "list", items)}CSRF のヒント
Section titled “CSRF のヒント”html/template に CSRF は同梱されていません。ミドルウェア(例:
gorilla/csrf)を選び、キット公認の
meta タグをレイアウトに描画します — auto-init の installCsrfHeader()
ビヘイビアがすべての htmx リクエストでトークンを転送するので、
スクリプトは不要です:
<meta name="csrf-token" content="{{.CSRFToken}}" data-header="X-CSRF-Token">トークンはミドルウェアからテンプレートデータへ渡してください
(gorilla/csrf は csrf.Token(r) を公開しています)。規約の詳細
(独自の htmx:configRequest リスナーで追加ヘッダーを重ねる方法を
含む)は
htmx → CSRF トークンを参照してください。
embed.FS—//go:embed templates/* static/*でテンプレートと 静的アセットをバイナリに埋め込み、http.FS(embedFS)から配信 できます。デプロイが単一ファイルで済みます。text/templateとhtml/template— HC のフラグメントには常にhtml/templateを使ってください。属性への値の埋め込みを安全に保つのは 文脈依存エスケープです。- ストリーミングスワップ — 長時間かかる検索には、チャンク化した
フラグメントを返します。htmx は返された要素の
data-hx-swap-oob="true"による out-of-band スワップをサポート します。
- レシピ → confirm-action
- レシピ → live-search
- Examples → htmx — 同梱の例は、同じハンドラ構造の Node http サーバを使っています。