camelon

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>
  );
}
service worker

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 });
}
service worker

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.