SSR für Blogs: Warum HTML wieder wichtig wird
Ein Blog ist nicht zuerst eine Anwendung. Ein Blog ist zuerst ein Dokument im Web. Das ist für mich keine nostalgische Aussage, sondern eine architektonische. Wenn ich das ernst nehme, dann wird SSR plötzlich nicht zu einem Spezialtrick, sondern zur naheliegenden Form.
Viele moderne Sites liefern am Anfang nur eine Hülle aus. Ein Container, ein Script-Tag, ein paar Assets. Der eigentliche Inhalt entsteht erst später im Browser. Das mag für hochinteraktive Oberflächen in manchen Fällen sinnvoll sein. Für einen Blog ist es oft die falsche Priorität.
Ein Blogartikel soll lesbar sein, auch bevor der Client hochgefahren ist. Suchmaschinen sollen ihn erfassen können. Crawler sollen ihn verstehen. Andere Systeme sollen ihn extrahieren können. Und ja: Auch KI-Systeme lesen am Ende zuerst HTML, nicht die interne Hoffnung, dass sich nach 2,3 Sekunden Hydration schon alles korrekt zusammensetzen wird.
Deshalb ist die SSR-Strecke in diesem Projekt keine Nebensache. Sie ist bewusst modelliert.
class RequestTimeSsrRenderer(staticPath: Path, timeoutMillis: Long) {
private val ssrDir = staticPath.getParent.resolve("ssr")
private val scriptPath = ssrDir.resolve("ssr-server.mjs").toAbsolutePath.normalize()
private val port = Option(System.getenv("SSR_PORT")).getOrElse("8081")
private val serverUrl = s"http://127.0.0.1:$port"Allein das gefällt mir schon besser als viele schwerere Lösungen. SSR ist hier kein diffuses Cloud-Feature, sondern eine klar benannte Laufzeitkomponente. Sie hat ein Verzeichnis, ein Script, einen Port und eine Ansprechstelle.
Beim Rendern wird dann bewusst ein HTML-Dokument angefordert:
val jsonPayload =
s"""{
| "staticPath": "${escapeJson(staticPath.toAbsolutePath.normalize().toString)}",
| "routePath": "${escapeJson(path)}",
| "origin": "${escapeJson(origin)}",
| "cookie": "${escapeJson(cookie.getOrElse(""))}"
|}""".stripMarginDas Entscheidende daran ist nicht JSON als Transport. Das Entscheidende ist, dass die Seite serverseitig als konkrete Route mit konkretem Kontext gerendert wird. Die Ausgabe ist nicht ein generisches Shell-Dokument, sondern eine Antwort, die bereits weiß, welchen Inhalt sie darstellen soll.
Ich finde diese Ehrlichkeit wichtig. Das Web versteht HTML. Wenn ich Inhalte veröffentliche, sollte ich das Basismedium des Webs nicht behandeln, als wäre es nur ein Zwischenformat.
Gleichzeitig geht es mir nicht darum, JavaScript abzulehnen. Interaktivität bleibt wertvoll. Zustandsreiche Oberflächen brauchen Client-Code. Auch in diesem Projekt gibt es das. Aber der Kern eines Blogartikels ist nicht Interaktivität. Der Kern ist Lesbarkeit.
Das Packaging im Build zeigt genau diese Priorität. Neben dem statischen Frontend werden auch die SSR-Artefakte explizit ausgeliefert:
Seq("render-route.mjs", "ssr-server.mjs").foreach { file =>
val source =
baseDirectory.value / "src" / "main" / "javascript" / "ssr" / file
if (source.exists()) {
IO.copyFile(source, ssrDir / file, preserveLastModified = true)
}
}
IO.copyFile(
frontendServerBundle,
ssrDir / "main.js",
preserveLastModified = true
)Das ist ein architektonisch sauberer Punkt: SSR ist kein nachträglicher Hack, sondern Teil des Systems. Genau deshalb wird es auch im Build und Packaging ernst genommen.
Warum ist HTML wieder wichtig? Eigentlich war es nie unwichtig. Wir haben nur eine Weile so getan, als wäre es hinnehmbar, Inhalte erst nachträglich in eine leere Oberfläche hineinzuberechnen. Für manche Produkte ist das okay. Für einen Blog halte ich es für eine Schwächung des Mediums.
Ich will, dass ein Artikel bereits als Artikel existiert, wenn er ausgeliefert wird. Nicht als Ladeversprechen. Nicht als zukünftige Komponente. Nicht als „wird schon noch hydriert“. Ein Blog darf gern modern aussehen. Aber er sollte in seinem Kern wieder Dokument sein.
SSR ist für mich deshalb kein Rückschritt. Es ist eine Korrektur. Eine Erinnerung daran, dass das Web mit Dokumenten begonnen hat und dass viele Systeme besser werden, wenn sie diesen Ursprung nicht verachten.
Noch keine Kommentare.