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

# Author semantic Markdown pages

Brix can expose an authored semantic Markdown representation alongside an SSR HTML page. Opt in page
by page and project the same successful loader data into intentionally different renderers.

This is not the website's `markdown-it` prose parser. `markdown-it` parses trusted documentation
files such as this one for display. Application representations use the closed `brix/markdown`
builder so CMS and user text cannot become active Markdown or raw HTML.

## Export project-bound factories

Keep contracts browser-safe and route bindings server-only:

```ts
// src/brix.ts
export const { createMarkdownIndex, createPageContract, getPath } = brix;

// src/brix.server.ts
export const { createMarkdownIndexRoute, createPageRoute } = brixServer;

```

See [pages](/docs/pages) for the complete contract, loader, component, and route workflow.

## Add a page representation

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

export const articlePageContract = createPageContract({
  path: "/articles/:slug",
  representations: ["markdown"],
});

```

```ts
// route.ts
import { md } from "brix/markdown";
import { createPageRoute } from "#brix/server";
import { ArticlePage } from "./Page.tsx";
import { articlePageContract } from "./contract.ts";

// getArticle is an application-specific data dependency.
export const articlePageRoute = createPageRoute(articlePageContract, {
  component: ArticlePage,
  load: async (c, { params }) => ({ article: await getArticle(c, params.slug) }),
  markdown: (_c, { article }) =>
    md.document({
      body: [md.heading(1, md.text(article.title)), md.paragraph(md.text(article.summary))],
    }),
});

```

The `markdown` callback is required when the contract declares Markdown and forbidden otherwise. It
receives a readonly view of successful loader props and returns a `MarkdownDocument`, synchronously or
as a promise. Keep redirects, not-found responses, and other early `Response` values in `load`.

Brix invokes exactly one renderer. HTML runs Preact, [styling](/docs/styling), islands, fragments, and
hydration preparation. Markdown runs the callback and deterministic serializer without Preact or a
browser runtime. The callback must explicitly include semantics for content that HTML obtains from a
[fragment](/docs/fragments); Markdown never fetches fragment endpoints.

## Build safe documents

Use `md.text` for every CMS or user-provided string. Builders are immutable branded values, not plain
objects or raw strings:

```ts
const content = md.fragment(
  md.paragraph(md.strong(md.text("Status: ")), md.text(status)),
  md.list(items.map((item) => md.listItem(md.paragraph(md.link(item.url, md.text(item.title)))))),
  md.image(image.url, image.alt),
  md.codeBlock(example, { language: "ts" }),
);

```

The API covers text, paragraphs, headings, emphasis, strong text, strikethrough, inline code, hard
breaks, links, images, blockquotes, code blocks, ordered and unordered lists, tables, thematic breaks,
fragments, documents, and index documents. It validates structure, URL schemes, depth, node count,
and output size, and emits CommonMark plus GFM tables and strikethrough without raw HTML.

Do not cast strings or objects to Markdown types. Extract projection functions returning
`MarkdownContent` or `MarkdownFragment` when multiple routes share semantic pieces.

## Negotiate and link durable URLs

The canonical page URL defaults to HTML and selects Markdown for `Accept: text/markdown`. An
unacceptable canonical request receives 406. A durable `.md` alias always selects Markdown regardless
of `Accept`; the root alias is `/index.md`.

Generate both forms with `getPath` rather than appending a suffix:

```ts
const htmlUrl = getPath(articlePageContract, { params: { slug: "example" } });
const markdownUrl = getPath(articlePageContract, {
  params: { slug: "example" },
  representation: "markdown",
});

```

Brix supplies `Content-Type`, `Vary`, `Content-Location`, and reciprocal alternate links. Pages that
do not declare a representation stay HTML-only and retain their previous Accept behavior.

## Localize the projection

In a translation-enabled app, call the project-bound server `getTranslator(c)` inside `markdown`.
Do not use `useTranslate`, which is a Preact hook. Loader and Markdown calls share request-local
dictionary preparation. Localized canonical and durable URLs resolve the same locale and return
`Content-Language`; generate paths with the localized `getPath(contract, locale, options)` overload.

Localized index callbacks receive `{ locale }` and can await the same translator for titles,
summaries, sections, and links. See [translations](/docs/translations) for locale configuration and
test fixtures.

Continue with [publish and operate semantic markdown](/docs/markdown-indexes).
