Por que migrei do HTMX para o Datastar
(everydaysuperpowers.dev)- Ao usar HTMX, foi possível reduzir o volume de código em cerca de 70%, mas surgiram problemas de sincronização entre UIs e um aumento da complexidade no gerenciamento de estado do frontend
- Após adotar o Datastar, ficou mais fácil estruturar e manter o código ao desenvolver aplicações multiusuário em tempo real sem WebSockets
- Enquanto o HTMX distribui a lógica de comportamento em atributos HTML, o Datastar melhora a consistência e a manutenibilidade da lógica por meio de um modelo de atualização orientado pelo servidor
- A API do Datastar tem menos atributos, o que aumenta a legibilidade e a produtividade do código
- O Datastar faz uso ativo de tecnologias nativas da web como Server-Sent Events (SSE), Web Components e CSS View Transitions, permitindo colaboração em tempo real e uma estrutura de componentes reutilizável
Introdução e motivação
- Em 2022, David Guillot compartilhou na DjangoCon Europe um caso em que migrou um SaaS baseado em React para HTMX, reduzindo o código em cerca de 70% e melhorando funcionalidades
- Depois disso, muitas equipes passaram a relatar que, ao migrar de aplicativos de página única (SPA) para aplicativos hipermídia multipágina, conseguiram reduzir código e melhorar tanto a experiência de desenvolvimento quanto a do usuário
- O autor também confirmou, ao migrar seu projeto de HTMX para Datastar, que o código ficou menor e que era possível desenvolver apps multiusuário em tempo real sem WebSocket nem gerenciamento de estado complexo
Problemas que motivaram a migração
- Ao preparar uma apresentação na FlaskCon 2025, o autor tentou sincronizar a UI combinando HTMX e AlpineJS, mas encontrou problemas de sincronização
- Como as duas bibliotecas são ferramentas separadas criadas por desenvolvedores diferentes, não conseguem se comunicar entre si, então o trabalho de integração fica por conta do desenvolvedor
- No processo de inicializar componentes em vários momentos e coordenar eventos, acabou sendo necessário escrever muito mais código e gastar mais tempo com depuração do que o esperado
- O autor resolveu testar o Datastar ao notar que ele unificava as funções das duas bibliotecas e ainda era entregue com menos de 11 KB
- Isso também favorece o desempenho de carregamento de página para usuários em dispositivos móveis
Um design de API melhor no Datastar
- A API do Datastar passa uma sensação de ser muito mais leve do que a do HTMX, com menos atributos necessários para obter o resultado desejado
- No HTMX, a maioria das interações exige vários atributos
- Definir a URL, indicar o elemento de destino e especificar como tratar a resposta são coisas configuradas em atributos separados
- Em geral, são usados 2 ou 3 atributos toda vez, e às vezes ainda é preciso subir pela cadeia de herança para entender como o atributo funciona
<a hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></a> - No Datastar, normalmente a mesma função é implementada com um único atributo
<a data-on-click="@get('/rebuild/status-button')"></a>- Mesmo meses depois, fica fácil entender como o código funciona ao revisitá-lo
Diferença no modo de funcionamento
- Enquanto o HTMX é uma biblioteca de frontend com o objetivo de expandir a especificação HTML, o Datastar é uma biblioteca orientada pelo servidor cujo foco é construir aplicações de atualização em tempo real de alta performance com tecnologias nativas da web
- O HTMX define o comportamento adicionando atributos ao elemento que dispara a requisição e, mesmo quando atualiza elementos distantes na página, a lógica acaba espalhada por várias camadas
- No Datastar, o servidor decide o que deve mudar, concentrando toda a lógica de atualização em um só lugar
-
Exemplo com HTMX
<div> <div id="alert"></div> <button hx-get="/info" hx-select="#info-details" hx-swap="outerHTML" hx-select-oob="#alert"> Get Info! </button> </div>- Ao clicar no botão, uma requisição GET é enviada para
/info; o botão é substituído pelo elemento com IDinfo-detailspresente na resposta, e o elemento da página com IDalerté substituído pelo elemento com o mesmo ID vindo da resposta - O elemento botão precisa saber informação demais e também precisa conhecer previamente o que o servidor vai retornar, o que enfraquece o princípio de "localidade do comportamento" (locality of behavior) do HTMX
- Ao clicar no botão, uma requisição GET é enviada para
-
Abordagem melhorada do Datastar
<div> <div id="alert"></div> <button id="info-details" data-on-click="@get('/info')"> Get Info! </button> </div>- O servidor retorna uma string HTML contendo dois elementos raiz com os mesmos IDs
<p id="info-details">These are the details you are looking for…</p> <div id="alert">Alert! This is a test.</div> - Uma opção simples e com ótimo desempenho
- O servidor retorna uma string HTML contendo dois elementos raiz com os mesmos IDs
Pensar no nível de componente
- Uma abordagem melhor é tratar o HTML como componentes
- Identificar a essência desse componente
- Como o usuário obtém mais informações sobre um item específico
- Quando o usuário clica no botão, a informação aparece ou, se não houver informação, um erro é renderizado; em ambos os casos, o componente passa para um estado estático
-
Separar o componente por estado
- Estado de placeholder:
<!-- info-component-placeholder.html --> <div id="info-component"> <button data-on-click="@get('/product/{{product.id}}/info')"> Get Info! </button> </div> - Estado de exibição da informação:
<!-- info-component-get.html --> <div id="info-component"> {% if alert %}<div id="alert">{{ alert }}</div>{% endif %} <p>{{product.additional_information}}</p> </div> - Quando o servidor renderiza o HTML, o Datastar atualiza a página automaticamente
- Pensar em nível de componente ajuda a evitar entrar em estados incorretos ou perder o estado do usuário
- Estado de placeholder:
Atualizando vários componentes ao mesmo tempo
- Um ponto marcante da apresentação de David Guillot foi que, quando o app atualizava a contagem de itens favoritos, o componente alterado e também um elemento de contagem muito distante dele eram atualizados juntos
- No HTMX, isso exigia disparar um evento JavaScript, que por sua vez disparava uma requisição GET em um componente remoto
- No Datastar, é possível atualizar vários componentes ao mesmo tempo até dentro de uma função síncrona
-
Exemplo de carrinho de compras
- Componente para adicionar ao carrinho:
<form id="purchase-item" data-on-submit="@post('/add-item', {contentType: 'form'})">" > <input type=hidden name="cart-id" value="{{cart.id}}"> <input type=hidden name="item-id" value="{{item.id}}"> <fieldset> <button data-on-click="$quantity -= 1">-</button> <label>Quantidade <input name=quantity type=number data-bind-quantity value=1> </label> <button data-on-click="$quantity += 1">+</button> </fieldset> <button type=submit>Adicionar ao carrinho</button> {% if msg %} <p class=message>{{msg}}</p> {% endif %} </form> - Componente de exibição da contagem do carrinho:
<div id="cart-count"> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <use href="#shoppingCart"> </svg> {{count}} </div> - Atualizando os dois componentes na mesma requisição em Django:
from datastar_py.consts import ElementPatchMode from datastar_py.django import ( DatastarResponse, ServerSentEventGenerator as SSE, ) def add_item(request): # omissão de atualizações de estado importantes return DatastarResponse([ SSE.patch_elements( render_to_string('purchase-item.html', context=dict(cart=cart, item=item, msg='Item added!')) ), SSE.patch_elements( render_to_string('cart-count.html', context=dict(count=item_count)) ), ])
- Componente para adicionar ao carrinho:
Filosofia web nativa
- Pela comunidade do Discord do Datastar, o autor passou a entender que o Datastar não é apenas um script utilitário, mas uma filosofia de construção de apps baseada nos primitivos nativos da web
- Enquanto o HTMX tenta evoluir a especificação HTML, o Datastar se interessa mais em promover a adoção de recursos web nativos, como:
- CSS view transitions
- Server-Sent Events
- Web Components etc.
- O autor obteve um grande resultado ao refatorar componentes complexos em AlpineJS e extrair Web Components simples para reutilizá-los em vários lugares
- É um ótimo padrão para alcançar alta localidade de comportamento e reutilização por meio da criação de elementos HTML customizados, mesmo sem ferramentas como React
Atualizações em tempo real para apps multiusuário
- Apps que tratam colaboração como funcionalidade de primeira classe se diferenciam dos demais, e o Datastar resolve esse desafio
- A maioria dos desenvolvedores com HTMX recorre a polling para buscar informações do servidor ou escreve código WebSocket customizado, aumentando a complexidade
- O Datastar usa uma tecnologia web simples chamada Server-Sent Events (SSE) para que o servidor faça o "push" das atualizações aos clientes conectados
- Quando um usuário adiciona um comentário ou um estado muda, o servidor atualiza o navegador imediatamente, com o mínimo de código extra
- Dá para construir dashboards em tempo real, painéis administrativos e ferramentas colaborativas sem JavaScript customizado
- Se a conexão do cliente cair, o navegador tenta se reconectar automaticamente, sem necessidade de código adicional
- Também é possível informar ao servidor o "último evento recebido"
Evitando complexidade excessiva
- A comunidade do Discord do Datastar ajudou o autor a entender a visão do Datastar para a construção de apps web
- Atualizações de UI baseadas em push
- Redução de complexidade
- Tratamento de casos localmente complexos com ferramentas como Web Components
- A comunidade também ajuda novos usuários a perceber quando estão abordando o problema de forma complexa demais
Dicas principais
- Não tenha medo de renderizar novamente o componente inteiro e enviá-lo
- É mais simples e não afeta muito a performance
- Pode até gerar melhor taxa de compressão, e o navegador é muito rápido ao fazer parsing de strings HTML
- O servidor é a fonte da verdade e é mais poderoso que o navegador
- Deixe o servidor lidar com a maior parte do estado; talvez você não precise de tantos sinais reativos quanto imagina
- Web Components são excelentes para encapsular lógica em elementos customizados com alta localidade de comportamento
- A animação do campo de estrelas no header do site do Datastar é um bom exemplo
- O elemento
<ds-starfield>encapsula todo o código da animação do campo de estrelas e expõe três atributos para alterar seu estado interno - O Datastar aciona esses atributos quando o valor de um input de faixa muda ou quando o mouse se move sobre o elemento
Possibilidades que vão além dos limites
- O potencial que o Datastar torna possível é o aspecto mais empolgante
- A comunidade cria regularmente projetos que vão muito além das limitações que desenvolvedores costumam encontrar com outras ferramentas
Casos de destaque
- O demo de monitoramento de banco de dados na página de exemplos
- Usa hypermedia para melhorar bastante a velocidade e o uso de memória de um demo apresentado em conferências de JavaScript
- Os 1 bilhão de checkboxes de Anders Murphy
- Quando o experimento com 1 milhão de checkboxes ultrapassou a capacidade do servidor, ele usou Datastar para implementar 1 bilhão em um servidor barato
- Um app web que mostra dados de todas as estações de radar dos EUA
- Quando o sinal de um radar muda, o ponto correspondente na UI muda em menos de 100 milissegundos
- Mais de 800 mil pontos por segundo são atualizados, e o usuário pode navegar no tempo até 1 hora atrás (com latência inferior a 700 milissegundos)
- O fato de isso ser possível em um app de hypermedia mostra o que o Datastar torna viável
Experiência de uso atual
- O autor ainda está na fase de exploração do Datastar e já implementa com rapidez e facilidade o tratamento AJAX de atualização de UI típico do HTMX
- Ele está aprendendo e experimentando vários padrões para conquistar ainda mais com o Datastar
- Há décadas ele se interessa por formas de oferecer melhor experiência ao usuário com atualizações em tempo real, e gosta do fato de o Datastar permitir atualizações baseadas em push até em código síncrono
- Quando começou a usar HTMX, sentiu uma grande alegria, mas desde que migrou para o Datastar sente que não perdeu nada e, pelo contrário, ganhou muito mais
- Se você sentiu essa alegria ao usar HTMX, provavelmente vai sentir o mesmo salto com Datastar — como redescobrir aquilo que a web sempre deveria ter sido
2 comentários
Datastar - framework hipermídia leve para criar aplicativos web interativos
Comentários no Hacker News
hx-trigger="click", já reduz 20% dos atributos. E talvez a credibilidade aumentasse se o HTML fosse escrito de forma mais acessível, como usar<button>em vez de<span>. No fim, a grande força do Datastar parece ser que ele já vem com recursos do Alpine ou do Stimulus embutidos, e isso é realmente impressionantedata-replace-urlpara atualizar automaticamente a URL da view atual com coordenadas comox=123&y=456<span hx-target="#rebuild-bundle-status-button" hx-select="#rebuild-bundle-status-button" hx-swap="outerHTML" hx-trigger="click" hx-get="/rebuild/status-button"></span>aparentemente vira este código em datastar:<span data-on-click="@get('/rebuild/status-button')"></span>E os outros exemplos me confundem ainda mais. No fim, não entendo por que alguém migraria de htmx para Datastar/rebuild/status-button, extraia do HTML retornado o elemento#rebuild-bundle-status-buttone substitua o elemento existente”. Já no Datastar, significa: “ao clicar no span, siga as instruções vindas de/rebuild/status-button”. Se o servidor retornar vários elementos com IDs, o Datastar reconhece e substitui todos eles automaticamente. Ou seja, sem precisar detarget,selectouswap, basta usar IDs para que funcione como esperadospanpara clique? Umbuttonou um link não seria mais apropriado?htmx-swap-oob="true"é obrigatório; sem esse atributo, o comportamento não é o esperado 2. por outro lado, se não for OOB, terhtmx-swap-oob="true"faz com que seja ignorado ou funcione errado. Isso acaba sendo bem incômodo porque, para reutilizar o mesmo componente como OOB e não OOB, o servidor precisa enviar uma flagisOobtoda vez