DEV Community

Alex Spinov
Alex Spinov

Posted on

Deno Fresh Has a Free API — Zero-JS Web Framework with Island Hydration

Fresh is a full-stack web framework for Deno that ships zero JavaScript to the client by default. Islands architecture means only interactive components get hydrated.

Why Fresh?

  • Zero JS by default — pages render as pure HTML
  • Islands — only interactive components ship JavaScript
  • No build step — JIT rendering, instant startup
  • Deno Deploy — deploy globally in seconds

Quick Start

deno run -A -r https://fresh.deno.dev my-app
cd my-app
deno task start
Enter fullscreen mode Exit fullscreen mode

Pages (Server-Rendered)

// routes/index.tsx
export default function Home() {
  return (
    <div>
      <h1>Welcome to Fresh</h1>
      <p>This page ships ZERO JavaScript.</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Data Loading

// routes/users/[id].tsx
import { Handlers, PageProps } from '$fresh/server.ts';

interface User {
  id: string;
  name: string;
  email: string;
}

export const handler: Handlers<User> = {
  async GET(req, ctx) {
    const user = await db.getUser(ctx.params.id);
    if (!user) return ctx.renderNotFound();
    return ctx.render(user);
  },
};

export default function UserPage({ data }: PageProps<User>) {
  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Islands (Interactive Components)

// islands/Counter.tsx
import { useSignal } from '@preact/signals';

export default function Counter() {
  const count = useSignal(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => count.value++}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
// routes/index.tsx — only Counter ships JS
import Counter from '../islands/Counter.tsx';

export default function Home() {
  return (
    <div>
      <h1>Static content (no JS)</h1>
      <p>This paragraph is pure HTML.</p>
      <Counter /> {/* This island gets hydrated */}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

API Routes

// routes/api/users.ts
import { Handlers } from '$fresh/server.ts';

export const handler: Handlers = {
  async GET() {
    const users = await db.getUsers();
    return new Response(JSON.stringify(users), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
  async POST(req) {
    const body = await req.json();
    const user = await db.createUser(body);
    return new Response(JSON.stringify(user), { status: 201 });
  },
};
Enter fullscreen mode Exit fullscreen mode

Middleware

// routes/_middleware.ts
import { MiddlewareHandlerContext } from '$fresh/server.ts';

export async function handler(req: Request, ctx: MiddlewareHandlerContext) {
  const start = Date.now();
  const response = await ctx.next();
  const duration = Date.now() - start;
  response.headers.set('X-Response-Time', `${duration}ms`);
  return response;
}
Enter fullscreen mode Exit fullscreen mode

Deploy to Deno Deploy

# Install deployctl
deno install -A --no-check -f https://deno.land/x/deploy/deployctl.ts

# Deploy
deployctl deploy --project=my-app main.ts
Enter fullscreen mode Exit fullscreen mode

Building fast web apps? Check out my Apify actors for web scraping, or email spinov001@gmail.com for custom Deno solutions.

Fresh, Astro, or SvelteKit — which zero-JS framework do you prefer? Share below!

Top comments (0)