Begin
Quick start
Create an Ajo Kit application, render a page from server data and run it locally.

This guide builds a small Ajo Kit application: a layout, a page rendered from server data, and a component that keeps its own state in the browser. You need Node 22.18 or newer and pnpm. Node is only the development host; production runs on Ajo Engine.
Create the project
Create a directory and a
package.jsonfor an ES module with two commands.Terminal mkdir hello-ajo && cd hello-ajopackage.json { "name": "hello-ajo", "private": true, "type": "module", "scripts": { "dev": "kit dev", "build": "kit build" } }Ajo Kit depends on
better-sqlite3, and Vite onesbuild; both run install scripts, which recent pnpm versions only run when allowed. The official starter allows exactly these two:pnpm-workspace.yaml allowBuilds: better-sqlite3: true esbuild: trueInstall the UI library, the framework and the build tools.
Terminal pnpm add ajo@0.1.35 ajo-kit@0.3.2 pnpm add -D vite@8.0.16 typescript@6.0.3 @types/node@25.9.3Register the Kit plugin with Vite. The client entry imports the generated route graph, so it stays in Vite’s plugin pipeline instead of the dependency optimizer.
vite.config.ts import { defineConfig } from 'vite' import { kit } from 'ajo-kit/vite' export default defineConfig({ optimizeDeps: { exclude: ['ajo-kit/client'] }, plugins: [...kit()], })Tell TypeScript and Vite that JSX compiles to Ajo.
tsconfig.json { "compilerOptions": { "target": "ESNext", "lib": ["ESNext", "DOM"], "types": ["node", "vite/client"], "module": "ESNext", "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "noEmit": true, "strict": true, "jsx": "react-jsx", "jsxImportSource": "ajo" } }Give the application a document. Kit fills the three
ssr:slots with the head, the route data and the rendered page;/src/clientresolves to Kit’s client entry.index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- ssr:head --> </head> <body> <!-- ssr:data --> <div id="root"><!-- ssr:root --></div> <script src="/src/client" type="module"></script> </body> </html>
Render the first route
Files under src become routes. A layout.tsx wraps every page below it, and src/page.tsx is the page for /.
import type { LayoutArgs } from 'ajo-kit'
export default ({ children }: LayoutArgs) => (
<main>{children}</main>
)Next to a page, a handler.ts runs on the server. Its page() loader returns the data the page receives as data, and head() sets the document title.
export function page() {
return { greeting: 'Hello from the server.' }
}
export function head() {
return { title: 'Hello, Ajo' }
}import type { PageArgs } from 'ajo-kit'
import Counter from './counter'
export default ({ data }: PageArgs<{ greeting: string }>) => (
<>
<h1>{data?.greeting}</h1>
<Counter />
</>
)Add a stateful component
A stateful component is a generator function. Variables declared before its loop live as long as the component; this.next() runs the update you pass it and renders again. It is not a page, so its file name is up to you.
import type { Stateful } from 'ajo'
const Counter: Stateful = function* () {
let count = 0
while (true) yield (
<button set:onclick={() => this.next(() => count++)}>
Clicked {count} times
</button>
)
}
export default CounterRun it
pnpm devKit starts a Vite development server on http://localhost:5173. The page is rendered on the server first, then the client takes over the same DOM and the button starts counting. Edit a page or layout and it updates in place.
pnpm build prepares the production inputs in .ajo/: the server module graph, client assets and the compiler descriptor. Sealing and running them is covered in Seal and run.