Pages
Forge pages are Svelte components selected by UI OpenAPI routes. The x-view path points to a page relative to client.componentDir,
and optional x-layout entries wrap that page from outside to inside.
# src/ui/openapi/pages/dashboard.yaml
modules:
dashboard:
basePath: /dashboard
x-layout:
- layout/app_layout.svelte
- layout/dashboard_layout.svelte
paths:
/:
get:
summary: Dashboard home
x-view: pages/dashboard/main.svelte
/reports/{id}:
get:
summary: Report detail
x-view: pages/dashboard/report.svelte<!-- src/ui/pages/dashboard/main.svelte -->
<script lang="ts">
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<h1>Dashboard</h1>
<button onclick={() => count++}>
Count: {count} ({doubled})
</button>Route Metadata
x-viewresolves the Svelte page.x-layoutwraps the page with one or more layout components.x-controllercan attach a page controller for richer behavior.- Path values are inferred from route segments like
/{id}and exposed to loaders and controllers.
Route Params and Query
Forge uses the frontend route definitions as the page router. A module basePath is prefixed to every path in that module, then route segments like /{projectId} become named params.
For /projects/42/tasks/7?tab=activity, Forge exposes params.projectId === "42", params.taskId === "7", and query.tab === "activity".
# src/ui/openapi/pages/projects.yaml
modules:
projects:
basePath: /projects
paths:
/:
get:
summary: Project list
x-view: pages/projects/list.svelte
/{projectId}:
get:
summary: Project detail
x-view: pages/projects/detail.svelte
x-controller: controllers/project_detail.controller.svelte.ts
/{projectId}/tasks/{taskId}:
get:
summary: Task detail
x-view: pages/projects/task.svelte// src/ui/pages/projects/detail.load.ts
export default async function load({ params, query }) {
const projectId = params.projectId;
const selectedTab = query.tab ?? "overview";
return {
projectId,
selectedTab
};
}import type { PageController, RouteContext } from "@noego/forge/client";
export default class ProjectDetailController implements PageController {
data = $state({
projectId: "",
selectedTab: "overview"
});
initialize(loadData: { selectedTab?: string }, route: RouteContext) {
this.data.projectId = route.params.projectId;
this.data.selectedTab = loadData.selectedTab ?? route.query.tab ?? "overview";
}
}Where to Read Params
- Use
load({ params, query })for data needed before render. - Use
initialize(loadData, route)in page controllers for behavior state. - Use the
pagestore in simple components that only need the current URL state.
Route Matching Rules
- Params are strings. Parse numbers or booleans at the boundary that needs them.
- Query values come from the URL search string, separate from path params.
- Do not add OpenAPI
parametersblocks for Forge page params; path segments define them.
<script lang="ts">
import { page } from "@noego/forge/client";
const projectId = $derived($page.params.projectId);
const tab = $derived($page.query.tab ?? "overview");
</script>
<h1>Project {projectId}</h1>
<p>Current tab: {tab}</p>Layouts
Layouts wrap your pages with common UI elements like headers, sidebars, and footers. Define layouts in your OpenAPI configuration and Forge will automatically nest them.
// ui/openapi/api.yaml - Define layouts in OpenAPI
paths:
/admin/users:
get:
summary: Admin users page
x-view: pages/admin/users.svelte
x-layout:
- layout/root_layout.svelte # Outermost
- layout/admin_layout.svelte # Nested inside root<!-- ui/layout/admin_layout.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
// Children are passed as a snippet
let { children }: { children: Snippet } = $props();
</script>
<div class="admin-container">
<aside class="sidebar">
<nav>
<a href="/admin/dashboard">Dashboard</a>
<a href="/admin/users">Users</a>
<a href="/admin/settings">Settings</a>
</nav>
</aside>
<main class="content">
<!-- Render nested content -->
{@render children()}
</main>
</div>Snippet Children Pattern
Layouts receive their nested content as a Svelte snippet. Use @render children() to render the child content at the appropriate location in your layout.