10 pontos por GN⁺ 2025-10-11 | 2 comentários | Compartilhar no WhatsApp
  • 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 ID info-details presente na resposta, e o elemento da página com ID alert é 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
  • 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

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

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))  
              ),  
          ])  
      

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

 
GN⁺ 2025-10-11
Comentários no Hacker News
  • Agradeço ao Chris por sair da zona de conforto, encarar esse desafio e compartilhar a experiência com a gente. Posso ser um pouco enviesado porque já faço webapps com htmx há 4 anos, mas acho que isso deixa clara a principal diferença arquitetural entre Datastar e htmx: o htmx é orientado a HTML, enquanto o Datastar é orientado ao servidor. É verdade que a API do lado do cliente é simples, mas isso acontece porque a lógica do lado do servidor fica mais complexa. Por exemplo, se um elemento HTML não tiver informação sobre onde inserir o fragment retornado pelo servidor, essa informação precisa ser registrada no servidor; ou seja, a complexidade necessariamente vai existir de um lado ou de outro. A escolha de arquitetura parece ser uma questão de preferência. No exemplo, a lógica de “less attributes” (menos atributos) não me parece 100% justa, porque inclui atributos opcionais no htmx; por exemplo, se tirar 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 impressionante
    • Acho que, com Datastar, a complexidade cai bastante porque você pode baixar e atualizar tudo de uma vez, sem precisar implementar separadamente um sistema de eventos para atualizar outras partes da página em tempo real. Claro, dependendo do caso, uma abordagem baseada em eventos ou carregamento posterior pode ser melhor
    • Vi a observação de que ele seria como “ter recursos do Alpine ou Stimulus embutidos no HTMX”, e isso me fez pensar em usar HTMX em um projeto pessoal. Queria saber se existe algum material explicando que bibliotecas adicionais como AlpineJS ou Stimulus acabam sendo necessárias
    • Houve aquela discussão de que, se o elemento HTML não tiver informação sobre onde inserir o fragment, o servidor precisa saber disso; mas também fico pensando se, nesse caso, o frontend não ficaria mais leve e rápido. Especialmente se houver muitos elementos, não?
    • Essa estrutura lembra um pouco o framework Seaside, do Pharo. Na nossa empresa, quando fazíamos apps B2B com Pharo, o estado da UI era gerenciado no backend, então havia bastante troca entre front e backend. Para B2B, onde tempo real e latência não são tão críticos, funciona bem; mas para apps B2C com grande necessidade de escala, não parece adequado
  • Como alguém que já usou Datastar e HTMX diretamente, ainda não entendi bem qual seria a grande diferença prática ao construir um app com Datastar. Eu uso FastAPI, HTMX, Alpine.js e SSE juntos para coisas como exibição em tempo real de logs e atualização de status de deploy. Quando olho os exemplos de Datastar, não vejo claramente o que ficaria mais simples do que essa estrutura. (Referência de código: devpush SSE partial). Também já tentei Web Components quando desenvolvia o Basecoat, mas por vários motivos, como problemas de estilo e gerenciamento de estado, acabei voltando ao HTML/CSS/JS tradicional. devpu.sh, basecoatui.com
    • Mesmo no HTMX, quando você pensa em extensibilidade funcional como no Datastar, muitas vezes fica mais simples apenas atualizar a lista inteira de uma vez. Em vez de atualizar o status de cada deploy individualmente, atualizar a lista inteira elimina preocupações com casos de borda como paginação, então tudo fica muito mais simples e o código mais enxuto
  • Para quem acha que o Datastar é insuficiente para tempo real/colaboração/multiplayer, quero mostrar três demos que funcionam sem recursos PRO e chegaram até a página principal do HN rodando até em uma VPS de 5 dólares. Elas mostram o quão bem-feita é a tecnologia do Datastar: Checkboxes, Cells, Game of Life Example. Os exemplos de checkboxes e cells têm renderização de view dinâmica, então dá para dar bastante zoom out, e também há backpressure no scroll virtual
    • Se entendi corretamente a estrutura do código, na prática parece que eles não usam o método recomendado de diff/patch do datastar, mas sim rerenderizam a página inteira toda vez. Na verdade, esse modelo mental me atrai mais justamente por parecer muito mais simples do que exemplos em que o servidor rastreia o estado do cliente de forma detalhada. Fico curioso para saber se apps complexos em geral também podem ser construídos assim. Se alguém tiver algum texto de referência sobre como fazer rastreamento de vários estados de widgets e rerenderização imediata quando usuários navegam por páginas diferentes, seria ótimo
    • No exemplo de checkboxes/cells, foi dito que dá para fazer “zoom out”, mas queria entender exatamente como isso funciona. E também teria sido legal se houvesse alguma opção como data-replace-url para atualizar automaticamente a URL da view atual com coordenadas como x=123&y=456
    • Ao ver a menção aos recursos PRO, percebi que eles seguem um modelo open core (uma parte open source, outra paga, licença de 299 dólares). Para mim, passo
  • Li recentemente este texto (htmx, datastar, greedy developer) e ouvi dizer que bons recursos centrais do Datastar foram movidos para a versão paga (Pro). Quero apoiar financeiramente frameworks, sejam open source ou pagos, mas esse tipo de precedente me preocupa
    • Eu também vinha acompanhando o Datastar há alguns meses, esperando o lançamento 1.0.0, mas agora meu entusiasmo esfriou completamente. Já me decepcionei muitas vezes com casos de “é open source, mas na prática não é”, então perdi a confiança
    • Na verdade, eu já tinha escrito que não gostava muito do Datastar, mas desta vez sinto vontade de defendê-lo um pouco. O criador do framework disponibilizou o código dele gratuitamente sob licença MIT, então os recursos que antes eram gratuitos continuam podendo ser usados sob MIT. Se alguém usou sem contribuir, depender de uma versão antiga é uma escolha da própria pessoa. Mudar para um modelo pago daqui para frente é um direito do dono do produto e, se precisar, é só fazer um fork
    • Paguei os 299 dólares pela licença PRO uma vez, mas ainda não usei os recursos PRO na prática. Eu queria fazer um clone do Google Sheets, mas mesmo sem PRO já dava para implementar o suficiente. (veja a demo cells)
    • Sempre achei que o pessoal do Datastar ia além de apenas divulgar o quanto ele é bom no Discord do HTMX; às vezes parecia até um pouco agressivo. Também já vi no reddit comentários com o tom de “se ele já tem tudo de que você precisa, então use a beta para sempre; open source não deve nada a ninguém”
    • Usando Datastar, lembrei imediatamente do caso antigo do Meteor.js. (Discussão no HN sobre Meteor.js)
  • Não estou entendendo bem os exemplos de código mostrados neste post. Por exemplo, este exemplo em htmx: <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
    • Basicamente, em HTMX isso significa: “quando clicar neste span, busque HTML em /rebuild/status-button, extraia do HTML retornado o elemento #rebuild-bundle-status-button e 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 de target, select ou swap, basta usar IDs para que funcione como esperado
    • A estrutura do Datastar concentra a lógica no backend. É como o HTML tradicional de antigamente, em que você fazia uma requisição, recebia HTML e o navegador renderizava; mas, no Datastar, depois que a página é carregada uma vez, cada interação envia uma requisição ao backend e recebe apenas as mudanças para aplicar. É a estrutura oposta de um SPA em que o frontend carrega a lógica e o backend só mantém estado mínimo. No fundo, é a mesma discussão recorrente sobre distribuição de lógica entre backend e frontend, e o Datastar tenta permitir interfaces dinâmicas mesmo concentrando a lógica no servidor
    • Mas por que usar uma tag span para clique? Um button ou um link não seria mais apropriado?
  • Ironicamente, o tema do texto é hipermídia, mas o link do site oficial do Datastar não está nele. Aqui está o site oficial: https://data-star.dev/
  • Uma das grandes vantagens do HTMX é que o cliente não precisa conhecer a estrutura dos dados retornados pelo servidor; se o cliente precisar conhecer IDs ou o significado de elementos individuais, parece que essa promessa se quebra. Claro, em muitos projetos usa-se OOB (Out of Band), então talvez essa separação perfeita de estrutura esteja mesmo distante da realidade. Seria ótimo ver surgir uma abordagem que pegue o melhor dos dois mundos
    • Na prática, o cliente não precisa saber de nada. O cliente executa uma ação e o servidor pode devolver a view inteira da página. O cliente então renderiza tudo imediatamente. É parecido com o modelo de immediate mode em videogames
  • Essa estrutura do Datastar, em que o servidor faz patch de HTML assim, não me parece boa do ponto de vista de separação de responsabilidades, e imagino que, quanto maior o app, mais trabalhoso fique gerenciar o servidor injetando pedaços de HTML o tempo todo
    • Mas também não é como se deixar o JS injetar HTML por fragmentos em todo lugar fosse melhor
    • Esse estilo de projetar endpoints que soltam fragmentos de HTML no servidor me passa uma sensação estranha
  • O Datastar me parece mais polido que o htmx. Já criei alguns projetos com htmx com sucesso, mas sempre me incomodou ter que adicionar JS de cola por causa do tratamento de eventos, especialmente com AlpineJS e afins. Se o Datastar conseguir reduzir essa necessidade, fico realmente animado
    • Recomendo ler o ensaio grugs around the fire no site do Datastar
  • Entrei relativamente tarde na tendência de hipermídia; comecei usando Datastar, mas hoje em dia migrei para HTMX. A API do Datastar é um pouco melhor, mas desde que o htmx 2.0 passou a suportar atualizações OOB (Out-Of-Band), na maioria das vezes acabo preferindo htmx
    • Essa frase “entrei tarde na hipermídia” me chamou atenção. É interessante lembrar que Ted Nelson cunhou o termo em 1965 e escreveu, na época, que “hyperfilm— a browsable or vari-sequenced movie— is only one of the possible hypermedia that require our attention”. Veja o artigo relacionado
    • O que me incomoda ao lidar com elementos OOB no HTMX: 1. se for OOB, htmx-swap-oob="true" é obrigatório; sem esse atributo, o comportamento não é o esperado 2. por outro lado, se não for OOB, ter htmx-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 flag isOob toda vez
    • Gosto mais da API do alpine-ajax. Basta especificar vários alvos e ele substitui cada elemento de forma consistente sem JS. Já os conceitos de signal/state do Datastar me parecem adicionar complexidade desnecessária