Routes and navigation
Every file under routes/ is a page: routes/index.tsx is /, routes/experiences/[id].tsx is /experiences/:id, and routes/_layout.tsx wraps the routes below it. A route exports its meta (who may open it, the row it is about, its title and SEO) and a default React component.
On this page
Route meta is plain data the platform reads when it checks the project: it decides who may open the page (public, or a list of roles), server-renders public pages with what a signed-out visitor may read, writes the head from the row and lists the page in the sitemap.
Navigation is client-side: Link and useNavigate move without a page load, and NavLink hides a link the signed-in user's role may not open.
Route meta
defineRoutefunction
Declares a route's meta: public (signed-out visitors may open it; it is server-rendered and in the sitemap), roles (who may open it; default any signed-in role), title, row (the row the page is about, found by the route's last parameter) and seo (title, description, image, JSON-LD and sitemap filter, as templates over the row).
defineRoute(meta: RouteMeta): RouteMetaParameters
| Name | Type | Description |
|---|---|---|
metarequired | RouteMeta | Plain data: no functions, no values computed at runtime. |
- Returns
- The same object (typed).
- Errors
- Does not throw.
- Permissions
- A visitor who may not open the route gets the sign-in screen; a public route whose
rowa guest can't read answers 404.
// routes/experiences/[id].tsx
import { defineRoute, useRow } from "@appbricx/ui";
export const meta = defineRoute({
public: true,
row: { table: "experience" }, // bound by :id
seo: {
title: "{{title}} · Teamday",
description: "{{title}} in {{city}}",
sitemap: { where: { status: "published" } },
},
});
export default function Experience() {
const { row, notFound } = useRow("experience");
if (notFound) return <main>No such experience.</main>;
if (!row) return <main>Loading…</main>;
return <h1>{row.title}</h1>;
}Source: internal/kernel/appsdk/ui/meta.ts:32
Links and navigation
<Link>component
An in-app link: a normal <a href> that moves without a page load (a modified click, target or another site falls back to the browser).
Link({ to, replace, onClick, ...rest }: LinkProps): ElementParameters
| Name | Type | Description |
|---|---|---|
{ to, replace, onClick, ...rest }required | LinkProps |
- Returns
Element- Errors
- Does not throw.
- Permissions
- None: whether the target opens is decided by its route's meta.
import { Link } from "@appbricx/ui";
<Link to={"/experiences/" + row.id}>{row.title}</Link>Source: internal/kernel/appsdk/ui/router.tsx:226
useParamsfunction
The current route's parameters, named after the brackets in the file name: routes/[city]/experiences/[slug].tsx gives { city, slug }.
useParams<P extends Record<string, string> = Record<string, string>>(): P- Returns
P- Errors
- Does not throw.
- Permissions
- None. Parameters come from the URL: treat them as untrusted input.
const { slug } = useParams<{ slug: string }>();
const { rows } = useQuery("experience", { where: { slug }, limit: 1 });Source: internal/kernel/appsdk/ui/router.tsx:197
useSearchParamsfunction
The query string as URLSearchParams.
useSearchParams(): URLSearchParams- Returns
URLSearchParams- Errors
- Does not throw.
- Permissions
- None. Values are untrusted input.
const q = useSearchParams().get("q") ?? "";Source: internal/kernel/appsdk/ui/router.tsx:207
useRoutefunction
The current route: path, search, params and its meta.
useRoute(): RouteState- Returns
RouteState- Errors
- Does not throw.
- Permissions
- None.
const { path, meta } = useRoute();
document.title = meta.title ?? path;Source: internal/kernel/appsdk/ui/router.tsx:202
useLocationfunction
The current { path, search }, re-rendering on every navigation (also outside a route, in a layout).
useLocation(): Location- Returns
Location- Errors
- Does not throw.
- Permissions
- None.
const { path } = useLocation();
const active = path.startsWith("/bookings");Source: internal/kernel/appsdk/ui/router.tsx:184
useCanOpenfunction
Whether the signed-in user may open a path: a public route, a route their role is in, or any route for a signed-out visitor (who may follow it to sign in).
useCanOpen(path: string): booleanParameters
| Name | Type | Description |
|---|---|---|
pathrequired | string |
- Returns
boolean- Errors
- Does not throw.
- Permissions
- A hint for the UI; the route's meta is what the server enforces.
const canOps = useCanOpen("/ops");
return canOps ? <OpsShortcuts /> : null;Source: internal/kernel/appsdk/ui/app.tsx:454
Errors
<ErrorBoundary>component
Catches a render error below it so the rest of the page keeps working; fallback draws the message, resetKey clears it when it changes. Every route and layout is already wrapped in one, so a broken page never takes the navigation with it.
<ErrorBoundary {...{ children?: ReactNode; resetKey?: string; fallback?: (e: Error) => ReactNode }} />- Returns
ReactNode- Errors
- Does not throw.
- Permissions
- None.
<ErrorBoundary resetKey={id} fallback={(e) => <p role="alert">Couldn't show the chart: {e.message}</p>}>
<BookingsChart id={id} />
</ErrorBoundary>Source: internal/kernel/appsdk/ui/app.tsx:485
Types
interface RouteMeta {
/** Open to signed-out visitors (server-rendered, in the sitemap). */
public?: boolean;
/** The roles that may open it; default: any signed-in role. */
roles?: string[];
/** The page's title (also the browser tab's). */
title?: string;
/**
* The row this page is about: { table, by } binds the route's last parameter (or `param`) to the
* column `by` (default "id"), which finds the row. `params` binds the route's other parameters, so
* the sitemap can list a URL per row: a column of the row, or "<ref column>.<column>" through a
* reference — `/[city]/experiences/[slug]`: `{ table: "experience", by: "slug", params: { city: "city.slug" } }`
* (the row's `city` column references city; the URL shows that city's `slug`).
*/
row?: { table: string; by?: string; param?: string; params?: Record<string, string> };
/** The head, as templates over the row's fields: "{{title}} · Teamday". */
seo?: {
title?: string;
description?: string;
image?: string;
/** Structured data: one object or several (a Product and its BreadcrumbList). */
jsonld?: Record<string, unknown> | Record<string, unknown>[];
noindex?: boolean;
/** Which rows the sitemap lists: { where: { status: "published" } }. */
sitemap?: { where?: Record<string, string | number | boolean> };
};
}Source: internal/kernel/appsdk/ui/meta.ts:4
interface RouteState {
path: string;
search: string;
params: Record<string, string>;
meta: RouteMeta;
}Source: internal/kernel/appsdk/ui/router.tsx:175
interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
to: string;
replace?: boolean;
}Source: internal/kernel/appsdk/ui/router.tsx:220