Single-Page Applications, State Management, Hydration, Client-Side Routing – moderne Webanwendungen strotzen vor Tools, Frameworks und Komplexität. Dabei war die ursprüngliche Idee des Webs eigentlich relativ einfach. In diesem Artikel beleuchten wir die Ursprünge von Hypermedia und wie Hypermedia-Driven Applications uns heute helfen können, wieder Spaß an der Webentwicklung zu finden – ohne die Komplexität typischer Single-Page-Anwendungen.

Das Komplexitätsproblem

Eine typische Webanwendung besteht heutzutage meist aus einer Server-Komponente, die eine JSON-API bereitstellt.
Diese APIs werden dann von einer Single-Page-Anwendung als Frontend konsumiert.
Während die Auswahl auf Serverseite noch relativ überschaubar ist, sind die Möglichkeiten im Frontend nahezu unerschöpflich und werden jeden Tag mehr).

Das führt oft zu:

  • hoher Komplexität: Zwei Stacks müssen beherrscht werden
  • hohen Einstiegshürden: Es ist nicht einfach, schnell „produktiv“ zu sein
  • hohem Wartungsaufwand: Sicherheitsupdates, Dependency-Updates, Build-Pipelines, API Churn
  • zusätzlichen Fehlerquellen: API Contracts, Client-Side State, Routing

Häufig wird die Entscheidung, eine SPA zu verwenden, auch als gegeben hingenommen, weil „es halt jede:r macht“. Thoughtworks hat diesem Phänomen eine eigene Kategorie im Tech Radar gewidmet): „SPA by default“.

Thoughtworks argumentiert, dass die Entscheidung, eine SPA zu verwenden, oft nicht hinterfragt wird und die Anforderungen an die Software die Komplexität, die damit einhergeht, häufig nicht rechtfertigen, sondern eine serverseitig gerenderte Website oft gut genug ist.

Die Erwartungen von Benutzerinnen sind seit dem Aufkommen von Webseiten natürlich gestiegen, und es wird ein Mindestmaß an Komfort und Interaktivität erwartet. Nur statische HTML-Seiten sind oft nicht genug. Anwendungen wie Miro oder Figma sind ohne Single-Page-Anwendungen und viel clientseitiger Logik undenkbar.

Zwei Extrema, dargestellt mit einem horizontalen grünen Balken. Links “statisches html”, rechts Figma, Miro. Auf dem grünen Balken ist ein blauer Balken, von links bis ca. 1/4 des grünen Balkens. Beschriftet mit Endless Scrolling
Zwei Extrema, dargestellt mit einem horizontalen grünen Balken. Links “statisches html”, rechts Figma, Miro. Auf dem grünen Balken ist ein blauer Balken, von links bis ca. 1/4 des grünen Balkens. Beschriftet mit Endless Scrolling

Was ist hypermedia?

Um zu verstehen, was eine „Hypermedia-driven Application“ ist, muss man ein wenig in der Geschichte zurückreisen.
Hypermedia oder Hypertext wurde von Ted Nelson in den 1960er Jahren geprägt, auch wenn die erste Erwähnung von Vannevar Bush bereits 1945 in seinem Artikel „As We May Think“ stattfand.

Hypermedia beschreibt eine (damlas) neue Art von Medien (in der Regel Texte), die sich durch ein nicht-lineares Modell auszeichnen. Im Gegensatz dazu stehen z.B. klassische Medien wie Filme (oder auch Bücher), die nicht mitten im Film zu einer Dokumentation über die Schauspielerinnen wechseln können, sofern der Zuschauer das möchte.

Mit Hilfe von Hypermedia und Hypertext sollten die Informationen der Welt zugänglicher gemacht werden und die Verbindungen zwischen Themen deutlicher werden.
Nelsons Vision von Hypermedia war nicht nur auf Texte und Verbindungen zwischen Texten beschränkt, sondern beinhaltete auch Ton, Video, Bilder und andere Informationsquellen.
Die Ideen von Nelson und anderen nahmen viele Konzepte vorweg, die wir heute für selbstverständlich erachten.

Basierend auf Nelsons Ideen von Hypermedia und Hypertext entwickelte Tim Berners-Lee das World Wide Web.
Das Internet der frühen 90er Jahre basierte nahezu vollständig auf Texten und Verlinkungen, sodass Nutzerinnen die bereitgestellten Informationen in beliebiger Reihenfolge lesen konnten.

Trotz dieser einfachen Grundlagen entwickelte sich das Web rasant weiter. Das beste Beispiel für Hypermedia ist wahrscheinlich Wikipedia.

Aber was macht eine Hypermedia-Driven Anwendung nun aus?

Eine Hypermedia-Driven Anwendung verwendet (ausschließlich) Hypermedia, um mit dem Server zu kommunizieren.

HTML als hypermedia

Obwohl HTML sehr erfolgreich das Grundgerüst des modernen Internets gebildet hat, hat es sich als Hypertext/Hypermedia kaum weiterentwickelt, nachdem Forms zur Manipulation von Daten eingeführt wurden.

Die Möglichkeiten sind in der Tat sehr eingeschränkt: Nur <a>-Elemente (Links) und <form>-Elemente dürfen HTTP-Anfragen erzeugen. click und submit sind die einzigen Ereignisse, die eine Serveranfrage auslösen dürfen. Es sind lediglich GET oder POST erlaubt, und es wird immer die gesamte Seite mit der Antwort des Servers ersetzt.

Hier kommt HTMX ins Spiel, um HTML als Hypertext zu erweitern, indem die genannten Limitierungen entfernt werden.

HTMX - HTML Extended

HTMX ist eine JavaScript-Bibliothek, die es ermöglicht, Funktionen wie AJAX, WebSockets und Server-Sent Events deklarativ über Custom Attributes direkt im HTML zu nutzen.
Die Verarbeitung der Serverantwort muss dabei nicht mehr die gesamte Seite ersetzen.

HTMX basiert auf einer überschaubaren Menge von Attributen, die mit dem Präfix hx- gekennzeichnet werden.

CODE
<p>
<dl>
  <dt>hx-get|post|put|delete</dt>
  <dd>Welches HTTP-Verb soll verwendet werden und welche URL soll angesprochen werden?</dd>
<dt>hx-target</dt>
<dd>Welches Element auf der Seite soll mit der Antwort des Servers manipuliert bzw. ersetzt werden?</dd>

<dt>hx-trigger</dt>
<dd>Welche Nutzerinteraktion löst die Anfrage aus?</dd>
<dt>hx-swap</dt>
<dd>Wie soll das alte Element durch die Antwort ersetzt werden?</dd>
</dl>
</p>

Mit Hilfe dieser grundlegenden Attribute lassen sich bereits sehr dynamische Anwendungen bauen. Die Lernkurve ist hierbei relativ flach, da die Anzahl der zentralen Attribute so gering ist.

Folgendes Beispiel löst beim Klicken des Buttons einen POST-Request an /clicked aus. Das Resultat (partial HTML) ersetzt das komplette Element mit der ID parent-div.
Die sogenannte swap-Strategie outerHTML beschreibt hierbei das Verhalten, dass nach der Ersetzung das Element mit der ID parent-div nicht mehr Teil des DOMs ist (das Gegenteil wäre innerHTML).

CODE
<button
    hx-post="/clicked"
    hx-trigger="click"
    hx-target="#parent-div"
    hx-swap="outerHTML">
    Click Me!
</button>

JTE - eine moderne Template Engine

Als Java-Entwicklerinnen sind wir es gewohnt, mit Typsicherheit zu arbeiten.
Es gibt viele tolle Template-Engines in Java, um HTML zu erzeugen.
Die meisten sind jedoch nicht typsicher, was eine häufige Fehlerquelle ist. Insbesondere wenn man Klassen ändert oder umbenennt, zieht das viel Arbeit in den Templates nach sich.
JTE ist eine junge Template-Engine, die sehr schnell, einfach und leichtgewichtig ist und ein großes Maß an Typsicherheit bietet.

CODE
@import org.example.Page
@param Page page
<head>
    <title>${page.getTitle()}</title>
</head>
<body>
    <h1>${page.getTitle()}</h1>
</body>

Es ist aber nicht notwendig, JTE zu benutzen, um eine Hypermedia-Anwendung zu bauen. Es kann nahezu jede Template-Engine verwendet werden. Dieser Ansatz wird auch HOWL-Stack genannt („Hypermedia on whatever you like“).

Im Folgenden werden einige typische Pattern mit HTMX und Hypermedia statt JavaScript/JSON umgesetzt.

HTMX Pattern

In folgendem werden wir einige typische Pattern und Funktionen mit Hilfe von HTMX als Hypermedia Anwendung bauen.

Form Validation

Ein Web Formular
Ein Web Formular

Ein typisches Web Formular

Inline-/Echtzeit-Formularvalidierung kann mit HTMX ausschließlich mit Hypermedia umgesetzt werden. Ein großer Vorteil ist, dass die Regeln zur Prüfung nur auf Serverseite implementiert werden und nicht die Gefahr besteht, dass Client- und Serverregelwerk auseinanderlaufen.

In diesem Beispiel ist das Attribut hx-include wichtig: Da der Server das gesamte Formular validiert, muss immer das komplette Formular gesendet werden. Ohne das zusätzliche hx-include würde nur das aktuelle Feld gesendet und die Validierung würde scheitern.
Die hx-swap-Strategie morph sorgt dafür, dass das Formular nicht einfach ausgetauscht wird, sondern mit Hilfe der Bibliothek morphdom der aktuelle Zustand in den neuen überführt wird. Dadurch wird z.B. die aktuelle Position des Cursors beibehalten.

Die Auswahl des passenden Handlers wird durch spezielle Header geregelt.
Die Annotation @HxRequest ist Teil des HTMX Starters. Die Verwendung ist optional; es kann ebenso mit Bordmitteln der Header HX-Request geprüft werden.

CODE
<form action="/" method="post" id="hx-form">
    <fieldset>
        <label>Firstname
        <input
                type="text"
                hx-trigger="keyup changed delay:250ms"
                hx-post="/"
                hx-include="closest form"
                hx-target="#hx-form"
                hx-swap="morph">
        </label>
    <fieldset>
    <button type="submit" value="Submit">Submit</button>
</form>
CODE
@HxRequest
@PostMapping("")
public String htmxPost(@Valid FormModel formModel, BindingResult bindingResult, Model model){
    model.addAttribute("formModel", formModel);
    model.addAttribute("validationResult", bindingResult);
    return "form";
}

@PostMapping("")
public String postIndex(@Valid FormModel formModel, BindingResult bindingResult, Model model){
    // usual form handling with redirect
}

Das selbe Formular mit markierten Validierungsfehlern

Endless Scrooling

Für Feeds oder Timelines wird oft unendliches Scrolling verwendet. Auch dies kann mit Hilfe von HTMX mit einer Handvoll Attributen deklarativ umgesetzt werden.
Hier wird der Request nicht durch einen Klick, sondern dadurch ausgelöst, dass das letzte Element durch den Viewport scrollt – und zwar genau nur einmal.
Die nächste „Seite“ wird dann ans Ende der Liste angehängt. Die neuen Elemente haben wiederum den gleichen Trigger, nur mit anderer Seitenzahl im letzten Element.

CODE
@for(var personEntry : ForSupport.of(persons.getContent()))
    @if (personEntry.isLast())
        <blockquote
        hx-get="/endless-scrolling?page=${persons.getNumber() + 1}&size=${persons.getSize()}&sort=name,desc"
        hx-trigger="intersect once"
        hx-swap="afterend"
        >${personEntry.get().getQuote()}</blockquote>
    @else
        <blockquote>${personEntry.get().getQuote()}</blockquote>
    @endif
@endfor

Modale Dialoge

Modale Dialoge sind oft dynamisch (z.B. zur Bestätigung einer destruktiven Aktion) und benötigen Inhalte vom Server, die auf der aktuellen Seite noch nicht vorhanden sind.
Das Erstellen aller potenziellen modalen Dialoge im Voraus würde den Payload unnötig aufblähen.
Mit HTMX kann der Inhalt eines modalen Dialogs erst geladen werden, wenn dieser tatsächlich benötigt wird. Mit Alpine.js kann die clientseitige Interaktion mit dem modalen Dialog zusätzlich vereinfacht werden.

CODE
<button  hx-get="/modal-dialogs"
         hx-target="#modals-here"
         hx-trigger="click"
         class="btn primary">Open Modal</button>
<div id="modals-here"></div>

Weitere Funktionen

Dieser Artikel kann nur einen kleinen Überblick geben. Mit HTMX lassen sich noch viele weitere Pattern umsetzen, z.B.:

  • clientseitige Event-Trigger
  • Transitions und Animationen
  • Server-Sent Events und WebSockets

Weitere Beispiele sind im Beispiel-Repository zu finden.

Fazit

Mit dem gezeigten Konzept einer Hypermedia-driven Application kann viel Komplexität entfernt werden.
Das Erlernen von HTML/HTTP und frameworkunabhängigen Techniken zahlt sich langfristig mehr aus, als ein spezielles JavaScript-Framework zu lernen.
Änderungen am Frontend sind sehr viel schneller möglich, da nicht erst der API-Vertrag angepasst werden muss usw.

Um zur Ausgangsfrage zurückzukommen: Durch HTMX hat sich die Grenze, ab der es unbedingt eine SPA braucht, ein gutes Stück verschoben.

Sollte man jetzt immer Hypermedia (und HTMX) verwenden, um eine Webanwendung zu bauen?
Wie so oft ist die Antwort natürlich: „It depends.“

Wenn man eine typische Businessanwendung mit Formularen und vielen CRUD-Oberflächen baut, ist Hypermedia eine gute Wahl – ebenso bei einer E‑Commerce-Anwendung.
Generell kann man sagen, dass eine UI, die wohldefinierte Updates benötigt, ein guter Anwendungsfall ist.
Im Gegensatz dazu ist eine Anwendung, die sehr viele Updates benötigt und viele dynamische UI-Elemente hat (z.B. Miro), eher schlecht für einen Hypermedia-getriebenen Ansatz geeignet.

Mit Hilfe des „Dynamic Island“-Patterns können aber beide Welten kombiniert werden, sodass SPAs bzw. clientseitige Logik nur gezielt und wo nötig eingesetzt werden.