htmx-go-templates

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

htmx on Go html/template

Go html/template 中的 htmx

The server owns state; a template only presents it; htmx moves HTML between the two. Most drift in these apps comes from that split being decided ad hoc, per handler and per template: seven ways to detect an htmx request, the same four attributes hand-copied across files, errors that vanish. The rules below make each of those decisions once.
This skill holds the general rules. The repo's AGENTS.md holds what only the repo knows: its swap-target ids, the names of its shared partials, and the htmx version it vendors. Read it first.
server 掌管 state;template 只负责呈现;htmx 在二者之间移动 HTML。此类应用中的大多数漂移,都源于这项拆分在每个 handler、每个 template 中被临时决定:检测 htmx request 的方式有七种,同一组四个属性被手工复制到多个文件,error 则会悄然消失。下面的规则让每项决策只做一次。
该 skill 包含通用规则。repo 的 AGENTS.md 记录只有该 repo 才知道的信息:swap-target id、shared partial 的名称,以及 repo 所使用的 htmx version。请先阅读它。

Where to look

查看位置

If you're working on…Load
A handler: choosing page / fragment / redirect, htmx headers,
Vary
, 303, error responses
references/responses.md
A template: partials, repeated attribute bundles, forms, live regions, conditionals
references/templates.md
Any client JS: whether you need it at all, islands, htmx glue, polling guards,
aria-busy
references/client.md
如果你正在处理……加载
handler:选择 page / fragment / redirect、htmx headers、
Vary
、303、error responses
references/responses.md
template:partials、重复的 attribute bundle、forms、live regions、conditionals
references/templates.md
任意 client JS:是否需要它、islands、htmx glue、polling guards、
aria-busy
references/client.md

The rules

规则

  1. htmx headers go through one helper set, never hand-typed strings. A typo in
    "HX-Request"
    or a check for
    ""
    instead of
    "true"
    fails silently; one package of constants and predicates makes the wrong spelling unrepresentable. If the repo has no such set yet, introduce it the first time a handler you touch needs an htmx header —
    Vary
    included.
  2. Every route declares its response shape — page, fragment or redirect — in one place. A handler that sniffs headers inline grows a new branch per feature. When one URL can return either a page or a fragment, set
    Vary: HX-Request
    , or a cache or the back button can serve the bare fragment as a page. A history-restore request (
    HX-History-Restore-Request: true
    ) always gets the full page.
  3. A non-htmx form POST answers 303 to a GET; an htmx one answers
    HX-Location
    or a fragment.
    303 stops a refresh from resubmitting. A 303 to an htmx request is followed by the XHR and the whole redirected page gets swapped into the target, which is never what you want.
  4. Forms work without JavaScript. Real
    action
    and
    method
    , a real submit button; htmx attributes layer on top. The non-JS path is also the path the 303 rule serves.
  5. A repeated attribute bundle becomes a named
    {{define}}
    partial.
    Four attributes copied to seventeen links drift the moment one copy changes. One partial, one place to change it. (
    codebase-design
    has the vocabulary for why: one deep module beats many shallow copies.)
  6. Polling never fights the user.
    hx-sync
    on every polling element, a guard that skips the poll while the user is focused or typing inside the target, and a target that never contains an input the user may be editing.
  7. Errors reach the user. htmx 2.x does not swap 4xx/5xx bodies by default, so a refused action looks like nothing happened. Fix it in two halves: a
    responseHandling
    entry that swaps the status you use (typically 422), and
    HX-Retarget
    /
    HX-Reswap
    from the server so the error lands in a known region. That region is a persistent
    aria-live
    container.
  8. In-flight state is visible.
    hx-disabled-elt
    on submit controls so a double-click cannot double-submit;
    hx-indicator
    or
    aria-busy
    on the region being replaced.
  9. Client JS is the exception, and it is never inline. Before writing any, ask whether the server can own it (a preference, a re-rendered region) or HTML can (
    <details>
    ,
    <dialog>
    ,
    popover
    ). A widget with real client state becomes an island in the repo's frontend framework, mounted through
    htmx.onLoad
    and unmounted on swap. The only other JS is a fixed set of htmx glue listeners. No
    <script>
    blocks,
    onclick
    or
    hx-on
    in templates: inline handlers are untestable and re-run unpredictably on swaps. htmx config goes in
    <meta name="htmx-config">
    with
    "allowEval": false
    , so htmx itself refuses
    hx-on
    and
    js:
    values.
  10. Templates branch on presentation only.
    {{if eq .Status "failed"}}
    chains are domain logic in the wrong layer. Compute a view-model field in Go (
    .Badge
    ,
    .CanRetry
    ) and let the template read it.
  1. htmx headers 统一通过一套 helper 处理,绝不手写字符串。 在
    "HX-Request"
    上出现拼写错误,或检查
    ""
    而不是
    "true"
    ,都会悄无声息地失败;用一套包含 constants 和 predicates 的 package,可以让错误拼写根本无从表达。如果 repo 尚无这样一套工具,应在首次修改某个 handler、而该 handler 需要 htmx header 时引入它——包括
    Vary
    。
  2. 每条 route 都要在一个地方声明自己的 response shape——page、fragment 或 redirect。 inline 检测 headers 的 handler 会随着每个 feature 的加入而不断新增分支。当同一 URL 既可能返回 page,也可能返回 fragment 时,设置
    Vary: HX-Request
    ,否则 cache 或后退按钮可能会把裸 fragment 当作 page 提供。history-restore request(
    HX-History-Restore-Request: true
    )始终应返回完整 page。
  3. 非 htmx form 的 POST 要以 303 响应并转到 GET;htmx form 则响应
    HX-Location
    或 fragment。
    303 可以防止刷新时重复提交。对 htmx request 返回 303 后,XHR 会跟随该重定向,而整个被重定向的 page 会被 swap 到 target 中,这从来都不是你想要的。
  4. Form 必须能在没有 JavaScript 的情况下工作。 使用真实的
    action
    和
    method
    ,以及真正的 submit button;htmx attributes 只是叠加在其上。无 JS 路径也正是 303 规则所服务的路径。
  5. 重复的 attribute bundle 应变成命名的
    {{define}}
    partial。
    四个属性复制到十七个 link 中,只要其中一处发生变化就会产生漂移。只保留一个 partial,只修改一处。(
    codebase-design
    提供了说明原因所需的术语:一个 deep module 胜过许多 shallow copies。)
  6. Polling 绝不能与用户操作冲突。 每个 polling element 都要设置
    hx-sync
    ;设置 guard,在用户聚焦于 target 或正在 target 内输入时跳过 poll;并且 target 内绝不能包含用户可能正在编辑的 input。
  7. Error 必须传达给用户。 htmx 2.x 默认不会 swap 4xx/5xx body,因此被拒绝的 action 看起来就像什么都没发生。分两部分修复:添加
    responseHandling
    entry,swap 你使用的 status(通常是 422),同时由 server 返回
    HX-Retarget
    /
    HX-Reswap
    ,让 error 落到已知区域。该区域应是一个持久存在的
    aria-live
    container。
  8. In-flight state 必须可见。 对 submit controls 使用
    hx-disabled-elt
    ,防止双击造成重复提交;对正在替换的 region 使用
    hx-indicator
    或
    aria-busy
    。
  9. Client JS 是例外,而且绝不能 inline。 编写任何 JS 之前,先问能否由 server 负责(preference、重新渲染的 region),或由 HTML 负责(
    <details>
    、
    <dialog>
    、
    popover
    )。具有真实 client state 的 widget 应成为 repo 的 frontend framework 中的 island,通过
    htmx.onLoad
    挂载,并在 swap 时卸载。其他 JS 仅限一组固定的 htmx glue listener。Template 中不得有
    <script>
    block、
    onclick
    或
    hx-on
    :inline handler 无法测试,且在 swap 后会不可预测地再次执行。htmx config 放在
    <meta name="htmx-config">
    中,并设置
    "allowEval": false
    ,这样 htmx 本身就会拒绝
    hx-on
    和
    js:
    值。
  10. Template 只根据 presentation 分支。
    {{if eq .Status "failed"}}
    chain 是放错层的 domain logic。先在 Go 中计算 view-model field(
    .Badge
    、
    .CanRetry
    ),再让 template 读取它。

Choosing a response shape

选择 response shape

request ──► HX-History-Restore-Request: true? ──yes──► page
               │ no
               ▼
            HX-Request: true? ──no──► POST? ──yes──► 303 to GET (or re-render page with 422 on invalid)
               │ yes                       └─no──► page
               ▼
            route declares: fragment ──► ExecuteTemplate(w, "<block>", vm)
                            page+select ──► page (template uses hx-select)
                            redirect ──► HX-Location (or HX-Redirect for a full reload)
references/responses.md
has the helper set and a handler written both ways.
request ──► HX-History-Restore-Request: true? ──yes──► page
               │ no
               ▼
            HX-Request: true? ──no──► POST? ──yes──► 303 to GET (or re-render page with 422 on invalid)
               │ yes                       └─no──► page
               ▼
            route declares: fragment ──► ExecuteTemplate(w, "<block>", vm)
                            page+select ──► page (template uses hx-select)
                            redirect ──► HX-Location (or HX-Redirect for a full reload)
references/responses.md
包含 helper set,以及以两种方式编写的 handler。

htmx version

htmx version

These rules target htmx 2.x. Check the version the repo vendors before relying on defaults. If it is on 4.x, re-check rules 7 and 9 against the 4.x migration notes:
  • error responses swap by default, with per-status control through
    hx-status
    ;
  • attribute inheritance is explicit, so bundles that relied on inheriting
    hx-target
    break;
  • event names changed, so listeners in the TS module need renaming.
这些规则针对 htmx 2.x。依赖默认值前,先检查 repo 使用的 version。如果 repo 使用 4.x,请根据 4.x migration notes重新检查规则 7 和 9:
  • error responses 默认会被 swap,并可通过
    hx-status
    按 status 控制;
  • attribute inheritance 现在是显式的,因此依赖继承
    hx-target
    的 bundle 会失效;
  • event names 已更改,因此 TS module 中的 listener 需要重命名。

What not to do

不应采用的做法

  • Don't reach for the
    response-targets
    extension to route errors. It moves a server decision into markup and adds a second mechanism next to
    HX-Retarget
    .
  • Don't swap an
    aria-live
    region with
    outerHTML
    . Screen readers track the original node; a replaced live region announces nothing. Swap its
    innerHTML
    .
  • Don't render into the
    ResponseWriter
    directly. Execute into a buffer first, so a template error becomes a 500 rather than a half-written 200.
  • Don't add
    hx-boost
    to fix a navigation problem. Declare the nav-link partial instead; boost changes every link and form under it at once.
  • Don't restate the htmx attribute reference here or in the repo; link htmx.org/reference.
  • 不要使用
    response-targets
    extension 来路由 error。它会把 server 决策移入 markup,并在
    HX-Retarget
    旁边增加第二套机制。
  • 不要用
    outerHTML
    swap
    aria-live
    region。Screen reader 会跟踪原节点;被替换的 live region 不会播报任何内容。应 swap 它的
    innerHTML
    。
  • 不要直接 render 到
    ResponseWriter
    。先 Execute 到 buffer 中,这样 template error 会变成 500,而不是半写入的 200。
  • 不要添加
    hx-boost
    来解决 navigation problem。应声明 nav-link partial;boost 会一次性改变其下的所有 link 和 form。
  • 不要在这里或 repo 中重述 htmx attribute reference;请链接 htmx.org/reference。