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
File-Based Routing
routes/
index.tsx -> /
about.tsx -> /about
posts/
index.tsx -> /posts
[id].tsx -> /posts/:id
api/
posts.ts -> /api/posts
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>
);
}
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>
);
}
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>
);
}
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 });
},
};
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;
}
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)