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.
src/
layout.tsx # Shared layout
page.tsx # /
hello/
page.tsx # /hello
handler.ts # Server data
posts/[id]/page.tsx # /posts/:idRead 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.
// 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.
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.
export function head() {
return {
title: 'Hello · My App',
meta: [{ name: 'description', content: 'A little introduction.' }],
}
}