Реактивные серверы, или как перестать писать JavaScript и начать жить
Thursday, 9 Apr 2026
Phoenix LiveViewи его друзья из других стеков:Rails Hotwire,Laravel LivewireиGo Live/HLive
Преамбула: краткая история нежелания
В начале двухтысячных, когда jQuery еще казался вершиной инженерной мысли, а слово «фреймворк» произносилось с придыханием и легким немецким акцентом, веб-разработчик был существом предсказуемым. Он писал HTML, немного CSS, чуть-чуть серверного кода — и уходил домой к шести. Ajax обрел второе дыхание, и подходы к созданию веб-приложений разветвились, как генеалогическое древо разорившегося аристократа.
К 2010-м трещина превратилась в пропасть. По одну сторону остались серверные разработчики — степенные, что твоя пожилая немецкая пара на Фридрихштрассе, в отутюженных рубашках и с прямой спиной. По другую — фронтендеры, вечно взъерошенные, перебирающие фреймворки, как четки: Backbone, Angular, React, Vue, Svelte, Solid, $mol, конечно, куда без него, — и далее по списку. Между ними пролегала нейтральная полоса из JSON-ов и REST API, которую обе стороны пересекали с выражением лица таможенника, проверяющего документы.
И вот, примерно к 2018 году, несколько независимых друг от друга команд пришли к одной и той же еретической идее: а что, если вообще не отдавать рендеринг браузеру? Что, если сервер будет отрисовывать HTML, отправлять его по WebSocket — и пусть клиент занимается тем, чем ему положено: отображением текста и картинок?
Идея была не нова. Собственно, именно так работал весь веб до изобретения XMLHttpRequest. Но в новой реинкарнации она обросла диффами, патчами, виртуальными DOM-ами на сервере и прочей инженерной роскошью, позволяющей создавать иллюзию мгновенного отклика без единой строчки клиентского JavaScript. Ну, почти без единой.
Так родились четыре подхода к одной и той же проблеме: Phoenix LiveView (2018, Elixir), Rails Hotwire (2020, Ruby), Laravel Livewire (2019, PHP) и целое семейство Go-реализаций: Live (jfyne), HLive (SamHennessy), Fir (livefir — заархивировано в 2025). Каждая из них — ответ своей экосистемы на вопрос, который можно сформулировать коротко: как написать интерактивное веб-приложение, не выходя из зоны комфорта?
I. Phoenix LiveView: демиург серверной реактивности
LiveView появился первым -- если не считать Meteor, который, впрочем, решал несколько другую задачу и делал это с размахом пьяного декоратора. Крис Маккорд представил LiveView на ElixirConf 2018, и с тех пор фреймворк прошел путь от экспериментальной библиотеки до версии 1.0 (декабрь 2024), обретя по дороге HEEx-шаблоны, компоненты, стримы и прочую атрибутику зрелости.
Принцип работы прост до смешного: при первом запросе сервер отрисовывает полноценную HTML-страницу. Затем браузер открывает WebSocket-соединение, и дальше все изменения приходят в виде минимальных диффов. Состояние живет на сервере, в отдельном легковесном BEAM-процессе — том самом, которых Erlang VM умудряется держать одновременно по два миллиона на машине, не особо при этом потея.
defmodule DemoLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
def render(assigns) do
~H"""
<div>
<h1>Счетчик: {@count}</h1>
<button phx-click="inc">+</button> <button phx-click="dec">-</button>
</div>
"""
end
def handle_event("inc", _, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
def handle_event("dec", _, socket) do
{:noreply, update(socket, :count, &(&1 - 1))}
end
end
Обратите внимание: здесь нет ни контроллера, ни JSON, ни маршрутизатора в привычном смысле. Один модуль. Состояние — живет в assigns. Рендеринг — в ~H-сигиле. События — в handle_event. Все остальное берет на себя фреймворк. Примерно так хороший печник складывает из кирпичей нечто кривобокое, немного покосившееся — но оно протапливается с первого раза, как родное.
Сильные стороны:
- Настоящий реалтайм через WebSocket. Не имитация, не поллинг (хотя при отсутствии вебсокетов предусмотрен откат на него) — живое, двустороннее соединение с персистентным процессом на сервере.
- Fault tolerance по наследству от BEAM/OTP. Процесс упал — отжался перезапустился. Приложение продолжает работать.
- Минимальный трафик: отправляются только диффы измененных частей DOM.
- Один язык, один стек, одна кодовая база. Тестирование LiveView-компонентов не требует браузера.
Слабые стороны:
- Elixir — прекрасный почти функциональный язык с паттерн-матчингом, иммутабельностью и рекурсией. Но найти на рынке команду, знакомую с нюансами — практически нереально.
- Экосистема заметно меньше, чем у Ruby или PHP. Библиотеку для интеграции с экзотическим API придется, вероятнее всего, написать самому. Хотя, по моему опыту, все нужное — уже давно написано.
- Каждое WebSocket-соединение потребляет память на сервере (
40KB). При миллионе одновременных пользователей арифметика становится неуютной. Впрочем, это не CGI, конечно, и когда у вас есть миллион активных пользователей, можно позволить себе воткнуть рядом еще один сервер.
II. Rails Hotwire: HTML по проводам, как завещал Дедушка Харизматичного Хайпа (известный в узких кругах, как DHH)
Hotwire — это Basecamp/37signals, DHH, Ruby on Rails, и все, что из этого следует: конвенция превыше конфигурации, мнение фреймворка превыше мнения разработчика, и твердая уверенность, что большинству приложений не нужен React (что так на самом деле и есть, в принципе). Это набор из двух библиотек: Turbo (управление навигацией и обновлением фрагментов страницы) и Stimulus (минимальный JS-фреймворк для поведения).
Философия Hotwire — «Html Over The WIRE» в буквальном смысле: сервер отправляет готовые куски HTML, а клиент вставляет их в нужные места страницы. Никаких диффов на уровне атрибутов, никакого виртуального DOM. Грубо, эффективно, понятно — как кирпичная кладка.
<%# app/views/posts/index.html.erb %>
<%= form_with url: posts_path, method: :get,
data: { turbo_frame: "results" } do %>
<input type="search" name="q" value="<%= params[:q] %>">
<% end %>
<turbo-frame id="results">
<% @posts.each do |post| %>
<div><%= post.title %></div>
<% end %>
</turbo-frame>
# Turbo Stream: обновление в реальном времени
# app/models/message.rb
class Message < ApplicationRecord
after_create_commit -> {
broadcast_append_to "messages, target: "messages, partial: "messages/message"
}
end
Turbo Frames — это изолированные контексты навигации: ссылка внутри фрейма обновляет только фрейм, остальная страница остается нетронутой. Turbo Streams — серверные команды (append, prepend, replace, remove), которые можно доставлять как через обычный HTTP-ответ, так и через WebSocket (Action Cable).
Сильные стороны:
- Максимально пологая кривая обучения. Если вы знаете Rails — вы уже знаете Hotwire.
- Гигантская экосистема джемов. Для любой задачи найдется готовое решение, проверенное временем и тысячами продакшенов.
- Нет привязки к WebSocket: Turbo Frames работают через обычный HTTP. Turbo Streams умеют и так, и так.
- Stimulus дает ровно столько JavaScript, сколько нужно, и ни граммом больше.
Слабые стороны:
- Hotwire — не реактивный фреймворк. Это набор соглашений по доставке HTML. Состояние живет в базе данных и перестраивается при каждом запросе. Это принципиально другая модель, нежели чем у LiveView.
- Сложная интерактивность (drag-and-drop, nested modals, client-side state) быстро упирается в необходимость писать настоящий JavaScript.
- Производительность Ruby при высокой конкурентности — тема для отдельного, грустного разговора, но никто не мешает воткнуть много прикленных к соединениям серверов за балансером, так делает, например, Shopify.
III. Laravel Livewire: состояние как образ жизни
Livewire — самый непосредственный среди наследников LiveView. Калеб Порцио создал его в 2019 году, вдохновившись именно Phoenix LiveView, и перенес основные идеи на почву Laravel и PHP. Результат — компонентная система, в которой PHP-класс хранит состояние, а Blade-шаблон его отображает. При каждом событии (клик, ввод текста, отправка формы) Livewire отправляет AJAX-запрос на сервер, сервер перерисовывает компонент и возвращает обновленный HTML.
// app/Livewire/Counter.php
class Counter extends Component {
public int $count = 0;
public function increment(): void { $this->count++; }
public function decrement(): void { $this->count--; }
public function render(): View { return view('livewire.counter'); }
}
{{-- resources/views/livewire/counter.blade.php --}}
<div>
<h1>Счетчик: {{ $count }}</h1>
<button wire:click="increment">+</button>
<button wire:click="decrement">-</button>
</div>
Выглядит так, словно первый пример переписали на PHP? Неудивительно. Архитектурный паттерн тот же: состояние на сервере, события от клиента, перерисовка и доставка изменений. Но дьявол, как водится, прячется в деталях.
PHP не умеет держать постоянное WebSocket-соединение так же элегантно, как Erlang VM. Livewire 2 использовал AJAX-поллинг; Livewire 3 добавил поддержку WebSocket через Laravel Reverb, но по умолчанию все еще работает через HTTP. При каждом запросе состояние компонента сериализуется в JSON, отправляется на клиент, а при следующем событии — возвращается обратно. Это работает, но с грацией бригады грузчиков, затаскивающих рояль на шестнадцатый этаж в доме без грузового лифта.
Зато Livewire прекрасно интегрируется с Alpine.js — маленьким JavaScript-фреймворком, который берет на себя анимации, тогглы и прочую клиентскую мелочь, которую гонять через сервер было бы расточительно. Связка Livewire + Alpine — фактический стандарт в мире Laravel.
Сильные стороны:
wire:model— двустороннее связывание данных. Вводите текст в поле — свойство компонента обновляется автоматически.- Огромная экосистема Laravel. Filament, Jetstream, Breeze — все это работает с Livewire из коробки.
- Минимальный порог входа для PHP-разработчиков.
- Alpine.js покрывает те случаи, когда серверный рендеринг избыточен.
Слабые стороны:
- Отсутствие персистентного соединения (по умолчанию). Каждое взаимодействие — это HTTP round-trip. На нестабильных сетях пользователь это почувствует. Но мы и так на PHP.
- Сериализация состояния при каждом запросе — накладные расходы, растущие пропорционально сложности компонента.
- PHP и конкурентность — оксюморон того же толка, что «интеллект языковой модели» или «интеллигентный военрук».
IV. Go Live/HLive: суровый минимализм
В экосистеме Go нет одного доминирующего решения — скорее, россыпь проектов разной степени зрелости, объединенных общей идеей и общим нежеланием становиться мейнстримом. Два наиболее заметных: Live (jfyne, 730 звезд на GitHub) и HLive (SamHennessy, 98 звезд). Есть также Fir (livefir), но его автор уже объявил о прекращении разработки в пользу нового проекта.
Live — прямой наследник Phoenix LiveView по духу. WebSocket, серверный рендеринг, минимальные диффы. Встраивается в стандартный net/http.
// Термостат на Go Live (jfyne/live)
func thermoMount(ctx context.Context, s *live.Socket) (any, error) {
return &ThermoModel{C: 19.5}, nil
}
func tempUp(ctx context.Context, s *live.Socket, p live.Params) (any, error) {
model := s.Assigns().(*ThermoModel)
model.C += 0.1
return model, nil
}
// Шаблон
// <div>{{.Assigns.C}}</div>
// <button live-click="temp-up">+</button>// <button live-click="temp-down">-</button>
HLive идет дальше: вместо шаблонов — программная генерация DOM на сервере (виртуальный DOM à la React, но на Go). Никаких HTML-файлов, никаких template engines. Чистый Go.
// HLive: программная генерация
DOMfunc home() *l.Page {
var message string
input := l.C("input", l.Attrs{"type": "text"}, l.OnKeyUp(func(ctx context.Context, e l.Event) {
message = e.Value
}),)
page := l.NewPage()
page.DOM.Body.Add(
l.T("div", input),
l.T("hr"),
"Hello, ",
&message,
)
return page
}
Сильные стороны:
- Go — компилируемый, статически типизированный, быстрый. Один бинарник, минимальное потребление памяти.
- Совместимость с
net/http— никаких зависимостей от мега-фреймворков. - Идеально для внутренних инструментов, админок, дашбордов.
Слабые стороны:
- Незрелость. Live — наиболее развитый проект, но и он далек от production-ready в том смысле, в каком это понимают в индустрии.
- Минимальная экосистема компонентов. Нет аналога Phoenix Components или Blade-компонентов.
- Документация — на уровне README и примеров. Для сколько-нибудь серьезного проекта придется читать исходники.
- Нет горячей перезагрузки, нет встроенной аутентификации, нет генераторов. Вы один на один с языком и стандартной библиотекой.
V. Сравнительная анатомия
Модель состояния
LiveView: состояние живет в BEAM-процессе, привязанном к WebSocket-соединению. Процесс — легковесный (2KB), изолированный, с собственной кучей. Падение одного процесса не затрагивает остальные.
Hotwire: состояния на сервере нет. Каждый запрос — stateless. Состояние — в базе данных, в сессии, в URL-параметрах. Это архитектурное решение, не ограничение.
Livewire: состояние сериализуется и путешествует между клиентом и сервером при каждом запросе. Сервер восстанавливает компонент из JSON, обрабатывает событие, перерисовывает, отправляет обратно.
Go Live/HLive: состояние — в горутине, привязанной к WebSocket-сессии. Модель ближе всего к LiveView, но без OTP-супервизоров и горячей перезагрузки кода.
Транспорт
LiveView: WebSocket (всегда). Первый рендер — по HTTP, далее — только WS.
Hotwire: HTTP (Turbo Frames) или WebSocket (Turbo Streams через Action Cable). Выбор за разработчиком.
Livewire: HTTP (по умолчанию). WebSocket — опционально, через Laravel Reverb.
Go Live/HLive: WebSocket (всегда).
Гранулярность обновлений
LiveView: диффы на уровне отдельных выражений внутри шаблона. Фреймворк знает, какие части шаблона статичны, а какие динамичны, и отправляет только изменения в динамических частях.
Hotwire: замена целых HTML-фрагментов. Turbo Stream команды (append, replace, remove) оперируют DOM-элементами целиком.
Livewire: полная перерисовка компонента, затем morphdom на клиенте определяет минимальный набор DOM-операций.
Go Live/HLive: Live — аналогично LiveView (диффы шаблонов). HLive — виртуальный DOM на сервере с tree diffing.
Заключение
Все четыре подхода решают одну задачу — но каждый делает максимально уютно для своей экосистемы.
Phoenix LiveView — наиболее зрелый и архитектурно последовательный. Если вы готовы инвестировать в изучение Elixir, вы получите инструмент, спроектированный от первого до последнего винтика для серверной реактивности. Это печь, сложенная мастером: кривобокая, но греет.
Rails Hotwire — прагматичный ответ зрелой экосистемы. Не претендует на революцию, зато работает в любом Rails-приложении через пять минут после bundle install. Покрывает восемьдесят процентов потребностей — и этого, как правило, достаточно.
Laravel Livewire — ближайший клон LiveView, адаптированный под реалии PHP. Идеален для Laravel-разработчиков, которые хотят интерактивности без JavaScript, но готовы мириться с накладными расходами HTTP round-trip.
Go Live/HLive — перспективный, но пока незрелый эксперимент. Подойдет для внутренних инструментов, дашбордов и прототипов, где Go уже используется на бэкенде и тянуть отдельный фронтенд-стек — непозволительная роскошь.
Выбор между ними — не вопрос технического превосходства. Это вопрос экосистемы, команды, и того, в каком кресле вам удобнее сидеть, когда самолет, наконец, наберет высоту.
А самое главное, конечно, что при помощи этих технологий можно перестать писать JavaScript :)