> [Agent navigation index](/llms.txt)

# Pages

Brix pages pair a browser-safe contract with a Preact component and a server route, giving SSR and URL construction one typed source of truth.

## File layout

```text
src/features/catalog/pages/productDetails/
  contract.ts
  Page.tsx
  route.ts
  route.test.ts

```

The conventional `route.ts` is discovered automatically. It must export one runtime value named `*PageRoute`.

## Define the contract

Contracts define paths and optional query validation:

```ts
import { createPageContract } from "#brix";
import { z } from "brix/zod";

export const productDetailsPageContract = createPageContract({
  path: "/products/:productId",
  query: z.object({ preview: z.string().optional() }),
});

```

Path parameters are inferred from `:productId`. Invalid query input receives a 400 response before the loader runs.

### Portable route paths

Page and API routes use the same portable Hono path grammar so origin and edge matching agree. Named parameters can use constraints such as `/products/:productId{[0-9]+}`. Optional parameters must be terminal. Constraints cannot contain anchors, alternation, lookarounds, or patterns that match an empty string; a constraint that can span slashes must be terminal. Unsupported patterns are rejected during route registration rather than interpreted differently by different routers.

Use query or body validation for data rules that do not belong in the URL structure. Use `getPath` for URL construction rather than assembling constrained paths manually.

## Write the component

The page module exports exactly one named runtime component ending in `Page`:

```tsx
type ProductDetailsPageProps = {
  product: { id: string; name: string };
};

export function ProductDetailsPage({ product }: ProductDetailsPageProps) {
  return (
    <main>
      <h1>{product.name}</h1>
      <p>Product ID: {product.id}</p>
    </main>
  );
}

```

Type-only exports are allowed. Do not use a default export or add unrelated runtime exports.

## Create the route

The loader receives parsed params and query and returns component props or an early `Response`:

```ts
import { createPageRoute } from "#brix/server";
import { ProductDetailsPage } from "./Page.tsx";
import { productDetailsPageContract } from "./contract.ts";

const products = new Map([["sku-123", { id: "sku-123", name: "Everyday backpack" }]]);

export const productDetailsPageRoute = createPageRoute(productDetailsPageContract, {
  component: ProductDetailsPage,
  load: (c, { params }) => {
    const product = products.get(params.productId);
    if (!product) return c.notFound();
    return { product };
  },
});

```

Middleware order is authentication, route middleware, loader, then render. Set `auth: true` only after configuring authentication in `defineBrixServer`.

## Build links safely

Use `getPath`; do not concatenate path parameters manually:

```ts
import { getPath } from "#brix";
import { productDetailsPageContract } from "./contract.ts";

const href = getPath(productDetailsPageContract, {
  params: { productId: "sku 123" },
  query: { preview: "true" },
});

```

Brix encodes path values and serializes query values; incoming requests are validated before the loader runs. Localized contracts take an exact locale key before the options object.

## Metadata and alternate representations

Render `Head` from `brix/islands/server` in the server component to contribute document
metadata without maintaining a second HTML template:

```tsx
import { Head } from "brix/islands/server";

export function ProductDetailsPage({ product }: ProductDetailsPageProps) {
  return (
    <>
      <Head>
        <title>{product.name}</title>
        <meta name="description" content={`Details for ${product.name}`} />
      </Head>
      <main>
        <h1>{product.name}</h1>
      </main>
    </>
  );
}

```

The example replaces the earlier component and uses its props type. Keep metadata on
the server; do not hydrate a component just to set its title.

For locale-keyed paths and dictionary loading, continue with [translations](/docs/translations).
For an explicit semantic projection, add the contract's Markdown representation and the
route's matching callback as shown in [agent-readable Markdown](/docs/markdown).
Route cache policies are edge hints, not a response store; see [fragments](/docs/fragments).

## Test and run

```sh
brix add page productDetails --feature catalog
brix test src/features/catalog/pages/productDetails/route.test.ts
brix dev

```

See [testing](/docs/testing) for route-harness examples and [islands](/docs/islands) for adding narrowly scoped interaction to SSR pages.
