„Zero JavaScript by default" ist seit dem ersten Tag das Versprechen von Fresh. Das ehrliche Kleingedruckte: Selbst eine Seite ohne Islands und ohne Partials lieferte einen kleinen Client-Entry aus, je nach Projekt zwischen 14 und 22 KB gzipped. Kein Skandal, aber eben auch nicht null. Fresh 2.3, angekündigt im Deno-Blog, schließt diese Lücke endgültig.
Diesmal wirklich null
Es gibt nichts zu konfigurieren. Nach dem Upgrade prüft Fresh beim Rendern, ob eine Seite tatsächlich Islands oder Partials verwendet. Nutzt sie keines von beidem, enthält die Antwort keine Script-Tags, keine Module-Preload-Header und kein Client-Bundle. Eine Marketing-Seite, ein Blogpost, eine Docs-Seite: reines HTML über die Leitung.
Wer es selbst sehen will: Upgrade einspielen und auf einer statischen Route den Netzwerk-Tab öffnen. Der Unterschied lässt sich leicht messen, wo 2.2 auf jeder Seite den Client-Entry lud, lädt 2.3 nichts, bis die erste Island auftaucht.
View Transitions mit einem Attribut
Client-seitige Navigation gab es in Fresh schon über das Partials-System (f-client-nav). Version 2.3 verdrahtet damit die View-Transitions-API des Browsers. Aktiviert wird per Attribut, direkt neben dem, das man ohnehin schon hat:
<body f-client-nav f-view-transition>
Partielle Navigationen werden jetzt in document.startViewTransition() verpackt, die Animation steuert man in purem CSS:
::view-transition-old(root) {
animation: fade-out 150ms ease-out;
}
::view-transition-new(root) {
animation: fade-in 150ms ease-in;
}
Chrome 111+, Edge 111+ und Safari 18+ animieren nativ. Firefox noch nicht, dort fällt Fresh einfach auf ein normales Partial-Update zurück. Früh einzusteigen kostet also nichts.
WebSockets ohne Beiboot-Server
Bisher bedeutete Echtzeit in einer Fresh-App entweder handverdrahtetes Deno.upgradeWebSocket oder einen separaten Server. Fresh 2.3 macht WebSockets zum festen Bestandteil. Die schnellste Variante lebt auf der App-Instanz:
app.ws("/ws", {
open(ctx) {
console.log("Client verbunden");
},
message(ctx, event) {
ctx.send(`Echo: ${event.data}`);
},
close(ctx) {
console.log("Client weg");
},
});
Dateibasierte Routen bekommen dieselbe Fähigkeit über ctx.upgrade() in einem GET-Handler. Und wer Verbindungen selbst verwalten will, etwa ein Chatraum, der ein Set von Sockets hält, ruft ctx.upgrade() ohne Handler-Objekt auf und bekommt das rohe WebSocket-Objekt samt Lebenszyklus in die eigene Hand.
Der Rest des Releases
Drei kleinere Neuerungen sind wissenswert:
- CSP-Nonces. Fresh kann jetzt pro Request einen Nonce in die ausgelieferten Scripts und Styles injizieren, damit wird eine strikte
Content-Security-Policypraktikabel, ohne Hashes von Hand zu pflegen. - Temporal in Islands. Alle acht Temporal-Typen lassen sich als Island-Props übergeben und korrekt über die Server/Client-Grenze serialisieren, nützlich, jetzt wo Temporal sich über die Runtimes hinweg stabilisiert.
- Prerendering. Eine Route mit
prerender: truemarkieren, und Fresh rendert sie beim Build zu statischem HTML; dynamische Routen können ihre Pfade aufzählen. Zusammen mit Zero-JS-Seiten wird Fresh nebenbei ein fähiger Static-Site-Generator.
Fresh 2.3 ist ein deno update entfernt, neue Projekte entstehen mit Deno 2.7+ über deno create. Für ein Framework, dessen ganze Identität Zurückhaltung ist, ist das der Release, in dem die Zurückhaltung aufhört, ungefähr zu sein.



