Aller au contenu principal

Découverte de HTMX

HTMX mérite d’être connu pour une raison simple : il permet de remettre du dynamisme dans une page web sans embarquer immédiatement React, Angular ou une autre SPA complète. Si votre besoin se limite à une recherche live, un formulaire qui met à jour une liste, une suppression sans rechargement ou un petit écran d’administration, HTMX peut suffire largement. L’intérêt n’est pas de remplacer tous les frameworks front modernes, mais de rappeler qu’entre une page totalement statique et une SPA lourde, il existe une voie plus sobre et souvent très efficace.

1. Le vrai intérêt de HTMX

Avec HTMX, vous gardez votre HTML comme point central. Au lieu d’écrire beaucoup de JavaScript côté client pour appeler une API JSON, gérer un état local, puis reconstruire le DOM, vous demandez simplement au serveur de renvoyer un fragment HTML prêt à être injecté dans la page.

Dit autrement :

  • le navigateur déclenche une requête
  • le serveur renvoie du HTML
  • HTMX insère ce HTML au bon endroit

Cette approche paraît presque trop simple aujourd’hui, et c’est justement ce qui la rend intéressante.

2. Quand HTMX est souvent une meilleure réponse

HTMX devient pertinent quand vous avez besoin d’un peu de dynamisme, mais pas d’une vraie application front complexe.

Exemples classiques :

  • une recherche instantanée
  • une liste que l’on filtre ou recharge
  • un mini CRUD dans une interface admin
  • un formulaire qui met à jour une zone de la page
  • une modale ou un panneau latéral chargés à la demande

Dans ce genre de cas, partir sur une SPA peut vite coûter trop cher en complexité :

  • build front séparé
  • gestion d’état
  • routes client
  • appels API JSON
  • mapping des données
  • code JavaScript métier uniquement pour remettre du HTML dans la page

HTMX permet souvent de rester dans une logique KISS : le serveur produit encore le rendu, mais l’expérience utilisateur reste fluide.

3. La démo de départ

Pour cet article, je pars d’un POC plus complet réalisé avec Node.js, Express et HTMX. L’idée n’est pas de tout reprendre, mais d’en extraire une version plus simple et plus lisible.

La démo montre deux usages concrets :

  • une recherche de pays sans rechargement de page
  • un mini backlog avec création, édition et suppression en direct

Le message à retenir est important : HTMX ne fait pas de magie, il s’appuie surtout sur une bonne vieille application serveur qui sait rendre du HTML.

4. Le principe en une ligne de code

Le coeur de HTMX tient souvent dans des attributs HTML comme ceux-ci :

<input
type="search"
name="q"
hx-get="/search"
hx-trigger="input changed delay:350ms, search"
hx-target="#search-results"
>

Ici, l’intention est très lisible :

  • quand l’utilisateur tape
  • HTMX envoie une requête GET /search
  • la réponse est injectée dans #search-results

Vous n’avez pas écrit de fetch, pas de addEventListener, pas de code de rendu manuel. Vous avez seulement décrit le comportement attendu dans le HTML.

5. Exemple concret : une recherche live

Dans la démo, le serveur expose une route /search qui appelle l’API restcountries, puis renvoie directement une liste de cartes HTML.

app.get("/search", async (req, res) => {
const q = String(req.query.q || "").trim();

if (!q) return res.send("");

try {
const response = await fetch(
`https://restcountries.com/v3.1/name/${encodeURIComponent(q)}`
);

if (!response.ok) return res.send(renderEmpty("Aucun résultat"));

const countries = await response.json();
res.send(countries.slice(0, 5).map(renderCountry).join(""));
} catch {
res.send(renderEmpty("Aucun résultat"));
}
});

Le point intéressant ici n’est pas l’appel HTTP externe. Le point intéressant est que le serveur renvoie déjà le morceau de HTML final attendu par la page.

Exemple simplifié du rendu :

const renderCountry = (country) => {
return `
<div class="country-card">
<strong>${escapeHtml(country.name.common)}</strong>
<span>${country.population.toLocaleString()} habitants</span>
</div>
`;
};

Dans une approche SPA classique, vous auriez souvent :

  • un endpoint JSON
  • une couche de transformation côté front
  • un composant chargé d’afficher la liste

Ici, le serveur fait déjà ce travail. Pour ce type de besoin, ce n’est pas une faiblesse. C’est souvent un gain.

6. Exemple concret : un mini CRUD sans SPA

Le deuxième usage est encore plus parlant : un backlog produit avec ajout, édition et suppression sans rechargement complet.

Le formulaire d’ajout

<form
hx-post="/tasks"
hx-target="#tasks"
hx-swap="innerHTML"
>
<input name="title" placeholder="Nouvelle action..." required>
<input name="owner" placeholder="Owner" value="Equipe" required>
<select name="status">
<option>Todo</option>
<option>Doing</option>
<option>Done</option>
</select>
<button type="submit">Ajouter</button>
</form>

Quand le formulaire part, le serveur ajoute la tâche puis renvoie le HTML de la liste mise à jour :

app.post("/tasks", (req, res) => {
const taskInput = normalizeTaskInput(req.body);

if (!taskInput.title) {
return res.status(422).send(renderEmpty("Le titre est obligatoire."));
}

tasks.unshift({ id: nextTaskId++, ...taskInput });
res.send(renderTasks());
});

L’édition inline

Chaque ligne de tâche peut demander sa version éditable :

<button
hx-get="/tasks/2/edit"
hx-target="#task-2"
hx-swap="outerHTML"
>
Edit
</button>

Le serveur renvoie alors un petit formulaire HTML à la place de la ligne affichée.

La suppression

Même logique pour la suppression :

<button
hx-delete="/tasks/2"
hx-target="#task-2"
hx-swap="outerHTML swap:120ms"
>
Delete
</button>
app.delete("/tasks/:id", (req, res) => {
const index = tasks.findIndex(task => task.id === Number(req.params.id));

if (index !== -1) tasks.splice(index, 1);

res.send("");
});

La ligne ciblée disparaît, sans rechargement global, sans état front sophistiqué, sans store, sans reducer, sans sérialisation JSON.

7. Pourquoi cela reste intéressant en 2026

Le vrai sujet n’est pas de dire que React ou Angular seraient “mauvais”. Ce serait absurde. Ces outils restent très solides quand vous construisez une vraie application front riche, avec beaucoup d’état client, de composants interactifs et de navigation complexe.

Le vrai sujet est plutôt celui-ci :

avez-vous réellement besoin d’une SPA pour cette page précise ?

Si la réponse est non, HTMX peut vous faire gagner :

  • du temps
  • de la simplicité
  • moins de code JavaScript à maintenir
  • moins de duplication entre back et front

8. Les limites à connaître

HTMX n’est pas la solution à tout.

Vous atteindrez plus vite ses limites si vous avez :

  • une interface très fortement pilotée par l’état côté client
  • beaucoup d’interactions croisées entre composants
  • une logique offline
  • un vrai besoin de routing front complexe
  • une expérience applicative proche d’un produit SaaS lourd

Dans ces cas-là, un framework front complet reprend logiquement l’avantage.

L’idée n’est donc pas d’opposer dogmatiquement les approches. L’idée est de remettre HTMX dans la boîte à outils.

9. Un extrait plus complet de la démo

Voici un extrait condensé du serveur utilisé dans le POC. Le but est surtout de voir à quel point la mécanique reste lisible.

server.js
import express from "express";

const app = express();
const port = process.env.PORT || 3000;
const validStatuses = ["Todo", "Doing", "Done"];

app.use(express.urlencoded({ extended: false }));
app.use(express.static("public"));

let nextTaskId = 4;
const tasks = [
{ id: 1, title: "Brancher une route Express", owner: "Back", status: "Done" },
{ id: 2, title: "Rendre le formulaire instantané", owner: "Front", status: "Doing" },
{ id: 3, title: "Supprimer sans recharger la page", owner: "Produit", status: "Todo" },
];

const escapeHtml = (value = "") => {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
};

const taskById = (id) => tasks.find(task => task.id === Number(id));

const normalizeTaskInput = (body, fallbackStatus = "Todo") => {
return {
title: String(body.title || "").trim(),
owner: String(body.owner || "Equipe").trim(),
status: validStatuses.includes(body.status) ? body.status : fallbackStatus,
...

10. Ce qu’il faut retenir

HTMX est intéressant parce qu’il remet une idée simple au centre : toutes les pages dynamiques n’ont pas besoin d’une SPA complète. Quand le besoin reste modéré, il permet souvent d’obtenir une UX propre avec très peu de JavaScript applicatif, tout en gardant un code serveur lisible.

Si vous avez juste besoin d’un peu d’interactivité et que vous voulez rester dans une approche pragmatique, HTMX mérite clairement une place dans votre boîte à outils.