HTMX para quem vem do React: uma mudança de mentalidade

1. O abismo conceitual: React vs. HTMX

1.1. React: estado no cliente, virtual DOM e o paradigma de componentes

No React, tudo gira em torno do estado gerenciado no cliente. Você declara componentes, usa useState para armazenar dados localmente, useEffect para sincronizar com APIs externas e o Virtual DOM para reconciliar mudanças. O servidor é apenas uma fonte de dados — um backend que entrega JSON. O navegador faz o trabalho pesado de montar a UI.

1.2. HTMX: hipertexto como fonte da verdade, servidor como autoridade

HTMX inverte essa lógica. O servidor renderiza HTML completo ou fragmentos. O navegador apenas faz requisições HTTP (via atributos como hx-get, hx-post) e substitui partes da página com a resposta HTML. Não há estado no cliente, não há Virtual DOM, não há reconciliação. O servidor decide o que mostrar.

1.3. Por que a mudança de mentalidade é mais difícil que aprender sintaxe

A dificuldade não está em decorar atributos do HTMX. Está em abandonar o reflexo de criar estado para tudo. Você precisa pensar: "O que o servidor precisa me enviar quando o usuário clicar aqui?" em vez de "Qual variável de estado eu atualizo?". É uma troca de paradigma: de cliente-centrado para servidor-centrado.

2. Como HTMX simplifica o que React complica

2.1. Sem estado de componente: requisições HTTP substituem useState e useReducer

React:

const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;

HTMX:

<button hx-post="/increment" hx-target="#counter" hx-swap="innerHTML">
  +1
</button>
<span id="counter">0</span>

O servidor recebe a requisição, incrementa o valor no banco ou sessão, e devolve o novo número como HTML puro.

2.2. Sem roteamento no cliente: URLs reais e navegação via hx-get/hx-push-url

React exige React Router, configuração de rotas no cliente e componentes para cada página. HTMX usa links comuns e atributos:

<a href="/produtos">Produtos</a>
<!-- ou, para carregar sem recarregar a página: -->
<a hx-get="/produtos" hx-target="#conteudo" hx-push-url="true">Produtos</a>

A URL real muda, o histórico funciona, e o servidor entrega o HTML completo da seção.

2.3. Sem bundlers complexos: HTML puro, atributos declarativos e servidor existente

React exige Webpack, Vite, Babel, npm install, node_modules. HTMX exige apenas uma tag script no HTML:

<script src="https://unpkg.com/htmx.org@1.9.10"></script>

Seu servidor existente (Node, Python Flask, Ruby on Rails, Go, PHP) já serve HTML. HTMX apenas estende esse HTML com atributos.

3. Padrões comuns do React e seus equivalentes em HTMX

3.1. Formulários controlados vs. hx-post com validação no servidor

React:

const [email, setEmail] = useState('');
<form onSubmit={handleSubmit}>
  <input value={email} onChange={e => setEmail(e.target.value)} />
</form>

HTMX:

<form hx-post="/cadastro" hx-target="#mensagem">
  <input name="email" type="email" required>
  <button type="submit">Enviar</button>
</form>
<div id="mensagem"></div>

O servidor valida, processa e devolve HTML com erro ou sucesso. Sem estado intermediário no cliente.

3.2. Listas com filtro e paginação: hx-trigger + hx-target vs. useEffect + estado

React:

const [filtro, setFiltro] = useState('');
const [itens, setItens] = useState([]);
useEffect(() => {
  fetch(`/api/itens?q=${filtro}`).then(r => r.json()).then(setItens);
}, [filtro]);

HTMX:

<input name="q" hx-get="/itens" hx-target="#lista" hx-trigger="keyup delay:300ms">
<div id="lista">
  <!-- HTML servido pelo servidor -->
</div>

3.3. Modais e abas: substituindo useState por troca de fragmentos HTML

React usa const [abaAtiva, setAbaAtiva] = useState('info'). HTMX carrega o conteúdo da aba do servidor:

<button hx-get="/aba/info" hx-target="#conteudo-aba">Informações</button>
<button hx-get="/aba/historico" hx-target="#conteudo-aba">Histórico</button>
<div id="conteudo-aba"></div>

4. Gerenciamento de estado em um mundo sem cliente

4.1. Estado efêmero: cookies, sessão e banco de dados como camadas naturais

Sem useState, o estado vive no servidor: sessão HTTP, banco de dados, cookies. O cliente é apenas uma tela que exibe o que o servidor decide mostrar.

4.2. Atualizações em cascata: hx-swap-oob (out-of-band) para múltiplas áreas

Uma única resposta pode atualizar vários elementos da página:

<div id="carrinho-count" hx-swap-oob="true">3 itens</div>
<div id="total">R$ 150,00</div>

O servidor envia ambos os fragmentos na mesma resposta, e HTMX os coloca nos lugares corretos.

4.3. Cache inteligente: servidor decide o que re-renderizar, não o cliente

Headers HTTP padrão (Cache-Control, ETag) funcionam naturalmente. O servidor pode retornar 304 Not Modified e o navegador usa o cache. Sem lógica de cache no cliente.

5. Interatividade sem JavaScript pesado

5.1. Eventos do navegador reutilizados: hx-trigger (click, change, keyup, reveal)

<div hx-get="/mais-conteudo" hx-trigger="revealed">
  <!-- Carrega mais conteúdo quando visível na tela -->
</div>

5.2. Transições e animações: CSS puro + hx-swap (morph, delay, settle)

<div hx-get="/novo-conteudo" hx-swap="morph:300ms">
  <!-- Transição suave via CSS + HTMX morph -->
</div>

5.3. Quando usar JavaScript ainda: complementos com Alpine.js ou Hyperscript

Para interações puramente locais (acordeões, tooltips), Alpine.js é leve e complementar:

<div x-data="{ open: false }">
  <button @click="open = !open">Toggle</button>
  <div x-show="open">Conteúdo local</div>
</div>

6. Performance e SEO: ganhos imediatos

6.1. Menos JavaScript = carregamento mais rápido e menor consumo de bateria

HTMX tem ~14KB minificado. React + ReactDOM tem ~120KB. Em dispositivos móveis, menos JS significa menos tempo de parse, menos energia gasta.

6.2. HTML servido é indexável por padrão (sem SSR extra)

React precisa de Next.js ou Gatsby para SSR. HTMX já serve HTML — Google indexa direto, sem JavaScript necessário.

6.3. Cache em camadas funciona naturalmente (CDN, browser, servidor)

Headers HTTP padrão funcionam. CDNs como Cloudflare cacheiam respostas HTML sem configuração especial.

7. Quando (não) migrar do React para HTMX

7.1. Aplicações ideais: CRUDs, dashboards, sites institucionais, formulários complexos

Qualquer app onde a interação principal é: usuário clica → servidor processa → nova tela aparece. HTMX brilha aqui.

7.2. Casos contra: jogos, editores em tempo real, aplicações offline-first

Se você precisa de manipulação contínua de estado no cliente (arrastar elementos, edição colaborativa em tempo real), React (ou algo como SolidJS) é mais adequado.

7.3. Estratégia híbrida: React em partes isoladas + HTMX no restante

Você pode ter um modal React dentro de uma página HTMX, ou um componente de chat em tempo real ao lado de formulários HTMX. Não é uma escolha binária.

8. Começando na prática: um mini-projeto de migração

8.1. Setup mínimo: servidor (Node, Python, Go) + HTMX via CDN

HTML inicial:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
  <div hx-get="/saudacao" hx-trigger="load" hx-target="this">
    Carregando...
  </div>
</body>
</html>

Servidor (Node/Express):

app.get('/saudacao', (req, res) => {
  res.send('<h1>Olá, HTMX!</h1>');
});

8.2. Exemplo passo a passo: transformar um useState + fetch em hx-get

Antes (React):

const [dados, setDados] = useState(null);
fetch('/api/dados').then(r => r.json()).then(setDados);

Depois (HTMX):

<div hx-get="/dados" hx-trigger="load" hx-target="#container">
  Carregando...
</div>
<div id="container"></div>

Servidor retorna HTML diretamente:

app.get('/dados', (req, res) => {
  res.send('<ul><li>Item 1</li><li>Item 2</li></ul>');
});

8.3. Debug e ferramentas: extensão HTMX DevTools, logs de requisição, headers customizados

Instale a extensão HTMX DevTools para Chrome/Firefox. Use hx-headers para enviar tokens CSRF ou headers customizados:

<div hx-get="/protegido" hx-headers='{"X-CSRF": "token123"}'></div>

Referências