DEV Community

Alex Spinov
Alex Spinov

Posted on

Fresh Framework Has a Free API — Heres How to Build Islands-Based Apps on Deno

Fresh is a next-gen web framework for Deno — zero JS shipped by default, islands architecture for interactivity, and instant deployments on Deno Deploy.

Why Fresh?

  • Zero JS by default: Pages render as pure HTML
  • Islands architecture: Only interactive components ship JS
  • No build step: Write code, deploy instantly
  • Deno native: TypeScript, permissions, Web APIs built-in
  • Preact: Lightweight 3KB runtime for islands

Quick Setup

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

File-Based Routing

routes/
  index.tsx       -> /
  about.tsx       -> /about
  posts/
    index.tsx     -> /posts
    [id].tsx      -> /posts/:id
  api/
    posts.ts      -> /api/posts
Enter fullscreen mode Exit fullscreen mode

Route with Data Loading

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

export const handler: Handlers = {
  async GET(_req, ctx) {
    const post = await db.post.findUnique({ where: { id: ctx.params.id } });
    if (!post) return ctx.renderNotFound();
    return ctx.render(post);
  },
};

export default function PostPage({ data }: PageProps) {
  return (
    <article>
      <h1>{data.title}</h1>
      <p>{data.body}</p>
    </article>
  );
}
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++}>+1</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Use in a route — only this island ships JavaScript:

import Counter from '../islands/Counter.tsx';

export default function Home() {
  return (
    <div>
      <h1>Welcome</h1>
      <p>This is static HTML  zero JS.</p>
      <Counter /> {/* Only this ships JS */}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

API Routes

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

export const handler: Handlers = {
  async GET() {
    const posts = await db.post.findMany();
    return new Response(JSON.stringify(posts), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
  async POST(req) {
    const body = await req.json();
    const post = await db.post.create({ data: body });
    return new Response(JSON.stringify(post), { 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 resp = await ctx.next();
  resp.headers.set('X-Response-Time', `${Date.now() - start}ms`);
  return resp;
}
Enter fullscreen mode Exit fullscreen mode

Real-World Use Case

A content site switched from Next.js to Fresh. Their Lighthouse score jumped from 67 to 99 because Fresh ships zero JS for static pages. Page load dropped from 2.1s to 0.3s.


Need to automate data collection? Check out my Apify actors for ready-made scrapers, or email spinov001@gmail.com for custom solutions.

Top comments (0)