camelon
A server-rendered hypermedia framework. A web-standard Request → Response core, @kitajs/html for JSX, and Datastar for client reactivity. No client framework. File-based routes. Your TypeScript types are the runtime contract.
It starts with plain HTML from the server — no client JavaScript at all:
export default function Hello() {
return (
<div class="d-card">
<strong>Hello from the service worker</strong>
<p>This HTML was rendered by camelon inside your browser — no server.</p>
</div>
);
}Add Datastar and it becomes interactive when you want it. The counter below is a page, two resource routes, and an SSE patch — running for real, right here:
export default function Counter() {
return (
<div class="d-row" signals='{"count": 0}'>
<button class="d-btn" on:click="@post('/counter/decrement')">−</button>
<output class="d-num" text="$count">0</output>
<button class="d-btn" on:click="@post('/counter/increment')">+</button>
</div>
);
}import type { RouteArgs } from 'camelon';
export async function action({ stream, signals }: RouteArgs) {
const { count = 0 } = signals as { count?: number };
stream.patchSignals({ count: count + 1 });
}import type { RouteArgs } from 'camelon';
export async function action({ stream, signals }: RouteArgs) {
const { count = 0 } = signals as { count?: number };
stream.patchSignals({ count: count - 1 });
}New here? Start with Install & bootstrap.
Hypermedia, not SPA
The server renders HTML. Datastar patches it over SSE. The browser never downloads a component runtime.
One core, many runtimes
The HTTP core is web-standard, so the same app runs on Node, Deno, Bun, Workers, or a browser service worker. See runtime targets.
Types are the contract
Declare LoaderInput and LoaderOutput once. They drive the typed args, runtime validation, and the OpenAPI spec. See typed contracts.