Über unsMediaKontaktImpressum
Thomas Surmann 09. Januar 2024

SSR in Spring Boot: Stand der Technik

SSR in 2024 – alles wie früher? Ist HTMX ein Ersatz für SPAs?

In den letzten Jahren gab es einen stetigen Trend, Webseiten als Single Page Application (SPA) zu entwickeln – mit Angular, React und Vue als deren populärsten Vertretern. Die damit verbundene Komplexität macht aber nicht in jedem Kontext Sinn. So haben viele Web-Anwendungen nur begrenzte dynamische Elemente. Hier ist das gute alte serverseitige Rendering (SSR) immer noch eine tragfähige Alternative – mit HTMX als sinnvoller Ergänzung.

Die folgenden Absätze geben einen Überblick über den Stand der Technik für SSR in Spring Boot. Welchen Mehrwert bieten die vorgestellten Trends und Technologien im Projekt?

Spring MVC mit Thymeleaf

Spring bzw. Spring Boot ist nach wie vor das verbreitetste Framework für die JVM. Während für eine SPA vor allem REST-Schnittstellen relevant sind, kommt für serverseitiges Rendering Spring MVC mit einer Template-Engine zum Einsatz. Hier ist Thymeleaf mit großem Abstand die erste Wahl.

Ein Thymeleaf-Template ist dabei selbst valides HTML und könnte theoretisch direkt im Browser geöffnet werden. Die dynamischen Elemente werden mit zusätzlichen Attributen in einem getrennten Namespace realisiert.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Homepage</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <link th:href="@{/webjars/bootstrap/5.3.2/css/bootstrap.min.css}" rel="stylesheet" />
        <script th:src="@{/webjars/bootstrap/5.3.2/js/bootstrap.bundle.min.js}" defer></script>
        <script th:src="@{/webjars/htmx.org/1.9.6/dist/htmx.min.js}" defer></script>
    </head>
    <body>
        <main class="my-5">
            <div class="container">
                <h1 class="mb-4" th:text="#{home.index.headline}" />
                <p class="mb-5 js-greeting" th:text="${greeting}" />
            </div>
        </main>
    </body>
</html>

HTML-Seite mit ein bisschen Thymeleaf-Magie

Alle Attribute im entsprechenden Namespace (th:) werden von Thymeleaf verarbeitet. Mit @{...} wird ein Link erzeugt und das Ergebnis mittels th:href in das href-Attribut geschrieben. Das Attribut th:text schreibt das Ergebnis in das Innere des Elements – dabei wird #{home.index.headline} aus den Properties-Dateien gelesen und ${greeting} wird als Eintrag im Model erwartet.

@Controller
public class HomeController {

    @GetMapping("/")
    public String index(final Model model) {
        model.addAttribute("greeting", "Hello World!");
        return "home/index";
    }
}

Einfacher Controller, der unser Template lädt und die Begrüßung bereitstellt

Je näher man mit dem Code am Standard bleibt, desto einfacher gestaltet sich langfristig die Wartbarkeit. Alles Weitere passiert dann – wie auch beim SPA-Ansatz – in den Services, die vom Controller aus aufgerufen werden. Die Businesslogik bleibt also innerhalb der Server-Anwendung, wo sie auch hingehört.

Thymeleaf hat schon weit über 10 Jahre auf dem Buckel und damit auch eine immense Auswahl möglicher Ausdrücke. Die zentralen Elemente haben sich aber kaum geändert und bieten zusammen mit Spring alles, was eine moderne Anwendung braucht: Internationalisierung, Formular-Binding und -Validierung, Formatierung und so weiter.

Nebeneffekt-frei mit Tailwind CSS

Das obige HTML-Snippet hatte ein paar Klassen von Bootstrap, dem Klassiker unter den CSS-Frameworks. Tailwind CSS ist erst wenige Jahre alt, erfreut sich aber ebenfalls großer Beliebtheit. Anstatt mit dedizierten CSS-Klassen zu arbeiten, werden alle Formatierungen als spezielle Tailwind-Klassen direkt in HTML hinterlegt. Aus dem Namen lässt sich die Funktion dabei meistens direkt ableiten.

<main class="my-12">
    <div class="container px-4 md:px-6">
        <h1 class="grow text-3xl md:text-4xl font-medium mb-8" th:text="#{home.index.headline}" />
        <p class="mb-12 js-greeting" th:text="${greeting}" />
    </div>
</main>

Gestaltung von HTML mit Tailwind CSS

Die Klasse text-3xl übersetzt beispielsweise zu einer Schriftgröße von 30 Pixeln. Dem Mobile-First-Ansatz folgend wird mit dem Präfix md: das Attribut für alle Bildschirmgrößen ab 768 Pixel überschrieben und auf 36 Pixel geändert.

Der Vorteil von diesem Ansatz ist, dass sich Nebeneffekte weitestgehend ausschließen lassen – wenn an einem HTML-Element die Klassen angepasst werden, muss nicht gleichzeitig ein Dutzend weiterer Seiten überprüft werden. Entwickler müssen sich weniger abstimmen und es sammelt sich über einen längeren Zeitraum auch kein CSS-Code, der gar nicht mehr im Einsatz ist. Da es für wiederkehrende Elemente in der Regel ohnehin eigene Thymeleaf-Templates gibt (z. B. includes/button.html), gibt es am Ende auch gar nicht so viele Duplikate.

Interessanterweise besteht bei Tailwind CSS auch die Möglichkeit, mehrere Styles wieder in selbstdefinierten Gruppen zusammenzufassen – was im Einzelfall sinnvoll sein kann, aber dem eigentlichen Entwicklungsansatz entgegensteht.

Tailwind CSS ist schon in großen Projekten produktiv im Einsatz und überzeugt durch die gewonnene Modularisierung. Bootstrap kommt in der aktuellen Version ohne JQuery aus und kann daher ebenfalls voll empfohlen werden.

Weniger JavaScript dank HTMX

Auch HTMX ist erst wenige Jahre alt, aber stark im Trend für den Einsatz in "Nicht-SPA"-Anwendungen. HTMX erlaubt das Konfigurieren von typischerweise benötigten Befehlen und Logik als Attribute direkt im HTML-Code.

<button th:hx-get="@{/newGreeting}"
        hx-trigger="click"
        hx-target=".js-greeting"
        hx-swap="outerHTML">Change greeting</button>

Erweiterung um einen Button mit HTMX Logik

Mit wenigen Attributen haben wir hier ein Verhalten definiert: Wenn der Benutzer den Button klickt, wird der newGreeting-Endpunkt via AJAX aufgerufen und mit dem Ergebnis unsere bisherige Begrüßung im <p>-Element ersetzt.

Die Attribute von HTMX erlauben nun das Verwenden von "echtem" Hypermedia – wenn ein Button beispielsweise ein Element löschen soll, so kann eine DELETE- anstatt einer POST-Anfrage abgeschickt werden. Auf Spring-Boot-Seite kann man mit @DeleteMapping dann genau diese HTTP-Methode erwarten.

Außerdem gibt es mit der sogenannten "Boost-Option" die Möglichkeit, alle regulären Links und Formulare von HTMX handhaben zu lassen. Wenn hx-boost="true" direkt am body hinterlegt ist, wird die Seite nur noch beim ersten Aufruf regulär geladen. Im Anschluss werden alle Formulare und internen Links automatisch per AJAX-Request abgewickelt; inklusive der Aktualisierung der URL-History. Damit fühlt sich die Seite schon viel mehr wie eine SPA an.

Bei eigenem JavaScript-Code sollte darauf geachtet werden, dass dieser auf die Aktualisierungen der HTML-Struktur immer reagiert. Ein einfaches on('click', ...) genügt ggf. nicht, stattdessen müssen alle click-Events auf dem document überprüft werden.

function handleEvent(eventType, selector, handler) {
    document.addEventListener(eventType, function(event) {
        if (event.target.matches(selector + ', ' + selector + ' *')) {
            handler.apply(event.target.closest(selector), arguments);
        }
    });
}

handleEvent('click', '.js-myclass', function(event) {
    ...
});

Flexible Methode zum Abfangen aller click-Events

Neben der Vermeidung von selbst zu schreibendem JavaScript-Code bietet HTMX den Vorteil der Lokalisierung des Verhaltens (Locality of Behaviour). Die Funktionsweise eines Teiles der Seite ist komplett oder zum großen Teil direkt in HTML enthalten. Damit kann ein Entwickler den Code schneller überblicken und anpassen.

Auf Build-Tools verzichten?

Die aktuellen Trends folgen dem Wunsch, die Komplexität von Web-Anwendungen wieder zurückzufahren. Daher gibt es auch die Idee, auf ein klassisches Build-Tool wie z. B. Webpack zu verzichten.

In der heutigen Zeit steht normalerweise ausreichend Bandbreite zur Verfügung, so dass die Minifizierung des JavaScript-Codes wenig Vorteile bringt oder diese sowieso direkt vom CDN erledigt wird. Wenn HTMX im Einsatz ist, bleibt vielleicht gar nicht mehr viel eigenes JavaScript übrig. Auf ein Werkzeug wie Webpack zu verzichten, vermeidet auf jeden Fall sehr viel Overhead: package.json, webpack.config.js, Entwickler-Onboarding, Erweiterung der Maven- oder Gradle-Dateien und der damit verbundene Pflegeaufwand sind alle nicht mehr notwendig.

Andererseits gibt es auch weiterhin gute Gründe für den Einsatz von Webpack: SEO-Optimierung, Modularisierung des JavaScript-Codes, Verwendung von TypeScript, Verschleierung (obfuscation) des Codes, Einsatz des DevServers und vieles mehr. Für Tailwind CSS ist ein Build-Tool nicht zwingend notwendig, wird allerdings dringend empfohlen: Die Anzahl aller möglichen CSS-Klassen ist einfach zu groß, so dass besser ein minimales CSS mit nur den Klassen erzeugt wird, die tatsächlich im HTML-Code gefunden werden.

Best Practices für Thymeleaf und HTMX

Wenn die vorgestellten Technologien im aktuellen Projekt zum Einsatz kommen sollen, lohnt es sich, einer Reihe von Best Practices zu folgen.

  • Arbeiten mit Layout und Fragmenten: Da sich viele Teile der HTML-Struktur wiederholen, sollte der Layoutdialekt von Thymeleaf eingebunden werden. Fragmente sind einzelne HTML-Snippets, die in einer übergeordneten Seite eingebunden werden können. Beim Einsatz von HTMX bietet es sich an, dieselben Fragmente sowohl bei den Endpunkten als auch in den Templates zu referenzieren.
  • Einheitliche Ordnerstruktur: Die Thymeleaf-Templates sollten immer im gleichen Ordner zu finden sein. Bei unserer index-Methode vom HomeController entspricht dies z. B. src/main/resources/home/index.html.

Hot Reload in Thymeleaf

Thymeleaf-Templates liegen auf dem Server und müssen daher auch während der Entwicklung kompiliert werden, um in der aktuellen Version angezeigt zu werden. Um diesen Schritt zu beschleunigen, können die Templates stattdessen mit dem FileTemplateResolver direkt vom Dateisystem gelesen werden.

@Configuration
@Profile("local")
public class LocalDevConfig {

    public LocalDevConfig(final TemplateEngine templateEngine) throws IOException {
        File sourceRoot = new ClassPathResource("application.yml").getFile().getParentFile();
        while (sourceRoot.listFiles((dir, name) -> name.equals("mvnw")).length != 1) {
            sourceRoot = sourceRoot.getParentFile();
        }
        final FileTemplateResolver fileTemplateResolver = new FileTemplateResolver();
        fileTemplateResolver.setPrefix(sourceRoot.getPath() + "/src/main/resources/templates/");
        fileTemplateResolver.setSuffix(".html");
        fileTemplateResolver.setCacheable(false);
        fileTemplateResolver.setCharacterEncoding("UTF-8");
        fileTemplateResolver.setCheckExistence(true);
        templateEngine.setTemplateResolver(fileTemplateResolver);
    }

}

Fehlerhandhabung von HTMX

Server-Fehler nach HTMX-Anfragen sollten auch abgefangen und behandelt werden. Dazu können beispielsweise die folgenden Anpassungen zum Code hinzugefügt werden.

<body hx-boost="true" hx-headers="{&quot;Accept&quot;: &quot;text/html&quot;}">
...
</body>

Alle AJAX-Requests von HTMX haben nun automatisch das neue hx-headers-Attribut, damit die Fehlerseiten von Spring Boot als HTML-Seite ausgegeben werden.

document.body.addEventListener('htmx:beforeSwap', function(evt) {
    evt.detail.shouldSwap = true;
})

Mit dieser Änderung wird HTMX den Austausch des HTML-Codes auch im Fehlerfall durchführen.

Fazit

Selbstverständlich haben SPAs weiterhin ihren Platz für client-lastige Applikationen. Mit einer Anwendung auf Basis von Spring Boot, Thymeleaf und HTMX steht alternativ ein moderner und schlanker Tech-Stack zur Verfügung, der von einzelnen Fullstack-Entwicklern gut beherrschbar ist. Die Funktionalität verbleibt hauptsächlich auf dem Server, wo das Ökosystem von Spring Boot mit seinen zahlreichen Helfern zur Verfügung steht. An den Browser wird dabei klassisches HTML ausgespielt, das mittels Thymeleaf mit Server-Daten angereichert ist. Mit der Bibliothek HTMX kann Client-Verhalten direkt als HTML-Attribut definiert werden und damit die Logik weiter zentralisieren.

Autor
Thomas Surmann

Thomas Surmann

Thomas Surmann arbeitet seit über 15 Jahren mit Java und Spring Boot und war dabei als Freiberufler in zahlreichen Rollen und Projekten tätig.
>> Weiterlesen
Das könnte Sie auch interessieren
Kommentare (0)

Neuen Kommentar schreiben