Begin

Quick start

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

Ajo holding a small glowing lantern, lighting the way.

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

  1. Create a directory and a package.json for an ES module with two commands.

    Terminal
    mkdir hello-ajo && cd hello-ajo
    package.json
    {
      "name": "hello-ajo",
      "private": true,
      "type": "module",
      "scripts": {
        "dev": "kit dev",
        "build": "kit build"
      }
    }
  2. Ajo Kit depends on better-sqlite3, and Vite on esbuild; 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: true
  3. Install 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.3
  4. Register 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()],
    })
  5. 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"
      }
    }
  6. Give the application a document. Kit fills the three ssr: slots with the head, the route data and the rendered page; /src/client resolves 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 /.

src/layout.tsx
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.

src/handler.ts
export function page() {
  return { greeting: 'Hello from the server.' }
}

export function head() {
  return { title: 'Hello, Ajo' }
}
src/page.tsx
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.

src/counter.tsx
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 Counter

Run it

Terminal
pnpm dev

Kit 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.

Where to go next