Skip to content
brix.v2 stable

Search documentation

70 guides

Documentation menu
Docs / Pages and UI / HTML fragmentsRead Markdown ↗

Author fragments

Pair an ordinary Preact component with an auto-discovered HTML endpoint sidecar and model the finite inputs that can cross its transport boundary.

Use fragments for body content such as shared chrome, banners, or booking widgets whose cache lifetime differs from the page shell. Do not use them for head metadata or repeated list rows. Fragment transport is HTML-only; a page's semantic Markdown callback owns all non-HTML semantics.

Generate the component and sidecar

brix add fragment siteHeader --feature chrome

The sidecar filename ends in .fragment.ts, lives under src/features, and exports exactly one PascalCase runtime value ending in Fragment:

// SiteHeader.tsx
export interface SiteHeaderProps {
  transparent: boolean;
}

export function SiteHeader({ transparent }: SiteHeaderProps) {
  return <header data-transparent={transparent}>Shared header</header>;
}
// siteHeader.fragment.ts
import { createFragment } from "#brix/server";
import { z } from "brix/zod";
import { SiteHeader } from "./SiteHeader.tsx";

export const SiteHeaderFragment = createFragment({
  name: "site-header",
  component: SiteHeader,
  endpoint: {
    params: z.object({
      transparent: z.enum(["true", "false"]).transform((value) => value === "true"),
    }),
  },
  cache: "sharedChrome",
});

The route is registered by exact component identity. Do not wrap or copy the component in the sidecar, and do not register one component twice. Page JSX imports only SiteHeader; the fragment export is a route handle for discovery and tests.

Render a fragment name at most once in a composed page. Duplicate names are rejected because scoped hydration state is unique. A deferred fragment also cannot sit beneath a hydrated island: move it outside that island or pass it through a server-rendered slot. See islands.

Model endpoint inputs

endpoint.params selects component props to serialize into the fragment URL. endpoint.forwardPageQuery allowlists values copied from the live outer page URL. Both must be finite z.object schemas with URL-serializable scalar or array outputs, their keys cannot overlap, and every params key must exist in component props. Unknown endpoint query keys return 400.

Use load for request-only values or to combine forwarded page query into complete props:

// BookingWidget and BOOKING_API_URL are application-specific.
export const BookingWidgetFragment = createFragment({
  name: "booking-widget",
  component: BookingWidget,
  endpoint: {
    params: z.object({ hotelId: z.string() }),
    forwardPageQuery: z.object({ campaign: z.string().optional() }),
    load: async (c, { params, pageQuery }) => ({
      hotelId: params.hotelId,
      campaign: pageQuery.campaign,
      bookingApiUrl: c.env.BOOKING_API_URL,
    }),
  },
  cache: "bookingWidget",
});

load returns all component props; Brix does not merge partial endpoint data. Inline SSR does not call it because the page already supplied complete props. Without load, parsed params must be the complete endpoint props.

Endpoint auth, middleware, enabled, and cache behave like page-route options. Configure server infrastructure as described in server setup.

Continue with compose and test fragments.