ajo

Application

Routes and server data

Connect pages, nested layouts, loaders and form actions.

A route follows its files

page.tsx renders a route. layout.tsx wraps a branch and its descendants. A colocated handler.ts owns server loaders, actions and API handlers; wares.ts applies middleware to a subtree.

Project structure
src/
  layout.tsx             # Shared layout
  page.tsx               # /
  hello/
    page.tsx             # /hello
    handler.ts           # Server data
  posts/[id]/page.tsx    # /posts/:id

Read durable data on the server

Export page() to supply args.data. A layout loader contributes shared data; parent() reads merged ancestor loader results. Components render the server result instead of maintaining a second copy.

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

// src/hello/page.tsx
import type { PageArgs } from 'ajo-kit'

export default ({ data }: PageArgs<{ greeting: string }>) => (
  <h1>{data?.greeting}</h1>
)

Give writes an action

Actions are POST requests on a route. Validate and authorize input on the server, then await the durable write. The action helper submits forms and exposes loading and errors.

A form component
import type { Stateful } from 'ajo'
import { action } from 'ajo-kit/client'

const Form: Stateful = function* () {
  const save = action<{ ok: boolean }>('save')
  while (true) yield (
    <form method="post" set:onsubmit={save.submit}>
      <label>Title <input name="title" required /></label>
      <button disabled={save.loading}>Save</button>
      {save.error && <p role="alert">{save.error.message}</p>}
    </form>
  )
}

Set page metadata

A head() loader supplies title, meta and link values. Kit merges ancestor and page metadata in the server response and on client navigation.

handler.ts
export function head() {
  return {
    title: 'Hello · My App',
    meta: [{ name: 'description', content: 'A little introduction.' }],
  }
}