mdx delivery
rule
- build one core MDX component bank; author content MDX-first.
- treat every LLM-produced MDX response as untrusted input until it parses and validates.
- one component vocabulary serves authored documents, generated content, and rendered product interfaces.
why
- model output is untrusted input: without one bank and a validation gate, a malformed response becomes executable application behavior instead of rejected content.
- one vocabulary means authored documents, LLM-generated content, and product interfaces share one rendering contract: a component's meaning is defined once, validated everywhere.
- invariant: nothing reaches the canonical store or renderer without parsing and validating against the declared schema.
candidate MDX from a model:with the bank: parse → validate components/props/frontmatter → store.invalid prop → one typed diagnostic → one repair pass.without the bank: renderer evaluates imports and expressions → a malformedresponse becomes live application behavior.
component bank
the renderer owns one central component map: Fumadocs primitives plus deliberately registered local components. individual MDX files do not import arbitrary React components.
| bank layer | responsibility |
|---|---|
| Fumadocs defaults | Callout, Cards, Card, enhanced code blocks, standard document rendering. |
| registered Fumadocs additions | components needed by the content model, e.g. Accordions, Accordion. |
| local semantic components | repeated project-specific information shapes: stable name, typed props, accessibility contract. |
| MDX parser and validator | frontmatter schema, component allowlist, props, links, AST shape, source locations. |
- create a local component only after a repeated semantic shape exists.
- name it for what it communicates, not how it looks:
Outcome,Invariant,AcceptanceEvidence— notBluePanel,InfoBox.
the component bank is an execution boundary
MDX may use only components the bank exposes. no arbitrary imports, JavaScript expressions, network access, or runtime evaluation from model output.
mdx-first response contract
- request valid MDX using the bank's documented components and required frontmatter; the answer is the candidate interface artifact. parsed content JSON is a derived view for search, indexing, rendering, automation.
- MDX beats a bespoke JSON document model for generation: the agent writes prose, hierarchy, tables, examples, and semantic blocks in one response. JSON only where a machine needs an extracted structured view — no flattening meaning into a rigid record before a human can read it.
- the prompt names allowed components, props, required sections, and the frontmatter schema. never ask for loose prose and recover semantics later with string rewriting.
validation and one repair pass
- receive candidate MDX as untrusted text.
- parse with an MDX-aware parser; reject syntax errors with line and column.
- validate frontmatter, allowed component names and props, links, block shape, domain schema.
- valid → store/render the canonical MDX, emit derived content JSON.
- invalid → send original MDX plus machine-readable diagnostics to one smaller repair agent.
- repair agent returns one complete replacement MDX document: declared outcome preserved, structure only corrected.
- parse and validate the replacement once. still failing → typed MDX-validation failure; invalid content stays out of the canonical store.
the repair agent has no authority to change requested meaning, invent facts, call tools, or silently fall back to plain text. one repair loop is a quality boundary, not an unbounded agent conversation.
machine-readable failure shape
stable diagnostic code, source location, parser or schema cause, invalid component or field, whether the single repair attempt was used. keep the original candidate only in protected diagnostics when it may contain private content.verification evidence
test the parser against valid and invalid component examples, inspect the derived content JSON, render a representative document, confirm the renderer rejects components outside the bank. record the result in the owning slice when this powers a user-facing capability.do not
- maintain separate component maps for humans, LLM output, and documentation.
- let LLM output execute arbitrary MDX expressions or imports.
- parse MDX with regex or repair it with unbounded retries.
- convert a failed MDX response to untyped plain text and present it as success.
- let the repair agent alter data authority, user intent, or accepted facts.
good and bad
bad
// insecure renderer - executes arbitrary MDXfunction renderMDX(mdxContent: string) {// evaluates imports and expressions directlyconst result = eval(mdxContent);return <MDXRenderer content={result} />;}// agent produces malformed contentconst maliciousMDX = `import { stealData } from './internal';<div>{stealData()}</div>`;
the renderer accepts model text with import statements and evaluates every expression: a malformed response becomes executable application behavior instead of rejected content.
good
// secure renderer - validates before parsingconst ComponentBank = {Callout: CalloutComponent,Accordions: AccordionsComponent,// only registered components allowed};function renderMDX(mdxContent: string) {// 1. parse with MDX-aware parserconst ast = parseMDX(mdxContent);// 2. validate frontmatter schemaconst frontmatter = FrontmatterSchema.parse(ast.frontmatter);// 3. validate component allowlistfor (const node of ast.children) {if (node.type === 'element' && !(node.name in ComponentBank)) {throw new ValidationError(`unknown component: ${node.name}`);}// validate props against component schemaComponentSchema.parse(node.props);}// 4. store only after validationreturn <MDXRenderer ast={ast} components={ComponentBank} />;}
the renderer parses candidate MDX, validates frontmatter and component allowlist, stores only after validation. an invalid <Callout> prop returns one typed diagnostic to the repair loop and never reaches rendering.