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
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>
);
}
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>
);
}
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>
);
}
// 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>
);
}
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 });
},
};
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;
}
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
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)