| name | kita-html |
| description | KitaJS HTML — TypeScript JSX rendering library. Use when building HTML templates, creating server-side components, handling async rendering, streaming HTML with Suspense, or integrating with Fastify for HTML responses. Covers type-safe JSX components, layouts, async/await patterns, error boundaries, and Fastify HTML plugin usage. |
KitaJS HTML Skill
KitaJS HTML is a modern TypeScript JSX framework for server-side HTML rendering. It provides type-safe component definitions, async/await support, streaming with Suspense, and seamless Fastify integration.
Critical Limitation: Server-Side Only
⚠️ KitaJS HTML is for server-side rendering only. You cannot write client-side JavaScript logic inside components like you do in React.
What You CANNOT Do:
- Use
useState, useEffect, useCallback, or any React hooks
- Add event handlers (
onClick, onChange, etc.) with inline logic
- Use browser APIs (
localStorage, window, document)
- Create stateful components or client-side interactivity
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
What You CAN Do:
- Render HTML structure server-side
- Fetch data from databases/APIs
- Process and transform data
- Output static HTML
- Use
data-* attributes for client-side enhancement (with separate JS)
function Counter(props: { initialCount: number }) {
return (
<div>
<p>Current count: {props.initialCount}</p>
<button data-action="increment">Click me</button>
</div>
);
}
Adding Client-Side Interactivity:
Use data-* attributes and separate JavaScript files:
function FormField(props: { id: string; label: string }) {
return (
<div>
<label htmlFor={props.id}>{props.label}</label>
<input id={props.id} data-validate="email" type="email" />
</div>
);
}
document.querySelectorAll('[data-validate="email"]').forEach(input => {
input.addEventListener('blur', () => {
});
});
Core Concepts
1. Basic JSX Components
KitaJS HTML uses JSX syntax to create type-safe HTML components. Components are functions that return HTML strings.
import { html } from '@kitajs/html';
function Greeting(props: { name: string }) {
return html`<p>Hello, ${props.name}!</p>`;
}
const page = html`
<div>
${Greeting({ name: 'World' })}
</div>
`;
Key Points:
- JSX is compiled to function calls
- Components receive props as the first argument
- HTML strings are tagged templates
- Props are type-safe with TypeScript interfaces
2. Layout Components
Create reusable layout wrappers for HTML structure:
interface LayoutProps {
title?: string;
head?: string;
children: string;
}
export function Layout(props: LayoutProps) {
return (
<>
{'<!doctype html>'}
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{props.title || 'Default Title'}</title>
<link rel="stylesheet" href="/style.css" />
{props.head}
</head>
<body>{props.children}</body>
</html>
</>
);
}
const page = (
<Layout title="My Page" head={<meta name="description" content="..." />}>
<h1>Welcome</h1>
</Layout>
);
Layout Patterns:
- Use fragments
<> to return multiple root elements
- Include DOCTYPE as a string literal:
{'<!doctype html>'}
- Nest
head slot for metadata and styles
- Nest
children slot for page content
3. Async Components & Suspense
Handle asynchronous data fetching in components:
import { Suspense } from '@kitajs/html';
async function UserCard(props: { userId: string }) {
const user = await fetchUser(props.userId);
return (
<div class="card">
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
const page = (
<Layout>
<Suspense fallback={<div>Loading user...</div>}>
<UserCard userId="123" />
</Suspense>
</Layout>
);
Suspense Behavior:
- Fallback UI renders first, then streams the async component
- Ideal for server-side rendering with data fetching
- Enables progressive HTML streaming
4. Conditional Rendering & Loops
Use JavaScript expressions for logic:
interface ListProps {
items: Array<{ id: string; name: string }>;
empty?: boolean;
}
function ItemList(props: ListProps) {
return (
<ul>
{props.empty ? (
<li>No items found</li>
) : (
props.items.map(item => (
<li key={item.id}>{item.name}</li>
))
)}
</ul>
);
}
5. Fastify Integration
Use the Fastify HTML plugin to send rendered HTML:
import Fastify from 'fastify';
import { fastifyHtmlPlugin } from '@kitajs/fastify-html-plugin';
const app = Fastify();
app.register(fastifyHtmlPlugin);
app.get('/page', (req, reply) =>
reply.html(
<Layout title="Home">
<h1>Welcome to my site</h1>
</Layout>
)
);
app.get('/stream', (req, reply) =>
reply.html(
<Layout title="Dashboard">
<Suspense rid={req.id} fallback={<div>Loading...</div>}>
<UserProfile userId={req.query.userId} />
</Suspense>
</Layout>
)
);
app.listen({ port: 3000 });
reply.html() options:
- Automatically sets
Content-Type: text/html; charset=utf-8
- Supports streaming with Suspense components
- Returns a Promise that resolves when HTML is sent
rid parameter enables request-specific streaming context
Common Patterns
Error Boundaries
Create error boundary components to catch rendering errors:
async function PageWithErrorBoundary(props: any) {
try {
return (
<Layout>
<SomeAsyncComponent />
</Layout>
);
} catch (err) {
return (
<Layout title="Error">
<div class="error">
<h1>Something went wrong</h1>
<p>{err instanceof Error ? err.message : 'Unknown error'}</p>
</div>
</Layout>
);
}
}
Props with Children
Define components that accept nested content:
interface CardProps {
title: string;
children: string;
}
function Card(props: CardProps) {
return (
<div class="card">
<h2>{props.title}</h2>
<div class="content">{props.children}</div>
</div>
);
}
const page = (
<Card title="My Card">
<p>This content goes inside the card</p>
</Card>
);
Conditional Elements
Use ternary operators and logical AND for conditional rendering:
function Dashboard(props: { isAdmin: boolean; items: Item[] }) {
return (
<div>
{props.isAdmin && <AdminPanel />}
{props.items.length > 0 ? (
<ItemList items={props.items} />
) : (
<EmptyState />
)}
</div>
);
}
Best Practices
-
Type Your Props: Use TypeScript interfaces for all component props
interface ButtonProps {
label: string;
href: string;
variant?: 'primary' | 'secondary';
}
-
Compose Components: Build larger UIs from smaller, reusable pieces
function Page() {
return (
<Layout>
<Header />
<MainContent />
<Footer />
</Layout>
);
}
-
Use Async Wisely: Async components are great for data fetching but can slow page load if not streamed
<Suspense>
<RecommendedProducts />
</Suspense>
const criticalData = await fetchEssentialData();
-
Escape User Input: Always escape dynamic content to prevent XSS
function SafeHTML(props: { userText: string }) {
return html`<p>${props.userText}</p>`;
}
-
Organize Component Structure:
components/
├── layouts/
│ ├── Base.tsx
│ └── Admin.tsx
├── sections/
│ ├── Header.tsx
│ └── Footer.tsx
└── ui/
├── Button.tsx
└── Card.tsx
Integration Tips
-
With Express: Use manual response handling instead of reply.html()
app.get('/page', (req, res) => {
const html = <Layout><h1>Page</h1></Layout>;
res.type('text/html').send(html);
});
-
With Next.js: Use KitaJS for email templates or partial rendering, not full page rendering
-
With Databases: Fetch data before rendering, not inside components for critical paths
app.get('/users/:id', async (req, reply) => {
const user = await db.users.findOne({ id: req.params.id });
return reply.html(<UserProfile user={user} />);
});
Related Resources