fresh
Fresh Deno web framework. Covers islands architecture, file-based routing, handlers, signals, and plugins. Use for server-rendered Deno applications. USE WHEN: user mentions "Fresh", "fresh", "islands architecture", "Deno SSR", "file-based routing in Deno", asks about "Preact SSR", "server-rendered Deno apps", "Deno web framework with hydration", "zero-config Deno framework" DO NOT USE FOR: Node.js apps - use `nextjs` or `remix` instead, Pure API servers - use `oak` instead, Edge runtimes - use `hono` instead, Static sites - use SSG tools like Astro
What this skill does
# Fresh Core Knowledge
> **Full Reference**: See [advanced.md](advanced.md) for Shared Signals, Middleware (Auth, CORS), Plugins, Error Handling, and Production Readiness.
## Basic Setup
```bash
# Create new Fresh project
deno run -A -r https://fresh.deno.dev my-project
# Project structure
my-project/
├── deno.json
├── dev.ts
├── main.ts
├── fresh.gen.ts # Auto-generated manifest
├── routes/ # File-based routing
├── islands/ # Interactive components
├── components/ # Static components
└── static/ # Static assets
```
## Configuration
```json
// deno.json
{
"tasks": {
"start": "deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts"
},
"imports": {
"$fresh/": "https://deno.land/x/[email protected]/",
"preact": "https://esm.sh/[email protected]",
"@preact/signals": "https://esm.sh/*@preact/[email protected]"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
```
## File-Based Routing
```tsx
// routes/index.tsx
export default function Home() {
return <h1>Welcome to Fresh</h1>;
}
// routes/users/[id].tsx - Dynamic route
import { PageProps } from "$fresh/server.ts";
export default function UserPage(props: PageProps) {
const { id } = props.params;
return <h1>User: {id}</h1>;
}
// routes/blog/[...slug].tsx - Catch-all
export default function BlogPost(props: PageProps) {
const { slug } = props.params;
return <h1>Post: {slug}</h1>;
}
// routes/(marketing)/pricing.tsx - Route groups
// URL: /pricing (group name not in URL)
```
## Handlers
```tsx
// routes/api/users.ts
import { Handlers } from "$fresh/server.ts";
export const handler: Handlers = {
GET(_req, _ctx) {
const users = [{ id: 1, name: "Alice" }];
return new Response(JSON.stringify(users), {
headers: { "Content-Type": "application/json" },
});
},
async POST(req, ctx) {
const body = await req.json();
// Process...
return new Response(JSON.stringify(body), { status: 201 });
},
};
```
### Handler with Page
```tsx
// routes/greet/[name].tsx
import { Handlers, PageProps } from "$fresh/server.ts";
interface Data {
greeting: string;
}
export const handler: Handlers<Data> = {
GET(_req, ctx) {
return ctx.render({ greeting: `Hello, ${ctx.params.name}!` });
},
};
export default function GreetPage({ data }: PageProps<Data>) {
return <h1>{data.greeting}</h1>;
}
```
## Islands Architecture
```tsx
// islands/Counter.tsx
import { useSignal } from "@preact/signals";
export default function Counter() {
const count = useSignal(0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick={() => count.value++}>Increment</button>
</div>
);
}
// routes/index.tsx - Using islands
import Counter from "../islands/Counter.tsx";
export default function Home() {
return (
<div>
<h1>My App</h1>
{/* This component is hydrated on the client */}
<Counter />
</div>
);
}
```
### Islands with Props
```tsx
// islands/Greeting.tsx
import { useSignal } from "@preact/signals";
interface GreetingProps {
initialName: string;
}
export default function Greeting({ initialName }: GreetingProps) {
const name = useSignal(initialName);
return (
<div>
<input
type="text"
value={name.value}
onInput={(e) => name.value = (e.target as HTMLInputElement).value}
/>
<p>Hello, {name.value}!</p>
</div>
);
}
```
## Signals (State Management)
```tsx
// islands/TodoList.tsx
import { useSignal, useComputed } from "@preact/signals";
export default function TodoList() {
const todos = useSignal<{ id: number; text: string; done: boolean }[]>([]);
const newTodo = useSignal("");
const remaining = useComputed(() =>
todos.value.filter((t) => !t.done).length
);
const addTodo = () => {
if (newTodo.value.trim()) {
todos.value = [...todos.value, {
id: Date.now(),
text: newTodo.value,
done: false,
}];
newTodo.value = "";
}
};
return (
<div>
<input
value={newTodo.value}
onInput={(e) => newTodo.value = (e.target as HTMLInputElement).value}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
/>
<button onClick={addTodo}>Add</button>
<p>{remaining.value} items remaining</p>
</div>
);
}
```
## Static Components
```tsx
// components/Header.tsx
import { JSX } from "preact";
interface HeaderProps {
title: string;
children?: JSX.Element;
}
export function Header({ title, children }: HeaderProps) {
return (
<header>
<h1>{title}</h1>
<nav>{children}</nav>
</header>
);
}
```
## Forms
```tsx
// routes/contact.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
interface PageData {
error?: string;
success?: boolean;
}
export const handler: Handlers<PageData> = {
GET(_req, ctx) {
return ctx.render({});
},
async POST(req, ctx) {
const form = await req.formData();
const name = form.get("name")?.toString();
const email = form.get("email")?.toString();
if (!name || !email) {
return ctx.render({ error: "All fields required" });
}
return ctx.render({ success: true });
},
};
export default function ContactPage({ data }: PageProps<PageData>) {
return (
<div>
{data.error && <p style={{ color: "red" }}>{data.error}</p>}
{data.success && <p style={{ color: "green" }}>Sent!</p>}
<form method="POST">
<input type="text" name="name" required />
<input type="email" name="email" required />
<button type="submit">Send</button>
</form>
</div>
);
}
```
## Basic Middleware
```tsx
// routes/_middleware.ts
import { FreshContext } from "$fresh/server.ts";
export async function handler(req: Request, ctx: FreshContext) {
const start = performance.now();
const resp = await ctx.next();
const duration = performance.now() - start;
resp.headers.set("X-Response-Time", `${duration.toFixed(2)}ms`);
return resp;
}
```
---
## Checklist
- [ ] Islands only for interactive components
- [ ] Static components for server-rendered content
- [ ] Middleware for cross-cutting concerns
- [ ] Error pages (_404.tsx, _500.tsx)
- [ ] Health/readiness endpoints
- [ ] Environment variables via Deno.env
- [ ] Form validation on both client and server
## When NOT to Use This Skill
- **Node.js Projects**: Use Next.js, Remix
- **Pure API Servers**: Use Oak for Deno API-only apps
- **Edge Runtimes**: Use Hono for Cloudflare Workers
- **Static Sites**: Use Astro or other SSG tools
## Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|--------------|--------------|------------------|
| Using islands for static content | Unnecessary hydration | Use static components |
| Heavy computation in islands | Slow client-side | Compute on server |
| Fetching data in islands | Waterfalls | Fetch in handlers |
| Hardcoding env variables | Not portable | Use `Deno.env.get()` |
## Quick Troubleshooting
| Issue | Solution |
|-------|----------|
| Island not hydrating | Move component to `islands/` folder |
| Props not passing | Ensure props are JSON-serializable |
| Handler not executing | Ensure `handler` is exported |
| 404 for dynamic route | Use `[param].tsx` naming |
| Signals not reactive | Access with `.value` |
## Reference Documentation
- [Routing](quick-ref/routing.md)
- [Handlers](quick-ref/handlers.md)
- [Islands](quick-ref/islands.md)
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.