htmx: интерактивность без JavaScript-фреймворков — когда это работает
Последние десять лет фронтенд двигался в одном направлении: JSON API на бэкенде, SPA-фреймворк на клиенте, тонны JavaScript для рендеринга того, что раньше делал сервер. htmx предлагает откатиться к основам — пусть сервер возвращает HTML, а браузер просто вставляет его в нужное место. Без React, без Vue, без бандлера. Одна библиотека в 14 КБ — и серверный шаблон становится интерактивным.
Философия: hypermedia вместо JSON API + SPA
В SPA-подходе сервер — JSON API, логика отображения на клиенте. htmx возвращает к модели hypermedia-driven application: сервер отвечает фрагментами HTML, браузер подставляет их в DOM.
- Логика отображения на сервере — один источник правды.
- Любой серверный язык: Go, Python, Ruby, PHP — без привязки к Node.js.
- Нет этапа сборки. Нет node_modules. Нет webpack/vite конфигов.
- SEO из коробки — сервер отдаёт готовый HTML.
Основные атрибуты: hx-get, hx-post, hx-swap, hx-trigger
htmx расширяет HTML атрибутами, превращающими любой элемент в AJAX-запрос:
<!-- Загрузить контент по клику -->
<button hx-get="/api/users" hx-target="#user-list" hx-swap="innerHTML">
Загрузить пользователей
</button>
<div id="user-list"></div>
<!-- Отправить форму без перезагрузки -->
<form hx-post="/api/comments" hx-target="#comments" hx-swap="beforeend">
<input name="text" placeholder="Комментарий..." />
<button type="submit">Отправить</button>
</form>
<!-- Удалить элемент с подтверждением -->
<button hx-delete="/api/posts/42"
hx-confirm="Удалить пост?"
hx-target="closest .post"
hx-swap="outerHTML">
Удалить
</button>
Ключевые атрибуты:
hx-get/post/put/delete— HTTP-метод и URL.hx-target— CSS-селектор элемента для вставки ответа.hx-swap— как вставить: innerHTML, outerHTML, beforeend, afterbegin.hx-trigger— событие: click, submit, keyup, intersect, load.
Паттерны: infinite scroll, inline editing, search
Infinite scroll через IntersectionObserver:
<div id="posts">
<article>...</article>
<div hx-get="/posts?page=2"
hx-trigger="intersect once"
hx-target="#posts"
hx-swap="beforeend">
Загрузка...
</div>
</div>
Search-as-you-type с debounce:
<input type="search" name="q"
hx-get="/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results" />
<div id="results"></div>
Inline editing — клик превращает текст в форму:
<span hx-get="/edit/title/42"
hx-trigger="click"
hx-swap="outerHTML">
Название статьи
</span>
Сервер возвращает форму, после сабмита — обратно текст. Цикл замыкается без единой строки JS.
Архитектура: htmx + серверные шаблоны
htmx работает с любым серверным стеком. Пример на Go:
func SearchHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
users := db.SearchUsers(query)
// Возвращаем HTML-фрагмент, не полную страницу
tmpl.ExecuteTemplate(w, "user-list.html", users)
}
<!-- templates/user-list.html -->
{{range .}}
<div class="user-card">
<img src="{{.Avatar}}" />
<span>{{.Name}}</span>
</div>
{{end}}
Аналогично с Python/Jinja2, Ruby/ERB, PHP/Blade. Правило: эндпоинты для htmx возвращают HTML-фрагменты, а не JSON.
Когда htmx не подходит
- Сложный клиентский стейт. Drag-and-drop, real-time коллаборация, оффлайн — нужен клиентский фреймворк.
- Высокая частота обновлений. Чат с сотнями сообщений в секунду, биржевые тикеры — latency на каждый запрос.
- Сложные формы. Многошаговые визарды с зависимыми полями проще на клиенте.
- Оффлайн. htmx требует сервер для каждого взаимодействия.
Где htmx идеален: админки, CRUD-интерфейсы, контентные сайты с формами, дашборды, внутренние инструменты — везде, где интерактивность сводится к «отправь запрос — покажи результат».
Вывод
htmx — не шаг назад, а осознанный выбор простоты. Если приложение — серверный рендеринг с точечной интерактивностью, htmx уберёт тысячи строк JavaScript и десятки зависимостей. Не каждому проекту нужен React.