Fresh 2.3: Zero JS by Default, View Transitions, and WebSocket Support

Fresh 2.3: Zero JS by Default, View Transitions, and WebSocket Support

lschvn

"Zero JavaScript by default" has been Fresh's pitch since day one. The honest fine print: even a page with no islands and no partials still shipped a small client entry, somewhere between 14 and 22 KB gzipped depending on the project. Not a scandal, but not zero either. Fresh 2.3, announced on the Deno blog, closes that gap for real.

Actually zero this time

There is nothing to configure. After upgrading, Fresh checks at render time whether a page actually uses islands or partials. If it uses neither, the response contains no script tags, no module preload headers, and no client bundle. A marketing page, a blog post, a docs page, plain HTML over the wire, the way it renders it.

If you want to see it yourself, upgrade and open the network tab on a static route. The difference is easy to measure: where 2.2 loaded the client entry on every page, 2.3 loads nothing until the first island shows up.

View Transitions with one attribute

Fresh already had client-side navigation through its partials system (f-client-nav). Version 2.3 wires the browser's View Transitions API into it. Opt in by adding one attribute next to the one you already have:

<body f-client-nav f-view-transition>

Partial navigations are now wrapped in document.startViewTransition(), and you style the animation in plain 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+, and Safari 18+ run the animations natively. Firefox doesn't yet, it simply falls back to a normal partial update, so there is no penalty for opting in early.

WebSockets without a sidecar

Until now, real-time features in a Fresh app meant either wiring Deno.upgradeWebSocket by hand or running a separate server. Fresh 2.3 makes WebSockets first-class. The quickest version lives on the App instance:

app.ws("/ws", {
  open(ctx) {
    console.log("client connected");
  },
  message(ctx, event) {
    ctx.send(`echo: ${event.data}`);
  },
  close(ctx) {
    console.log("client gone");
  },
});

File-based routes get the same capability through ctx.upgrade() inside a GET handler. And if you need to manage connections yourself, a chat room keeping a Set of sockets, say, calling ctx.upgrade() with no handler object returns the raw WebSocket so you own the lifecycle.

The rest of the release

Three smaller additions are worth knowing about:

  • CSP nonces. Fresh can now inject a per-request nonce into the scripts and styles it emits, which makes a strict Content-Security-Policy practical without hand-maintaining hashes.
  • Temporal in islands. All eight Temporal types can be passed as island props and serialize correctly across the server/client boundary, useful now that Temporal is stabilizing across runtimes.
  • Prerendering. Mark a route with prerender: true and Fresh renders it to static HTML at build time; dynamic routes can enumerate their paths. Combined with zero-JS pages, Fresh quietly becomes a capable static site generator.

Fresh 2.3 is a deno update away, and with Deno 2.7+ new projects scaffold via deno create. For a framework whose whole identity is restraint, this is the release where the restraint stops being approximate.

Frequently Asked Questions

Related articles

More coverage with overlapping topics and tags.

Deno Lands `deno desktop` Subcommand: WEF-Backed Self-Contained Desktop Apps with Deno.BrowserWindow, Unified DevTools, and Cross-Compile to macOS, Windows, and Linux
runtimes

Deno Lands `deno desktop` Subcommand: WEF-Backed Self-Contained Desktop Apps with Deno.BrowserWindow, Unified DevTools, and Cross-Compile to macOS, Windows, and Linux

Deno merged `deno desktop` on June 16, 2026 (PR #33441), a new subcommand that turns a Deno project into a self-contained desktop application. The feature ships the WEF backend (CEF by default, plus WebView and raw winit), the Deno.BrowserWindow API for window lifecycle and native events, framework auto-detection for Next, Astro, Fresh, Remix, Nuxt, SvelteKit, SolidStart, TanStack Start, and Vite SSR, a CDP multiplexer that exposes both V8 isolates in a single DevTools session, an auto-updater with bsdiff patches, and cross-compiled .app/.dmg/.exe/.AppImage outputs. Three smaller Deno PRs landed the same morning: `deno link`/`unlink`, `deno test --shard`, and a fetch `request_builder_hook` for `x-deno-fetch-token`/`cdn-loop` headers.
Nitro v3.0.260603-beta: Custom Framework Commands and Default Preset Config
runtimes

Nitro v3.0.260603-beta: Custom Framework Commands and Default Preset Config

Nitro's latest beta adds support for custom framework preview and deploy commands, introduces a defaultPreset config option for customizing the fallback preset, and fixes a type-stripping edge case.
Nuxt 4.4 Ships Vue Router v5, Typed Layout Props, and 28x Faster Dev Routing
css

Nuxt 4.4 Ships Vue Router v5, Typed Layout Props, and 28x Faster Dev Routing

Nuxt's latest point release brings major under-the-hood improvements: Vue Router v5, a custom useFetch factory API, an accessibility announcer composable, and a routing system migration that makes hot-module replacement up to 28x faster.

Comments

Log in Log in to join the conversation.

No comments yet. Be the first to share your thoughts.