Boring Stack


4 Min. Lesezeit

HTML vom Server

templ, htmx und Tailwind statt einer JavaScript-App

Teil drei des Boring Stack. Nach der Sprache und dem Router kommt die grösste Entscheidung: was im Browser läuft.

Der Standard für eine Web-App sind heute zwei Anwendungen. Ein Backend, das JSON liefert, und ein Frontend in React oder Vue, das aus diesem JSON eine Oberfläche macht. Zwei Codebasen, zwei Build-Pipelines, zwei Sätze Abhängigkeiten und dazwischen ein API-Vertrag, den beide Seiten synchron halten müssen.

Für die Apps, die ich baue (Formulare, Listen, Dashboards, Verwaltungsmasken), ist das eine Anwendung zu viel. Meine Apps rendern HTML auf dem Server und schicken es an den Browser. Drei Werkzeuge machen das angenehm.

templ: typgeprüftes HTML

Mit templ schreibe ich HTML-Komponenten in Go-Dateien. Sie werden zu Go-Code kompiliert, also prüft sie der Compiler. Erwartet eine Komponente einen User und ich übergebe etwas anderes, schlägt der Build fehl. Benenne ich ein Feld um, bricht jedes Template, das es nutzt, beim Kompilieren, nicht vor einem Nutzer.

Die erzeugten Go-Dateien sind im Repository eingecheckt. So funktioniert go build auf jedem Rechner, ohne vorher einen Generator laufen zu lassen. Die templ-Version ist in go.mod fixiert und läuft immer als go tool templ, weil eine andere Version Code erzeugen kann, den die fixierte Bibliothek nicht versteht.

htmx: Interaktivität ohne App

htmx ergänzt ein paar HTML-Attribute, mit denen jedes Element eine Anfrage schicken und einen Teil der Seite durch das zurückkommende HTML ersetzen kann. Ein Knopf mit hx-post schickt ein Formular. hx-target sagt, wohin die Antwort kommt. Der Server liefert ein HTML-Fragment, kein JSON.

Das deckt das meiste ab, was eine Single-Page-App in einem Business-Tool tut: ein Formular ohne Neuladen absenden, eine Liste aktualisieren, Validierungsfehler neben dem Feld anzeigen, einen Dialog öffnen.

Ein paar Konventionen verhindern, dass es unübersichtlich wird:

  • Fehler haben einen festen Platz. Jedes Formular hat einen Platz für Fehlermeldungen. Schlägt die Validierung fehl, sagt der Server htmx, dass der Fehler dort hingehört, statt in die Liste, die das Formular aktualisieren sollte. Sonst sieht ein fehlgeschlagenes Speichern aus wie ein erfolgreiches, und der Dialog schliesst sich mit der verlorenen Eingabe. Ein Test prüft, dass jedes Formular, das scheitern kann, diesen Platz hat.
  • Wenn eine Änderung mehrere Stellen betrifft, neu laden. Ändert das Speichern eines Eintrags Zähler, Nummerierung oder eine Seitenleiste, antwortet der Server mit HX-Refresh: true. Fünf Fragmente einzeln zu flicken ist der Punkt, an dem htmx-Code fragil wird.
  • Toasts kommen ausserhalb der Reihe. Benachrichtigungen werden neben der eigentlichen Antwort in einen festen Container eingesetzt.

Eine Regel ist wichtiger, als sie aussieht: Eine Anfrage, die in curl 200 liefert, beweist nicht, dass eine htmx-Interaktion funktioniert. Das Attribut und sein Auslöser müssen auf demselben Element sitzen oder im Formular, das sie trägt. Ich teste Interaktionen in einem echten Browser.

Tailwind: Styles neben dem Markup

Tailwind v4 wird in CSS konfiguriert, ohne JavaScript-Konfigurationsdatei. Die Klassen stehen neben dem HTML, das sie gestalten, also bringt eine Komponente ihr Aussehen selbst mit. Für fertige Komponenten (Dialoge, Dropdowns, Tabellen) nutze ich templui, das templ-Komponenten mit Tailwind baut.

Das ist die einzige Stelle, an der mein Stack Node braucht: für das Tailwind-Kommandozeilenwerkzeug. Es läuft im Entwicklungscontainer, also installiere ich Node nie auf meinem Rechner.

Was es kostet

  • Offline-Apps und sehr reiche Oberflächen sind schwierig. Ein kollaborativer Editor oder ein Zeichenwerkzeug braucht echtes JavaScript. Meine Apps nicht.
  • Jede Interaktion ist ein Weg zum Server und zurück. Bei einer langsamen Verbindung merkt man das. In der Praxis ist ein serverseitig gerendertes Fragment von einem nahen Server für Business-Software schnell genug.
  • Weniger Leute kennen dieses Setup. Für React findet man leichter Leute als für htmx. Dafür gibt es weniger zu lernen, und eine Go-Entwicklerin versteht den ganzen Ablauf an einem Nachmittag.

Wann ich anders entscheiden würde

Für eine App, die im Browser lebt, mit Offline-Modus, Zusammenarbeit in Echtzeit oder komplexem Zustand im Client, würde ich ein richtiges Frontend bauen. Für alles andere weiss der Browser schon, wie man HTML anzeigt. Ich lasse ihn.

Als Nächstes: die Datenbank.

Mehr in Boring Stack

Teilen