Use it in a React app
Serve a rendered record from a Next.js or React Router route, set one section inside a React page, or attach one on the client.
An app keeps its own React, routes and styles. mdxui adds a page or one section as HTML: renderPage returns a whole document, and renderBlock returns one section. Every snippet below is a part of react-app.ts, which pnpm check runs against Harbor tides' records.
Prerequisites
- An app on React 19, Next.js App Router or React Router, with
@mdxui/site@7.0.0-alpha.0installed as in the Quickstart. - Records in
pages/, such as the Harbor tides records.
Serve a whole page from a route
A rendered record is a complete document, so a route answers with it as text/html. In Next.js, export it as GET from app/pricing/route.ts; in React Router, export it as a resource route's loader.
import { readFile } from 'node:fs/promises';
import { PageSchema, renderPage } from '@mdxui/site';
/**
* A route handler that answers with a rendered record. It is a plain
* Request-to-Response function, so it drops into a Next.js App Router
* `app/pricing/route.ts` as `GET`, a React Router resource route as `loader`,
* or a Worker's `fetch`.
*/
export async function GET(): Promise<Response> {
const record = JSON.parse(await readFile('pages/pricing.json', 'utf8'));
const html = renderPage(PageSchema.parse(record));
return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8' } });
}The page keeps its own Theme and stylesheet; nothing from the app's CSS reaches it.
Set one section inside a React page
A server component renders one section into a declarative shadow root. The section's styles stay inside the root, and the app's styles stay out. No script runs.
import { createElement } from 'react';
import { renderToString } from 'react-dom/server';
import { BlockSchema, baseCss, blocksCss, context, primitivesCss, renderBlock } from '@mdxui/site';
import { THEMES, type ThemeName } from '@mdxui/themes';
/**
* One section inside a React page, as a server component: a declarative
* shadow root keeps the section's styles in and the app's styles out, and
* needs no script. In JSX: <Section block={block} theme="quaypoint" />.
*/
export function Section({ block, theme }: { block: unknown; theme: ThemeName }) {
const { theme: t, css } = THEMES[theme];
const html = renderBlock(BlockSchema.parse(block), context(t, 'paper', t.icons));
const styles = `<style>${css().replaceAll(':root', ':host')}${baseCss}${primitivesCss}${blocksCss}</style>`;
return createElement(
'div',
{ className: 'mdxui-section' },
createElement('template', { shadowrootmode: 'open', dangerouslySetInnerHTML: { __html: `${styles}${html}` } }),
);
}Use it as <Section block={block} theme="quaypoint" />. The block is any section's block from a record; BlockSchema.parse refuses one past its bounds at its path.
Attach a section on the client
A client component that receives the section's HTML as a string attaches the shadow root itself, after hydration:
/**
* In a client component, after hydration: attach the shadow root yourself,
* from HTML your server rendered with renderBlock and sent as a string.
*/
export function attachSection(host: HTMLElement, html: string): void {
const root = host.shadowRoot ?? host.attachShadow({ mode: 'open' });
root.innerHTML = html;
}Render the string on the server with renderBlock, as in the server component, and send it to the client as a prop.