Deno Frontend
DenoOfficialCreated by denolandDistributed by Deno
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides guidance on developing web frontends within the Deno ecosystem. It distinguishes between two primary approaches: using standard npm-based frameworks (like React, Vite, Astro, or Next.js) which run natively in Deno, and using Fresh, Deno's own island-architecture framework. The documentation covers project setup, configuration, and migration paths for Fresh 2.x, including details on routing, handlers, and state management.
What it does
- Explains how to run popular npm frontend frameworks using Deno's native task runner.
- Provides setup instructions for Fresh, Deno's server-side rendering framework.
- Details the project structure for Fresh 2.x applications.
- Outlines the migration process from Fresh 1.x to 2.x.
- Describes how to handle routing, data fetching, and state in Fresh.
- Offers configuration tips for integrating tools like Tailwind CSS with Deno.
When to use it
- When starting a new web project and deciding between standard npm frameworks or Deno's Fresh.
- When you need to run existing React, Vite, or Next.js projects using Deno.
- When building a new Deno-first application that requires server-side rendering.
- When upgrading an existing Fresh 1.x project to the 2.x version.
- When you need to understand how to structure routes, islands, and handlers in Fresh.
Advantages
- Seamless integration with the existing npm frontend ecosystem.
- Minimal client-side JavaScript overhead via Fresh's island architecture.
- Clear guidance on project configuration and dependency management.
- Automated migration tools for upgrading legacy Fresh projects.
- Consistent patterns for server-side data handling and routing.
Usage examples
- “How do I start a new Vite project using Deno?”
- You can use 'deno create vite my-app', followed by 'deno install' and 'deno task dev' to run the project.
- “How do I migrate my Fresh 1.x project to 2.x?”
- Run 'deno run -Ar jsr:@fresh/update' to automatically convert imports, update handler signatures, and switch to the Vite-based build system.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/denoland/deno-frontend/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip deno-frontend.zip -d .agents/skills
Documentation by denoland
Frontend development with Deno
Two paths. Pick by what the project already uses.
Regular npm frameworks — the usual choice
Deno runs the normal frontend ecosystem: React, Vue, Svelte, Solid, Vite, Astro, Next.js, Nuxt, SvelteKit, Remix, SolidStart. Nothing needs to be ported, and there is no Deno-specific way to write them.
deno create vite my-app # or astro, next, nuxt, svelte…
cd my-app
deno install
deno task devdeno create is npm create. deno install reads package.json.
deno task <script> runs package.json scripts. That is the whole difference.
Follow the framework's own documentation for everything else — routing, components, data loading, and config are the framework's concern, not Deno's. Don't invent Deno-flavoured variants of their APIs, and don't reach for Fresh patterns in a React or Svelte project.
The only things worth knowing:
Some frameworks need
"nodeModulesDir": "auto"indeno.json; Next.js does.Permissions apply to the dev server too — framework tasks generally want
-A.Deploying: see the
deno-deployskill, which lists per-framework build commands and presets.
Fresh — Deno's own framework
Fresh suits a new Deno-first project that wants server rendering with minimal
client JavaScript. It uses island architecture: pages render on the server,
and only components in islands/ ship JavaScript.
deno run -Ar jsr:@fresh/init
cd my-project
deno task dev # http://localhost:5173A Fresh project is Vite-based: vite.config.ts for the build, main.ts for the
server, file-based routes in routes/, islands in islands/, server-only
components in components/. Preact supplies the component model, with signals
for state.
// islands/Counter.tsx — interactive, ships JS
import { useSignal } from "@preact/signals";
export default function Counter() {
const count = useSignal(0);
return <button onClick={() => count.value++}>Count: {count.value}</button>;
}Three rules carry most of the weight:
Islands ship JavaScript — keep them small, leave everything else in
components/.Island props must be serializable. Functions cannot be passed.
Handlers take a single
(ctx)parameter.
Load references/FRESH.md for routes, handlers, define helpers, middleware,
islands, signals, and Tailwind.
Use Fresh 2.x, imported from "fresh". For an existing 1.x project, or one
pinned to a 2.0.0-alpha.* release, see references/FRESH_MIGRATION.md.
Further reading
https://fresh.deno.dev/docs — Fresh documentation
references/FRESH.md— Fresh 2.x patterns in depthreferences/FRESH_MIGRATION.md— Fresh 1.x and alpha migration
Files
- ATTRIBUTION.mdAdded by AutoGineer875 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md3.1 KiB
- references/FRESH.md7.5 KiB
- references/FRESH_MIGRATION.md3.0 KiB
references/FRESH.md
Fresh 2.x
Docs: https://fresh.deno.dev/docs. All imports come from "fresh" or
"fresh/runtime".
Project layout
my-project/
├── deno.json # config, dependencies, tasks
├── main.ts # server entry point
├── client.ts # client entry point (CSS imports)
├── vite.config.ts # Vite configuration
├── routes/ # pages and API routes
│ ├── _app.tsx # outer HTML wrapper
│ ├── _layout.tsx # optional nested layout
│ ├── _error.tsx # unified error page (404 and 500)
│ ├── _middleware.ts
│ └── index.tsx # /
├── islands/ # interactive, hydrated on the client
├── components/ # server-only, ships no JS
├── static/
└── utils/state.ts # define helpersThere is no manifest file, no separate dev entry point, and no separate config file.
main.ts
import { App, fsRoutes, staticFiles, trailingSlashes } from "fresh";
const app = new App()
.use(staticFiles())
.use(trailingSlashes("never"));
await fsRoutes(app, {
dir: "./",
loadIsland: (path) => import(`./islands/${path}`),
loadRoute: (path) => import(`./routes/${path}`),
});
if (import.meta.main) {
await app.listen();
}vite.config.ts
import { defineConfig } from "vite";
import { fresh } from "@fresh/plugin-vite";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [fresh(), tailwindcss()],
});deno.json
{
"tasks": {
"dev": "vite",
"build": "vite build",
"preview": "deno serve -A _fresh/server.js"
},
"imports": {
"fresh": "jsr:@fresh/core@^2",
"fresh/runtime": "jsr:@fresh/core@^2/runtime",
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1",
"@preact/signals": "npm:@preact/signals@^2",
"preact": "npm:preact@^10",
"preact/hooks": "npm:preact@^10/hooks",
"@/": "./"
}
}Imports
import { App, fsRoutes, staticFiles } from "fresh";
import { cors, csp, trailingSlashes } from "fresh";
import { createDefine, HttpError } from "fresh";
import type { Middleware, PageProps, RouteConfig } from "fresh";
import { IS_BROWSER } from "fresh/runtime";
import { computed, signal, useSignal } from "@preact/signals";
import { useEffect, useRef, useState } from "preact/hooks";Routing
File-based, from routes/:
| File | Matches |
|---|---|
about.tsx | /about |
blog/[slug].tsx | /blog/my-post |
docs/[[version]].tsx | /docs and /docs/v2 |
old/[...path].tsx | /old/foo/bar |
(marketing)/ | shared layout, no URL segment |
_app.tsx wraps every page:
import type { PageProps } from "fresh";
export default function App({ Component }: PageProps) {
return (
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My App</title>
</head>
<body>
<Component />
</body>
</html>
);
}Define helpers
One-time setup giving handlers and pages a shared typed state:
// utils/state.ts
import { createDefine } from "fresh";
export interface State {
user?: { id: string; name: string };
}
export const define = createDefine<State>();Bare handler exports work but lose type safety — prefer define.handlers().
Data fetching
Handler returning { data } — the default. Supports auth checks and
redirects before render:
// routes/posts.tsx
import { define } from "@/utils/state.ts";
export const handler = define.handlers(async (ctx) => {
const res = await fetch("https://example.com/posts");
return { data: { posts: await res.json() } };
});
export default define.page<typeof handler>(({ data }) => (
<ul>{data.posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
));Async page component — fine when there is no auth or redirect logic:
export default async function ServersPage() {
const servers = await db.query("SELECT * FROM servers");
return <ul>{servers.map((s) => <li key={s.id}>{s.name}</li>)}</ul>;
}Handlers
Single (ctx) parameter, one function or per-method:
export const handler = define.handlers({
GET(ctx) {
return Response.json({ users: [] });
},
async POST(ctx) {
const body = await ctx.req.json();
return Response.json({ created: true }, { status: 201 });
},
});The context object:
| Property | Is |
|---|---|
ctx.req | the Request |
ctx.url | URL with pathname, searchParams |
ctx.params | route parameters |
ctx.state | request-scoped data shared with middleware |
ctx.config | Fresh configuration |
ctx.route | matched route pattern |
ctx.error | caught error, on error pages |
Methods: ctx.render(<JSX />, { status, headers }) (JSX only, not data
objects), ctx.redirect("/other", 301), ctx.next().
Throw HttpError to reach _error.tsx:
import { HttpError } from "fresh";
if (!post) throw new HttpError(404);
if (!ctx.state.user) throw new HttpError(401);Middleware
// routes/_middleware.ts
import { define } from "@/utils/state.ts";
export const handler = define.middleware(async (ctx) => {
console.log(`${ctx.req.method} ${ctx.url.pathname}`);
const response = await ctx.next();
return response;
});Islands
Components in islands/ (or (_islands)/ inside routes) hydrate on the client.
Use them for interactions, client state, and browser APIs — nothing else.
Props must be serializable: primitives, Infinity/NaN/-0, Array, Map, Set,
plain objects, URL, Date, RegExp, Uint8Array, JSX elements, signals, and
circular references. Functions cannot be passed.
For code that must not run during SSR:
import { IS_BROWSER } from "fresh/runtime";
export default function LocalStorageCounter() {
if (!IS_BROWSER) return <div>Loading...</div>;
const count = useSignal(Number(localStorage.getItem("count") ?? 0));
return <button onClick={() => count.value++}>Count: {count.value}</button>;
}Preact
Preact is a 3KB React alternative. Hooks behave as in React; class and
className both work, class is the convention.
Signals are the preferred state primitive — granular updates, definable outside a component:
import { computed, signal } from "@preact/signals";
const count = signal(0);
const doubled = computed(() => count.value * 2);Inside a component use useSignal(0).
Tailwind
Optional. Fresh builds with Vite, so Tailwind attaches as a Vite plugin. Install both packages — the plugin and the core library:
deno add npm:@tailwindcss/vite npm:tailwindcssAdd tailwindcss() to vite.config.ts plugins, put @import "tailwindcss"; in
a CSS file (assets/styles.css by convention), and import that file from
client.ts. Prefer utility classes over @apply; use the class strategy for
dark mode.
Build and deploy
deno task dev # dev server, port 5173
deno task build # production build — required before deploying
deno task preview # preview the build locally
deno deploy --prodThe build step is not optional; deploying without it ships no compiled output.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- denoland
- Publisher
- Deno (Official)
- Distributed by
- Deno
- Source
- denoland/skills · skills/deno-frontend · commit d5f6ace
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by denoland or Deno.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download deno-frontend.zip