К основному содержанию
скилл CX Codex

hono-jsx — скилл для Claude Code: что делает и как установить

Настраивает серверный рендеринг интерфейсов в приложениях Hono через hono/jsx и связку с Vite без библиотек React.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add honojs/skills --skill hono-jsx -a claude-code
Открыть на GitHub

Что делает hono-jsx

Скилл задаёт правила вёрстки интерфейсов для Hono. Основа архитектуры — рендеринг HTML на сервере через jsxRenderer, сборка на Vite и клиентские компоненты на hono/jsx/dom только там, где браузеру нужно собственное состояние. По умолчанию проект разворачивается под Cloudflare Workers.

Главное ограничение — полный отказ от экосистемы React. Нельзя ставить react или использовать хуки на сервере: серверные компоненты представляют собой обычные функции, выполняемые один раз на запрос. Для стилизации пишется атрибут class, а не className. Доступ к контексту запроса внутри вложенных компонентов идёт через useRequestContext().

Какие задачи решает

  • настроить серверный рендеринг страниц через jsxRenderer в Hono
  • подключить сборку стилей и скриптов через Vite и vite-ssr-components
  • организовать вложенные макеты и передачу метатегов в <head>
  • получить доступ к контексту и переменным окружения через useRequestContext

Примеры запросов агенту

  • Создай базовый макет страницы для Hono с подключением стилей через renderer.tsx
  • Напиши форму добавления задачи на hono/jsx без клиентского JavaScript
  • Сделай серверный компонент профиля, который читает данные из c.env.KV через useRequestContext

Как установить hono-jsx

1Через Skills CLI (Vercel)

npx skills add honojs/skills --skill hono-jsx -a claude-code

2Вручную: положить файлы

~/.claude/skills/hono-jsx/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/hono-jsx/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о hono-jsx

Как установить hono-jsx в Claude Code?

Выполните команду npx skills add honojs/skills --skill hono-jsx -a claude-code в терминале или сохраните файл в ~/.claude/skills/hono-jsx/SKILL.md.

Зачем нужен скилл hono-jsx?

Он помогает агенту правильно структурировать UI в приложениях Hono, предотвращая случайную установку зависимостей React.

Почему в проекте нельзя использовать React?

Рантаймом выступает hono/jsx, где серверные компоненты вызываются как асинхронные функции без хуков и состояния. Вместо className в коде используется стандартный HTML-атрибут class.

Исходный текст

SKILL.md · MIT
---
name: hono-jsx
description: Use when building UI for a Hono app with hono/jsx — pages, layouts, forms, and interactive parts — server-rendered with jsxRenderer, developed and built with Vite, with client components via hono/jsx/dom. TRIGGER when tsconfig has jsxImportSource "hono/jsx", code imports 'hono/jsx', 'hono/jsx/dom', 'hono/jsx-renderer' or 'vite-ssr-components', or the user asks for a page, screen, layout, or form in a Hono project. Never reach for React.
---

# Hono JSX Skill

Build UI with `hono/jsx`: server-rendered HTML first, a small amount of client-side JSX only where the browser needs state. The `hono` skill covers the JSX syntax and `c.html()`; this skill covers how to structure the app. Cloudflare Workers with Vite is the default setup; see "Other runtimes" for the rest.

## No React

`hono/jsx` is the JSX runtime. Do not bring React in:

- Do not install `react`, `react-dom`, `@types/react`, or `@vitejs/plugin-react`. Do not import from `react`.
- `tsconfig.json` has `"jsx": "react-jsx"` and `"jsxImportSource": "hono/jsx"`. The `react-jsx` value is the transform name, not a React dependency.
- Server components are plain functions rendered once per request. No hooks, no state, no event handlers on the server. Need data: make the component `async` and `await`. Need interactivity: a client component with `hono/jsx/dom`, for that part only.
- Write `class`, not `className`.
- `vite-ssr-components` exports both flavors: import from `vite-ssr-components/hono`, never from `/react`.

## Project Layout (Cloudflare Workers + Vite)

Start from what `create-hono` generates with the `cloudflare-workers` template (`npm create hono@next`, or `npx hono init --template cloudflare-workers` in an existing directory): `src/index.ts` with a text response, `cloudflare.config.ts` for the `cf` CLI, and a `vite.config.ts` with only `cloudflare()`. It has no JSX setup; add the pieces below, and match this layout in existing projects instead of inventing a different structure. Verify routes with the Hono CLI from the `hono` skill (`npx hono request /`); it is a dev dependency of the template and runs the app through Vite, so `c.env` has the local bindings automatically.

```
src/
  index.tsx      # Hono app, routes (renamed from index.ts)
  renderer.tsx   # jsxRenderer layout: <html>, <head>, Vite assets
  client.ts      # optional: browser-side code
  style.css
public/          # static files served as-is
cloudflare.config.ts   # cf CLI: worker name, entrypoint, bindings
vite.config.ts
```

```jsonc
// package.json (scripts, from the template)
{
  "dev": "cf dev",
  "build": "cf build",
  "deploy": "cf deploy",
  "typecheck": "cf workers types && tsc"
}
```

On top of the template:

1. Rename `src/index.ts` to `src/index.tsx` and point the entrypoint import in `cloudflare.config.ts` at it:

   ```ts
   import * as entrypoint from './src/index.tsx' with { type: 'cf-worker' }
   ```

2. Install `vite-ssr-components` as a dev dependency and add its plugin:

   ```ts
   // vite.config.ts
   import { cloudflare } from '@cloudflare/vite-plugin'
   import { defineConfig } from 'vite'
   import ssrPlugin from 'vite-ssr-components/plugin'

   export default defineConfig({
     plugins: [cloudflare(), ssrPlugin()],
   })
   ```

3. Add `src/renderer.tsx`, `src/style.css`, and `public/`:

```tsx
// src/renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { Link, Script, ViteClient } from 'vite-ssr-components/hono'

export const renderer = jsxRenderer(({ children }) => {
  return (
    <html>
      <head>
        <ViteClient />
        <Link href="/src/style.css" rel="stylesheet" />
        <Script src="/src/client.ts" />
      </head>
      <body>{children}</body>
    </html>
  )
})
```

```tsx
// src/index.tsx
import { Hono } from 'hono'
import { renderer } from './renderer'

const app = new Hono<{ Bindings: Env }>()

app.use(renderer)

app.get('/', (c) => {
  return c.render(<h1>Hello!</h1>)
})

export default app
```

`vite-ssr-components` does the Vite plumbing: `<ViteClient />` injects the Vite client and SSR hot reload in dev and renders nothing in production; `<Script>` and `<Link>` point at source files and are rewritten to the hashed build output via the manifest. The plugin scans for them and adds each referenced file as a build entry, so there is no `rollupOptions.input` to maintain. Keep `<Script>` and `<Link>` as the component names, or configure the plugin's `components` option.

`npm run dev` (`cf dev`) is for humans; verify with the Hono CLI instead. Bindings are declared in `cloudflare.config.ts`, and their `Env` type comes from `npm run typecheck`; never hand-write it. In an existing wrangler project, keep wrangler, `npm run cf-typegen`, and `CloudflareBindings` as described in the `hono` skill.

## Layouts and Pages

`jsxRenderer` sets the layout; handlers call `c.render()` with the page content.

- **Per-page head content**: write `<title>`, `<meta>`, and `<link>` inside the page component. They are hoisted into `<head>`. Existing head elements stay, so keep the layout's `<title>` out if pages set their own.
- **Props to the layout** (a title, a current-nav key): extend `ContextRenderer` once, then pass the props as the second argument of `c.render()`.

```tsx
declare module 'hono' {
  interface ContextRenderer {
    (content: string | Promise<string>, props: { title: string }): Response
  }
}

export const renderer = jsxRenderer(({ children, title }) => (
  <html>
    <head>
      <title>{title}</title>
    </head>
    <body>{children}</body>
  </html>
))

app.get('/about', (c) => c.render(<About />, { title: 'About' }))
```

- **Nested layouts**: a sub-app can register its own `jsxRenderer` and receive the parent as `Layout`.

```tsx
const blog = new Hono()
blog.use(
  jsxRenderer(({ children, Layout }) => (
    <Layout>
      <nav>Blog</nav>
      {children}
    </Layout>
  ))
)
app.route('/blog', blog)
```

- **Request data inside components**: `useRequestContext()` from `hono/jsx-renderer` returns `c`, so a deep component can read `c.req`, `c.env`, or `c.var` without prop drilling. Prefer loading data in the handler and passing props when the data is only needed at one place.

```tsx
import { useRequestContext } from 'hono/jsx-renderer'

const UserMenu = async () => {
  const c = useRequestContext<{ Bindings: Env }>()
  const user = await c.env.KV.get('user')
  return <span>{user}</span>
}
```

- **Shared components**: put reusable pieces in `src/components/`. They are functions taking props; `FC<Props>` and `PropsWithChildren<Props>` from `hono/jsx` type them.

## Forms and Mutations

The default is an HTML form posting to a Hono route and a redirect. No client JavaScript is required, and it works before any script loads.

```tsx
app.get('/todos', async (c) => {
  const todos = await listTodos(c.env)
  return c.render(
    <>
      <form method="post" action="/todos">
        <input name="title" required />
        <button type="submit">Add</button>
      </form>
      <ul>{todos.map((t) => <li>{t.title}</li>)}</ul>
    </>
  )
})

app.post('/todos', async (c) => {
  const body = await c.req.parseBody()
  await addTodo(c.env, String(body.title))
  return c.redirect('/todos')
})
```

Validate with `validator('form', ...)` or a schema validator from the `hono` skill; on failure re-render the page with the error message rather than returning JSON.

## Client-Side Behavior

Most pages need no client JavaScript. When a part does, start with plain DOM code and reach for `hono/jsx/dom` only for UI that holds state and re-renders. Neither involves React.

**Plain script** — the default. Put it in `src/client.ts` and load it from the renderer with `<Script src="/src/client.ts" />`.

```ts
// src/client.ts
/// <reference lib="dom" />
document.querySelector('#menu-toggle')?.addEventListener('click', () => {
  document.querySelector('#menu')?.classList.toggle('open')
})
```

**`hono/jsx/dom`** — for a stateful part such as a counter, a live filter, or a multi-step form. The server renders an empty container; the client renders into it with `render()`. There is no hydration, so that part is empty until the script runs. Keep the page's actual content server-rendered.

```tsx
// src/client.tsx
/// <reference lib="dom" />
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

render(<Counter />, document.getElementById('counter')!)
```

Hooks (`useState`, `useEffect`, `useRef`, ...) have React-compatible signatures and are imported from `hono/jsx`. Calling the server from the client is a `fetch` to a JSON route; `hc` from `hono/client` adds typed calls once there is an API surface worth typing.

**Type-checking client files.** The template's `tsconfig.json` has `lib: ["ESNext"]` and no `DOM`, so `document` fails `tsc`. Put a lib reference at the top of each client file instead of editing `tsconfig.json`:

```ts
/// <reference lib="dom" />
```

Vite does not read `lib`, so this only matters for `tsc`.

## Styling

- A stylesheet at `src/style.css` linked with `<Link href="/src/style.css" rel="stylesheet" />` in the renderer. Vite processes it, so CSS imports and PostCSS work.
- `hono/css` for component-scoped styles: `css` returns a class name, `<Style />` in `<head>` emits the collected rules.

```tsx
import { css, Style } from 'hono/css'

const card = css`
  padding: 1rem;
  &:hover { background: #eee; }
`
// <head><Style /></head> ... <div class={card}>...</div>
```

## Streaming

For a page where one part is slow, keep the shell instant: enable `stream: true` on the renderer and wrap the slow async component in `Suspense`.

```tsx
import { Suspense } from 'hono/jsx/streaming'

export const renderer = jsxRenderer(({ children }) => <html><body>{children}</body></html>, { stream: true })

app.get('/', (c) =>
  c.render(
    <Suspense fallback={<p>Loading...</p>}>
      <SlowList />
    </Suspense>
  )
)
```

`Suspense` and `ErrorBoundary` are marked experimental in the docs. Use them for genuinely slow data, not by default.

## Escaping and Raw HTML

JSX escapes interpolated strings. To insert trusted HTML, use `dangerouslySetInnerHTML={{ __html }}` or `raw()` from `hono/html`. Never pass user input through either.

## Other Runtimes

Without the Cloudflare Vite plugin, use `@hono/vite-dev-server` for dev and `@hono/vite-build/<runtime>` for the build. Client assets are then built in a separate Vite mode and referenced by fixed paths:

```ts
// vite.config.ts
import build from '@hono/vite-build/node' // or /bun, /cloudflare-workers
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      build: { rollupOptions: { input: './src/client.tsx', output: { entryFileNames: 'static/client.js' } } },
    }
  }
  return {
    plugins: [
      // staticRoot is a Node adapter option: it serves dist/static from the built server
      build({ entry: 'src/index.tsx', staticRoot: './dist' }),
      devServer({ entry: 'src/index.tsx' }),
    ],
  }
})
```

```tsx
// renderer: pick the script path by mode
{import.meta.env.PROD ? (
  <script type="module" src="/static/client.js" />
) : (
  <script type="module" src="/src/client.tsx" />
)}
```

Build with `vite build --mode client && vite build`, then run `node dist/index.js` (the Node build bundles `@hono/node-server`, install it as a dependency). `@hono/vite-dev-server` also has adapters (`/cloudflare`, `/node`, `/bun`) when bindings or runtime APIs are needed in dev.

## Documentation

Fetch with `Accept: text/markdown` (see the `hono` skill):

- https://hono.dev/docs/guides/jsx
- https://hono.dev/docs/guides/jsx-dom
- https://hono.dev/docs/middleware/builtin/jsx-renderer
- https://hono.dev/docs/helpers/css
- https://hono.dev/docs/helpers/html
- https://github.com/yusukebe/vite-ssr-components
- https://github.com/honojs/vite-plugins (dev-server, build)

## Checklist

- No React packages; `jsxImportSource` is `hono/jsx`; `class` attributes.
- Layout in `jsxRenderer`, pages via `c.render()`, per-page `<title>` inside the page.
- Forms post to routes and redirect; client JavaScript only where needed, plain DOM first.
- Vite assets go through `vite-ssr-components` (`ViteClient`, `Script`, `Link`), not hand-written `<script>` tags, on Cloudflare.
- Bindings types generated, never hand-written: `Env` from `npm run typecheck` (`cf`), `CloudflareBindings` from `npm run cf-typegen` (wrangler).

© honojs · MIT · honojs/skills

Похожие скиллы

Всё по теме «Фронтенд и UI» →
frontend-design официальный anthropics Скилл задаёт осмысленное визуальное направление для новых интерфейсов и редизайна. Подойдёт, когда стандартный шаблонный UI не устраивает. 1 025 790установок design-taste-frontend Leonxlnx Скилл задаёт арт-дирекцию для публичных страниц и рабочих экранов, а затем проверяет результат в браузере по скриншотам. Нужен, когда интерфейс выглядит шаблонным или недоработанным. CX Codex +1 678 700установок prototype Matt Pocock Создаёт временный прототип, чтобы проверить логику, модель состояния или вид интерфейса до работы над основным кодом. 986 201установок hyperframes heygen-com Скилл задаёт агенту правила рендера HTML-кода в видео через HyperFrames. Помогает собирать проморолики, эксплейнеры и титры прямо в коде проекта. CX Codex +1 1 200 000установок hyperframes-cli heygen-com Управляет проектами HyperFrames через терминал: собирает таймлайн, проверяет контраст кадров, запускает предпросмотр и рендерит видео локально или в облаке. CX Codex +1 1 300 000установок webapp-testing официальный anthropics Скилл запускает проверки локальных веб-приложений через Playwright: помогает отлаживать UI, делать скриншоты и смотреть логи браузера. 186 758установок

Из того же набора2

Спонсоры проекта

Компании, которые помогают держать аналитику открытой