Routing
Axonyx routes are file-first for pages and explicit for backend-style API blocks. This keeps the app readable without requiring a large JS router.
Page routes
A `page.asx` file maps to a route. The root page is `app/page.asx`. Nested folders create nested routes.
app/page.asx -> /
app/docs/page.asx -> /docs
app/docs/routing/page.asx -> /docs/routing
app/components/button/page.asx -> /components/buttonDynamic route params
Bracket folders define params. Use loaders to read the param and fetch the matching data.
app/docs/content/[slug]/page.asx -> /docs/content/:slugRoute context
Pages, layouts, and imported shell components can read the current route during server render. This keeps active navigation out of client JavaScript.
page DocsShell() {
return ASX {
<nav>
<a href="/docs" aria-current={route.section == "docs"}>Docs</a>
<a href="/roadmap" aria-current={route.path == "/roadmap"}>Roadmap</a>
</nav>
}
}
// route.path -> /docs/getting-started
// route.section -> docs
// route.subsection -> getting-started
// route.params -> dynamic route paramsLoad data for a route
Query loaders belong beside the page and return data for render. Keep database reads here instead of hiding them in page markup.
// app/posts/loader.ax
export type Post {
title: String
summary?: String
}
query loadPosts(status: String = "published") -> Post[] {
data posts = db.posts.where({ status: input.status }).order({ created_at: "desc" }).limit(6).all()
return posts
}
// app/posts/page.asx
page Posts() {
data status: String = "published"
data posts = loadPosts(status)
return ASX {
<Each items={posts} as="post">
<Card title={post.title}>
<Copy>{post?.summary}</Copy>
</Card>
</Each>
}
}Reusable ASX components
Components can own params, local state, browser behavior, and an explicit `render ASX` boundary. The compiler emits component JS only on routes that actually use the component.
// app/components/theme-switch.asx
component ThemeSwitch(label: String = "Theme") {
state selected: String = "silver"
client JS {
window.__themeSwitchReady = true;
}
render ASX {
<label class="ax-field">
<span>{label}</span>
<select bind:value={selected} data-ax-behavior="theme">
<option value="silver">Silver</option>
<option value="bronze">Bronze</option>
<option value="gold">Gold</option>
</select>
</label>
}
}
// app/settings/page.asx
import { ThemeSwitch } from "@/components/theme-switch.asx"
page Settings() {
return ASX {
<Card title="Appearance">
<ThemeSwitch label="Site theme" />
</Card>
}
}Mutate with actions
Actions live beside the route too. They validate input, perform the mutation, then revalidate the page or query key that should refresh.
// app/posts/actions.ax
action CreatePost(title: string, excerpt: string) {
db.posts.insert({ title: input.title, excerpt: input.excerpt, status: "published" })
revalidate("/posts")
return ok()
}
// app/posts/page.asx
<form method="post" action={action CreatePost} className="ax-form">
<input name="title" className="ax-input" />
<textarea name="excerpt" className="ax-textarea"></textarea>
<Button type="submit">Add story</Button>
</form>API routes
Backend-style routes can declare a method, path, typed input, and return shape. This is the base for forms, CMS actions, and tooling.
route POST "/api/posts" {
input {
title: String
summary?: String = ""
featured?: Bool = false
}
return json(input.title)
}Inspect routes
Use the CLI to see what the app exposes before wiring UI or deploying.
cargo ax routes
cargo ax routes --format json
cargo ax api
cargo ax api --schemaCurrent rules
Pages stay readable
Use `.asx` pages for structure and UI composition, not heavy business logic.
Loaders fetch data
Use loaders for route-local data such as content collections or later database reads.
Actions mutate data
Use actions and typed route input for form-like operations and backend changes.
CLI exposes contracts
`routes`, `api`, and `actions` make app structure visible to humans and AI tools.