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

# Contentful Rendering

Use rendering adapters when Contentful owns nested, polymorphic page composition. Adapters keep CMS fields at the integration boundary while providing visual components and semantic Markdown from the same entry.

## Create the adapter factory

Create one browser-safe factory from the generated type map:

```ts
import { createContentfulAdapterFactory } from "brix/contentful/rendering";
import type { Context } from "brix/hono";
import type { ContentTypeMap } from "#/contentful-types.ts";

export const createAdapter = createContentfulAdapterFactory<ContentTypeMap, Context>();

```

Only conventional `*.adapter.tsx` modules inspect Contentful `fields` and `sys`. Each exports exactly one PascalCase runtime value ending in `Adapter`, defined by a direct factory call with a literal `contentType`. Components receive ordinary application props.

```tsx
import { md } from "brix/markdown";
import { createAdapter } from "#/contentful-rendering.ts";
import { Hero } from "./Hero.tsx";

export const HeroAdapter = createAdapter({
  contentType: "hero",
  markdown: ({ entry }) => md.heading(1, md.text(entry.fields.heading)),
  component: ({ entry, preview }) => (
    <Hero heading={entry.fields.heading} headingAttributes={preview?.getProps("heading")} />
  ),
});

```

An adapter may also define extra typed props, a bounded server `load`, and standalone preview fallbacks. Keep top-level imports browser-safe and dynamically import server-only code inside `load`. Pass the supplied `AbortSignal` to downstream requests. Loader results used during island hydration are browser-visible, so never return credentials or private data.

## Render polymorphic entries

Vite generates the registry used by `renderEntry`:

```tsx
import { renderEntry } from "brix/contentful/rendering/registry";

export function ContentPage({ entry }: { entry: UnknownEntry }) {
  return <PageLayout>{renderEntry(entry)}</PageLayout>;
}

```

Entries must be returned in explicit page load props so Brix can prepare adapter loaders before synchronous SSR. Nested adapters can call the `renderEntry` supplied to their component. Pass an optional transport props record as the second argument for polymorphic children; directly render a known adapter when those props should be statically checked.

## Add semantic Markdown

An adapter's synchronous `markdown` callback returns safe nodes from `brix/markdown`, not a string, promise, or complete document. It can recursively project child entries with its supplied `renderEntry`. Use `markdown: false` only for intentionally visual-only nested content; omitting required projections fails explicitly.

Represent the root in a page route and add the single document wrapper there:

```ts
import { markdownEntry } from "brix/contentful/rendering/registry";
import { md } from "brix/markdown";

export const articlePageRoute = createPageRoute(articlePageContract, {
  component: ArticlePage,
  load: async (c, { params }) => ({ entry: await getArticle(c, params.slug) }),
  markdown: async (c, { entry }) => md.document({ body: await markdownEntry(entry, c) }),
});

```

Preparation and projection have bounded depth, node counts, concurrency, passes, and time. Loader failures remain typed loaded states so both visual and semantic output can provide truthful fallback content.

## Project Contentful Rich Text

Keep visual Rich Text rendering in your application component. For agent-readable output, project the structured document directly rather than converting rendered HTML:

```tsx
import { projectContentfulRichText } from "brix/contentful/rendering/markdown";

export const ArticleAdapter = createAdapter({
  contentType: "article",
  markdown: ({ entry, renderEntry }) =>
    projectContentfulRichText(entry.fields.body, { renderEntry }),
  component: ({ entry }) => <Article body={entry.fields.body} />,
});

```

The projector supports paragraphs, headings, lists, blockquotes, separators, GFM tables, text marks, safe hyperlinks, assets, and embedded entries. Embedded entries use the adapter registry. Supply `getEntryUrl` for entry hyperlinks; without it, readable labels remain but Brix invents no destination. Unsafe links and unsupported or malformed structures fail safely and explicitly.
