Por qué cambié de HTMX a Datastar
(everydaysuperpowers.dev)- Al usar HTMX, pude reducir la cantidad de código en alrededor de un 70%, pero también experimenté problemas de sincronización entre interfaces y un aumento en la complejidad de la gestión de estado del frontend
- Después de adoptar Datastar, desarrollar aplicaciones multiusuario en tiempo real se volvió más fácil de mantener y con código más conciso, sin necesidad de WebSockets
- Mientras que HTMX distribuye la lógica de comportamiento en torno a atributos HTML, Datastar mejora la consistencia y mantenibilidad de la lógica mediante un modelo de actualización guiado por el servidor
- La API de Datastar tiene menos atributos y da la sensación de mejorar la legibilidad y productividad del código
- Datastar aprovecha activamente tecnologías nativas de la web como Server-Sent Events (SSE), Web Components y CSS View Transitions, lo que hace posible la colaboración en tiempo real y una estructura de componentes reutilizables
Introducción y motivación
- En 2022, David Guillot compartió en DjangoCon Europe un caso en el que migró un SaaS basado en React a HTMX, redujo el código en aproximadamente un 70% y mejoró funcionalidades
- Después de eso, muchos equipos experimentaron que, al pasar de una app de página única (SPA) a una aplicación hipermedia multipágina, se reducía el código y mejoraban tanto la experiencia de desarrollo como la experiencia de usuario
- El autor también confirmó, al migrar un proyecto de HTMX a Datastar, que el código se volvió más corto y que era posible desarrollar una app multiusuario en tiempo real sin WebSocket ni gestión de estado compleja
Problemas que motivaron el cambio
- Mientras preparaba una charla de FlaskCon 2025, intentó sincronizar la UI combinando HTMX y AlpineJS, pero se encontró con problemas de sincronización de interfaz
- Como ambas librerías son herramientas separadas creadas por desarrolladores distintos, no pueden comunicarse entre sí, por lo que el desarrollador tiene que encargarse manualmente de integrarlas
- Inicializar componentes en distintos momentos y coordinar eventos requirió mucho más código y tiempo de depuración de lo esperado
- Le llamó la atención que Datastar integrara las funciones de ambas librerías y aun así se entregara en menos de 11 KB, así que decidió probarlo
- Esto favorece el rendimiento de carga de página para usuarios en dispositivos móviles
Un mejor diseño de API en Datastar
- La API de Datastar se siente mucho más ligera que la de HTMX, y requiere menos atributos adicionales para obtener el resultado deseado
- HTMX necesita varios atributos en la mayoría de las interacciones
- Definir la URL, especificar el elemento objetivo y configurar cómo se procesa la respuesta se hace con atributos separados
- Normalmente se usan 2 o 3 atributos cada vez, y a veces hay que seguir la cadena de herencia para entender cómo se comportan
<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> - Datastar normalmente implementa la misma funcionalidad con un solo atributo
<a data-on-click="@get('/rebuild/status-button')"></a>- Incluso meses después, al volver a ver el código, es fácil entender cómo funciona
Diferencias en la forma de operar
- Mientras que HTMX es una librería de frontend cuyo objetivo es extender la especificación HTML, Datastar es una librería guiada por el servidor cuyo objetivo es construir aplicaciones de actualización en tiempo real de alto rendimiento usando tecnologías nativas de la web
- HTMX define el comportamiento agregando atributos al elemento que dispara la solicitud, y aunque actualice elementos muy alejados en la página, la lógica queda dispersa en varias capas
- Datastar hace que el servidor decida qué debe cambiar, concentrando toda la lógica de actualización en un solo lugar
-
Ejemplo de 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>- Al presionar el botón, se envía una solicitud GET a
/info, se reemplaza el botón con el elemento del IDinfo-detailsde la respuesta, y se reemplaza el elemento con IDalertde la página por el de la respuesta - El botón necesita saber demasiada información y además debe anticipar qué devolverá el servidor, lo que debilita el principio de HTMX de "localidad del comportamiento (locality of behavior)"
- Al presionar el botón, se envía una solicitud GET a
-
Enfoque mejorado de Datastar
<div> <div id="alert"></div> <button id="info-details" data-on-click="@get('/info')"> Get Info! </button> </div>- El servidor devuelve una cadena HTML que incluye dos elementos raíz con los mismos ID
<p id="info-details">These are the details you are looking for…</p> <div id="alert">Alert! This is a test.</div> - Es una opción simple y con buen rendimiento
- El servidor devuelve una cadena HTML que incluye dos elementos raíz con los mismos ID
Pensar a nivel de componente
- Un mejor enfoque es tratar el HTML como componentes
- Identificar la esencia de ese componente
- Cómo obtiene el usuario información adicional sobre un elemento específico
- Cuando el usuario hace clic en el botón, aparece la información o, si no la hay, se renderiza un error; en cualquier caso, el componente pasa a un estado estático
-
Separar componentes 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 con información mostrada:
<!-- info-component-get.html --> <div id="info-component"> {% if alert %}<div id="alert">{{ alert }}</div>{% endif %} <p>{{product.additional_information}}</p> </div> - Cuando el servidor renderiza el HTML, Datastar actualiza automáticamente la página
- Pensar a nivel de componente evita entrar en estados incorrectos o perder el estado del usuario
- Estado de placeholder:
Actualizar varios componentes al mismo tiempo
- Uno de los puntos impresionantes de la charla de David Guillot fue que, cuando la app actualizaba la cantidad de favoritos, también se actualizaba un contador ubicado muy lejos del componente modificado
- HTMX dispara un evento JavaScript, que a su vez dispara una solicitud GET desde un componente remoto
- Datastar permite actualizar varios componentes al mismo tiempo incluso dentro de una función síncrona
-
Ejemplo del carrito de compras
- Componente para agregar al carrito:
<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>Cantidad <input name=quantity type=number data-bind-quantity value=1> </label> <button data-on-click="$quantity += 1">+</button> </fieldset> <button type=submit>Agregar al carrito</button> {% if msg %} <p class=message>{{msg}}</p> {% endif %} </form> - Componente de visualización del conteo del carrito:
<div id="cart-count"> <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <use href="#shoppingCart"> </svg> {{count}} </div> - En Django, ambos componentes se actualizan en la misma solicitud:
from datastar_py.consts import ElementPatchMode from datastar_py.django import ( DatastarResponse, ServerSentEventGenerator as SSE, ) def add_item(request): # se omite la actualización importante del estado 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 agregar al carrito:
Filosofía web nativa
- A través de la comunidad de Datastar en Discord, el autor entendió que Datastar no es solo un script helper, sino una filosofía para construir apps aprovechando los primitivos básicos de la web
- Mientras HTMX busca hacer evolucionar la especificación HTML, Datastar está más interesado en fomentar la adopción de capacidades nativas de la web
- CSS view transitions
- Server-Sent Events
- Web Components, entre otros
- Un gran logro fue refactorizar componentes complejos de AlpineJS para extraer Web Components simples y reutilizarlos en varios lugares
- Es un excelente patrón para lograr alta localidad del comportamiento y reutilización mediante la creación de elementos HTML personalizados, sin herramientas como React
Actualizaciones en tiempo real para apps multiusuario
- Las apps con colaboración como función de primera clase se diferencian de otras, y Datastar resuelve ese reto
- La mayoría de los desarrolladores con HTMX obtienen información del servidor mediante polling o escriben código WebSocket personalizado, aumentando la complejidad
- Datastar usa una tecnología web simple llamada Server-Sent Events (SSE) para que el servidor empuje actualizaciones a los clientes conectados
- Cuando un usuario agrega un comentario o cambia un estado, el servidor actualiza el navegador de inmediato, con muy poco código adicional
- Permite construir dashboards en tiempo real, paneles de administración y herramientas colaborativas sin JavaScript personalizado
- Si la conexión del cliente se interrumpe, el navegador intenta reconectarse automáticamente, sin requerir código adicional
- También puede informar al servidor sobre el último evento recibido
Evitar la complejidad excesiva
- La comunidad de Datastar en Discord ayudó a entender la visión de Datastar sobre cómo crear aplicaciones web
- Actualizaciones de UI basadas en push
- Reducción de complejidad
- Manejo de situaciones localmente complejas usando herramientas como Web Components
- La comunidad también ayuda a que los nuevos usuarios se den cuenta cuando están abordando el problema de forma innecesariamente compleja
Consejos clave
- No temas volver a renderizar el componente completo y enviarlo
- Es más fácil y no tiene un gran impacto en el rendimiento
- Puede ofrecer mejor compresión, y el navegador es muy rápido parseando cadenas HTML
- El servidor es la fuente de verdad del estado y es más poderoso que el navegador
- Deja que el servidor maneje la mayor parte del estado; quizá necesites menos señales reactivas de las que imaginas
- Los Web Components son excelentes para encapsular lógica dentro de elementos personalizados con alta localidad del comportamiento
- La animación del campo de estrellas del encabezado del sitio web de Datastar es un buen ejemplo
- El elemento
<ds-starfield>encapsula todo el código de la animación del campo de estrellas y expone tres atributos para cambiar su estado interno - Datastar controla esos atributos cuando cambia un input de rango o cuando el mouse se mueve sobre el elemento
Posibilidades que van más allá de los límites
- Lo más emocionante es el potencial que Datastar hace posible
- La comunidad crea regularmente proyectos que superan por mucho los límites que suelen encontrar los desarrolladores con otras herramientas
Casos destacados
- El demo de monitoreo de base de datos en la página de ejemplos
- Aprovecha Hypermedia para mejorar de forma importante la velocidad y el uso de memoria frente a demos presentados en conferencias de JavaScript
- 1 Billion Checkboxes de Anders Murphy
- Cuando el experimento de 1 millón de checkboxes superó la capacidad del servidor, usó Datastar para implementar 1 mil millones en un servidor barato
- Una app web que muestra los datos de todas las estaciones de radar de Estados Unidos
- Cuando cambia la señal de un radar, el punto correspondiente en la UI cambia en menos de 100 milisegundos
- Se actualizan más de 800 mil puntos por segundo, y el usuario puede desplazarse hasta 1 hora hacia atrás con una latencia de menos de 700 milisegundos
- Que esto sea posible como app de Hypermedia muestra lo que Datastar puede habilitar
Experiencia actual de uso
- Aún está en una etapa de exploración de Datastar y puede implementar rápida y fácilmente el manejo AJAX de actualizaciones de UI típico de HTMX
- Está aprendiendo y experimentando con varios patrones para lograr más cosas con Datastar
- Desde hace décadas le interesa cómo ofrecer mejores experiencias de usuario con actualizaciones en tiempo real, y le gusta que Datastar permita actualizaciones basadas en push incluso en código síncrono
- Cuando empezó a usar HTMX sintió una gran alegría, pero desde que cambió a Datastar siente que no perdió nada y, por el contrario, ganó muchísimo más
- Si usar HTMX te dio esa alegría, con Datastar probablemente sentirás ese mismo salto otra vez, como redescubrir lo que la web siempre debió hacer
2 comentarios
Datastar - framework hipermedia liviano para crear aplicaciones web interactivas
Comentarios en Hacker News
hx-trigger="click", ya reduces un 20% de los atributos. Y si además se escribiera un HTML más accesible, usando<button>en vez de<span>, por ejemplo, daría más confianza. Al final, la gran fortaleza de Datastar parece ser que viene con funcionalidades tipo Alpine o Stimulus integradas, y eso sí me parece realmente impresionantedata-replace-urlque actualizara automáticamente la URL de la vista actual con esas coordenadas (x=123&y=456, etc.)<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>parece convertirse en este código de datastar:<span data-on-click="@get('/rebuild/status-button')"></span>Y los otros ejemplos me confunden todavía más. Al final, no entiendo por qué alguien se cambiaría de htmx a Datastar/rebuild/status-button, extrae el elemento#rebuild-bundle-status-buttondel HTML devuelto y reemplaza el elemento actual”. En cambio, en Datastar sería “al hacer clic en el span, sigue directamente las instrucciones de/rebuild/status-button”. Si el servidor devuelve elementos con varios ID, Datastar identifica todos esos elementos y los reemplaza automáticamente. O sea, no hace falta usar target, select ni swap: con solo poner los ID ya funciona como se esperaspancomo elemento clicable. Unbuttono un enlace parecerían más apropiadoshtmx-swap-oob="true"es obligatorio; si falta ese atributo, no funciona como uno espera 2. por el contrario, si no es OOB, entonces si tienehtmx-swap-oob="true", se ignora o se comporta mal. Por eso, cuando quieres reutilizar el mismo componente como OOB y no OOB, terminas teniendo que enviar siempre un flagisOobdesde el servidor, y eso es bastante molesto