JavaScriptWeb Development

Deno Fresh Tutorial: Build Web Apps with Zero JavaScript

Your blog loads 500 KB of JavaScript just to render static text. Your landing page ships a React bundle before users can read the headline. Modern web frameworks have normalized this waste. Fresh, Deno’s official web framework, takes the opposite approach: ship zero JavaScript by default. Only add interactivity where you actually need it. With Deno 2.0’s npm compatibility launched in October 2024, Fresh is now production-ready for mainstream adoption. This tutorial shows you how to build fast, lightweight web apps with Fresh’s islands architecture.

The Zero JavaScript Philosophy

Traditional frameworks like React and Next.js ship JavaScript for everything, even content that never changes. A blog post about databases gets wrapped in React components, bundled with hydration logic, and served with hundreds of kilobytes of runtime code. Fresh makes a different bet: most web content is static. Render it on the server and send plain HTML. Only the interactive pieces—a comment form, a search box, a “like” button—need JavaScript.

The result is dramatic. Fresh apps ship 90% less JavaScript than equivalent React apps. Pages load in milliseconds instead of seconds. Lighthouse scores consistently hit the high 90s. For mobile users on slow connections, this isn’t a marginal improvement. It’s the difference between a page that loads instantly and one that shows a blank screen for three seconds.

Islands Architecture Explained

Fresh implements this through “islands architecture”. Picture a webpage as an ocean. Most of it is static HTML—the water. Interactive components are islands scattered across that ocean. Traditional frameworks hydrate the entire ocean, turning every water molecule into JavaScript. Fresh keeps the ocean as simple HTML and only makes the islands interactive.

Technically, this works through file-based conventions. Your routes/ directory contains pages that render server-side by default. Your islands/ directory contains components that become interactive on the client. Fresh handles the rest—marking islands with HTML comments, shipping only their JavaScript, and hydrating them after the page loads.

Here’s what a static route looks like:

// routes/index.tsx
export default function Home() {
  return (
    <div>
      <h1>Welcome to Fresh</h1>
      <p>This entire page is server-rendered HTML. No JavaScript shipped.</p>
    </div>
  );
}

And here’s an interactive island:

// islands/Counter.tsx
import { useState } from "preact/hooks";

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

You compose them like normal components, but only the Counter ships JavaScript:

// routes/demo.tsx
import Counter from "../islands/Counter.tsx";

export default function DemoPage() {
  return (
    <div>
      <h1>Mostly Static</h1>
      <p>This text is plain HTML.</p>
      <Counter />  {/* Only this is interactive */}
    </div>
  );
}

The browser receives HTML for everything. JavaScript loads only for the Counter. The rest stays static.

Why Deno 2.0 Changes Everything

Fresh was always fast, but Deno’s ecosystem was the barrier. “I can’t use X npm package” killed adoption. Deno 2.0, released in October 2024, removed that barrier. It now recognizes package.json and node_modules, supports npm package imports via npm: specifiers, and gives you access to over 2 million npm packages. The package manager is also 15% faster than npm with a cold cache and 90% faster with a hot cache.

This matters because Fresh is no longer a toy for Deno enthusiasts. It’s a production framework. Deno uses it to power deno.com and Deno Deploy. E-commerce platform deco.cx builds on it. The ecosystem gap that made developers hesitant in 2024 closed in 2025.

Fresh also benefits from Deno’s built-in TypeScript support. No build step, no tsconfig debates, no Babel plugins. Write .tsx files and run them. The simplicity is jarring if you’re used to webpack configurations.

Getting Started Tutorial

Prerequisites: Install Deno 2.0 and have basic TypeScript knowledge. Then scaffold a project:

deno run -A -r https://fresh.deno.dev my-app
cd my-app

The project structure is minimal:

  • routes/ holds your pages and API routes (file-based routing)
  • islands/ holds interactive components
  • static/ holds CSS, images, and other assets
  • fresh.config.ts holds framework config (rarely touched)

Create your first route by editing routes/index.tsx. Add server-side data fetching with a handler:

import { Handlers, PageProps } from "$fresh/server.ts";

interface Post {
  title: string;
  excerpt: string;
}

export const handler: Handlers<Post[]> = {
  async GET(req, ctx) {
    const posts = [
      { title: "First Post", excerpt: "Learning Fresh" },
      { title: "Second Post", excerpt: "Islands are cool" },
    ];
    return ctx.render(posts);
  },
};

export default function BlogIndex({ data }: PageProps<Post[]>) {
  return (
    <div>
      <h1>Blog Posts</h1>
      {data.map(post => (
        <article key={post.title}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

The handler runs server-side, fetches data, and passes it to the component. The component renders to HTML. No client-side JavaScript runs unless you add an island.

To deploy, connect your GitHub repository to Deno Deploy. It detects Fresh projects automatically, deploys on every push, and distributes your app to 35+ edge locations globally. Add a custom domain in the dashboard. That’s it.

Fresh vs Next.js: When to Choose What

Next.js isn’t bad. It’s just overkill for many websites. If you’re building a blog, marketing site, or documentation portal, Fresh is simpler to build and faster for users. Static content doesn’t need React’s full power.

Choose Fresh when:

  • Content is mostly static (blogs, docs, marketing)
  • Performance is critical (mobile-first, global audience)
  • Interactivity is simple (forms, toggles, modals)
  • You value simplicity over flexibility

Choose Next.js when:

  • You need complex client-side state management
  • Your team is experienced with React
  • You depend heavily on React ecosystem libraries
  • You need advanced features like ISR or complex authentication

Performance comparison: Fresh apps ship 0-50 KB of JavaScript for typical content sites. Next.js apps start at 100-300 KB even for simple pages. Time to Interactive for Fresh is measured in milliseconds. Next.js takes 1-3 seconds. Developer experience is simpler with Fresh (no build step, no config files) but more flexible with Next.js (multiple rendering strategies, mature ecosystem).

The honest assessment from this Fresh vs Next.js comparison: You don’t need a Ferrari to drive to the grocery store. Next.js is the Ferrari—powerful, feature-rich, well-supported. Fresh is the bicycle—simple, fast, gets you where you need to go without overhead. Pick based on the trip you’re taking.

Conclusion

In 2026, shipping megabytes of JavaScript to render a blog post should feel absurd. Fresh makes it feel absurd again. Its islands architecture delivers the performance wins of static HTML with the interactivity of modern frameworks, exactly where you need it. Deno 2.0’s npm compatibility removed the ecosystem barrier. Fresh is now a viable production choice for content-heavy sites that value speed and simplicity. Try it for your next project. You might not go back to hydrating entire oceans.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:JavaScript