Zero dependencies. 12.5 kB gzipped. Islands, SSR streaming, and file-based routing — out of the box.
Karui is a full-stack TypeScript framework with its own JSX runtime. No React. No Preact. No mandatory runtime dependencies. Just fast, lightweight, production-ready apps.
npx @rigbyhost/karui create my-app
cd my-app
bun devMost frameworks ask you to assemble a puzzle:
| What you need | React ecosystem | Karui |
|---|---|---|
| UI runtime | react + react-dom (~130 kB) |
built-in |
| Routing | react-router (~30 kB) |
built-in |
| i18n | react-i18next (~20 kB) |
built-in |
| SSR | Next.js (~100 kB+) | built-in |
| State | Redux / Zustand / Jotai | built-in |
| Total | 300 kB+ | 12.5 kB gzipped |
Karui ships everything you need in a single package. No plugin hunting. No compatibility issues. No version hell.
- Own JSX runtime — full React-compatible API (
useState,useEffect,useReducer,useMemo,useCallback,useRef,createContext,useContext) without React - File-based routing — drop a file in
pages/, get a route - Nested layouts —
pages/layout.tsx,pages/docs/layout.tsx, infinitely nestable - Route loaders —
export function loader(ctx)for server-side data fetching - SSR streaming —
renderWithRouterStream,createHtmlChunkStream,streamToNodeResponse - Islands architecture —
defineIsland+hydrateIslandsfor partial hydration - Three hydration modes —
full,none(pure HTML),islands - Error boundaries — global
pages/error.tsxand per-routeexport const errorBoundary - SPA navigation —
<Link href="/route" />with no full-page reloads - Legacy browser build — IIFE bundle targeting ES2015 for
<script>tag usage - Zero prod dependencies — nothing sneaks into your
node_modulesat runtime
npx @rigbyhost/karui create my-app
cd my-app
bun dev # or npm run dev / pnpm devCLI options:
npx @rigbyhost/karui create my-app --pm bun
npx @rigbyhost/karui create my-app --no-install
npx @rigbyhost/karui create my-app --forcemy-app/
├── src/
│ ├── pages/
│ │ ├── layout.tsx # root layout
│ │ ├── index.tsx # → /
│ │ ├── about.tsx # → /about
│ │ ├── 404.tsx # not found page
│ │ └── docs/
│ │ ├── layout.tsx # nested layout for /docs/*
│ │ └── [slug].tsx # → /docs/:slug
│ └── entry.tsx
├── package.json
└── vite.config.ts
// src/pages/about.tsx
export default function About() {
return <h1>About</h1>
}// src/pages/docs/[slug].tsx
export default function Doc({ params }: { params: { slug: string } }) {
return <h1>Doc: {params.slug}</h1>
}export async function loader(ctx) {
const data = await fetchSomething(ctx.params.slug)
return data
}
export default function Page({ data }) {
return <pre>{JSON.stringify(data, null, 2)}</pre>
}import { notFound } from '@rigbyhost/karui/router'
export async function loader(ctx) {
const post = await getPost(ctx.params.slug)
if (!post) notFound()
return post
}Configure in tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@rigbyhost/karui"
}
}Import hooks directly:
import { useState, useEffect, useRef } from '@rigbyhost/karui/jsx'
export default function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}Hydration mode is configured in src/app.tsx via defineCounterSite:
// src/app.tsx
export const site = defineCounterSite({
pages,
layout: Layout,
hydrateMode: 'full',
})// src/app.tsx
export const site = defineCounterSite({
pages,
layout: Layout,
hydrateMode: 'none',
})// src/app.tsx
export const site = defineCounterSite({
pages,
layout: Layout,
hydrateMode: 'islands',
})// src/components/Counter.tsx
import { defineIsland } from '@rigbyhost/karui/ssr'
import { useState } from '@rigbyhost/karui/jsx'
export const Counter = defineIsland(() => {
const [n, setN] = useState(0)
return <button onClick={() => setN(n + 1)}>{n}</button>
})// In your page — only Counter ships JS to the client
import { hydrateIslands } from '@rigbyhost/karui/ssr'
import { Counter } from '../components/Counter'
export default function Page() {
return (
<div>
<p>This is pure HTML — no JS sent to client</p>
<Counter /> {/* only this is hydrated */}
</div>
)
}import { renderWithRouterStream, streamToNodeResponse } from '@rigbyhost/karui/ssr'
// In your Node.js server handler:
const stream = await renderWithRouterStream(request, router)
streamToNodeResponse(stream, response)| Size | |
|---|---|
| Minified | 34.8 kB |
| Minified + gzipped | 12.5 kB |
| Download on slow 3G | 250 ms |
| Download on 4G | 14 ms |
git clone https://github.com/Rigby-Foundation/BetterHelperjs
cd BetterHelperjs
npm install
npm run check
npm run test
npm run build
npm run dev # starts the test SSR site from ./siteTest site pages live in site/src/pages/.
Security vulnerabilities should be reported via GitHub Security Advisories. Please do not open public issues for security bugs.
Original idea and architecture: newHelper-js by MIOBOMB.