Navigation

Custom Components

One of the most powerful features of MDX is the ability to interleave interactive React components within your Markdown content. Omni-MDX makes this process incredibly efficient by parsing the component tree in Rust and rendering it seamlessly via React Server Components (RSC) or Client Components.

The Component Registry

When the Rust core parses a JSX tag (e.g., <Note type="warning">), it doesnt render HTML. Instead, it adds a NODE_ELEMENT to the AST with the tag name "Note" and its associated attributes.

To render this node, you pass a Component Registry (a standard JavaScript object) to your renderer. The keys in this object must exactly match the JSX tags used in your MDX source.

1. Creating your React Components

First, build your components exactly as you would in any standard Next.js application.

Here is an example of a pure Server Component (Note.tsx):

tsx
// components/Note.tsx
export function Note({ type, children }: { type: 'info' | 'warning', children: React.ReactNode }) {
  const bg = type === 'warning' ? 'bg-amber-500/10 border-amber-500/20' : 'bg-blue-500/10 border-blue-500/20';
  
  return (
    
{children}
); }

Here is an example of an interactive Client Component (Chart.tsx):

tsx
// components/Chart.tsx
"use client";

import { useState } from 'react';

export function Chart({ data }: { data: any[] }) {
  const [active, setActive] = useState(false);
  return 
setActive(!active)}>Interactive Chart...
; }

2. Mapping the Components

Next, import these components and pass them to either MDXServerRenderer or MDXClientRenderer using the components prop.

tsx
// app/blog/[slug]/page.tsx
import { parseMdx, MDXServerRenderer } from "@toaq-oss/omni-mdx/server";
import { Note }  from "@/components/Note";
import { Chart } from "@/components/Chart";

// The keys here define what tags are valid in your MDX
const MDX_COMPONENTS = {
  Note,
  Chart
};

export default async function Page({ params }) {
  const content = await getArticleContent(params.slug);
  const ast = await parseMdx(content);

  return ;
}

Server vs. Client Components

Omni-MDX embraces the Next.js App Router paradigm fully:

  • Server Components (Default): Components like Note or Details will be rendered entirely on the server. Zero JavaScript is sent to the client. This is the ultimate performance optimization for content-heavy sites.
  • Client Components: If a component requires state, hooks, or browser APIs (like the Chart component with "use client" inside), Next.js will automatically hydrate only that specific component on the frontend.
ℹ️ Information
Clean Separation: By keeping the parse step in Rust and the render step in React, you avoid the common pitfall of shipping a heavy markdown compiler bundle to your users.

Error Boundaries

When using the MDXClientRenderer for live previews or purely client-side applications, Omni-MDX automatically wraps every custom component in an MDXErrorBoundary.

If your custom <Chart /> component throws a JavaScript error due to bad data, the error is isolated. The chart will display a fallback, but the rest of the Markdown document will continue to render normally.

You can also use this boundary explicitly around components that might fail:

tsx
import { MDXErrorBoundary } from "@toaq-oss/omni-mdx/client";