Why We Built Draftbase Around MDX
Draftbase stores content as plain MDX, not a JSON tree. Here's why we made that call, and the real cost it carries.

Draftbase stores content as plain MDX, not a JSON rich-text tree, because it lets components render directly in content without a plugin system. The cost: a compile step that non-technical editors feel.
Draftbase stores content as plain MDX strings, not a JSON node tree. We chose that on purpose. It costs us something real: a compile step between an editor saving a field and a page rendering it. This post is about why we took that cost anyway.
The Alternative We Rejected
Most headless CMS platforms store rich text as a JSON document. Contentful uses its own rich text tree. Sanity uses Portable Text. Both work. Both also mean every client needs a renderer that walks the tree. It has to map each node type to markup. Add a new node type, like a callout box or an embed. Every renderer needs an update, not just the CMS.
MDX sidesteps that. It's Markdown with JSX embedded in it, compiled straight to a React component. A content field isn't a tree a client has to interpret. It's source code a client compiles and runs. Our renderer package (packages/renderer) does that compile step. It uses @mdx-js/mdx, remark-gfm for tables and strikethrough, and rehype-slug for heading anchors. Not next-mdx-remote. We evaluate MDX directly against a caller-supplied JSX runtime. That's what let us ship one shared compileMDXCore function for React, React Native, and Vue. Not three separate renderers.
What Components in Content Actually Buys You
The real reason to want MDX over JSON isn't the format, it's what the format allows. A JSON rich text tree can represent a paragraph, a heading, a list. It can't represent "render our pricing table here, with this plan highlighted." A CMS that only stores structured text pushes every layout call into the frontend template. MDX lets an editor drop <PricingTable highlight="growth" /> directly into the body and have it render.
We didn't want a plugin system to get there. A plugin system means a marketplace, a review process, a versioning story for third-party code running inside your CMS. Instead, the components prop on MDXContent is the entire mechanism. An app defines the components it wants to expose and passes them in. That's the complete list of what CMS-authored content can render. Nothing else is reachable. evaluate() runs without useDynamicImport, so an import statement inside a content field throws instead of silently reaching for a module. That's not a limitation we're apologizing for. It's the security boundary, and it's the boundary we picked over building a sandboxed plugin runtime.
The Cost We Don't Hide
Here's the part where the rejected option still wins for some teams. A JSON tree is safe to render with zero build step: send the tree to a client, walk it, done. MDX has to compile, and evaluate() can fail on malformed syntax. Our renderer fails soft. A broken content field renders as plain text inside an error boundary instead of crashing the page. But a marketing editor who breaks a JSX tag sees garbled output, not a friendly warning as they type. A structured rich-text editor UI can prevent that class of mistake at the input level. A raw MDX textarea can't. Not without a real MDX-aware editor on top of it, which we haven't built yet.
That tradeoff is real, and it's why MDX isn't automatically the right call for every CMS. A team of non-technical editors writing plain paragraphs gets nothing from JSX-in-content. They still pay the full compile-step tax, for a feature they'll never touch. We took that bet. Our own users lean technical, and the ones who don't still write more Markdown than JSX.
Where This Leaves Storage
The field type is still called richText in our schema, mostly for familiarity. What it actually stores is a raw string. That's no different from a text field, except in how the renderer treats it. There's no migration path baked in for someone who wants JSON later. If you want a headless CMS built around MDX from the ground up, that's the whole pitch. If you want a rich text tree with a WYSIWYG editor and zero compile step, that's a real, valid choice. It's just not the one we made.
Ship content that's built to be found
Draftbase generates schema, structured data, and a fast MDX editor for every post.
Frequently asked questions
Does Draftbase store content as JSON like most CMS platforms?
No. It stores MDX as a plain string, compiled to a component at render time.
Why not use next-mdx-remote?
Draftbase never has. The only MDX package in the stack is @mdx-js/mdx, used directly.
Can content authors run arbitrary code in MDX fields?
No. Only components an app explicitly passes in can render. Import statements throw and never run.
What happens if an editor writes broken MDX?
The renderer fails soft. It shows the broken part as plain text, not a crash.
Is MDX a good fit for every CMS user?
No. Teams with non-technical editors pay a compile-step cost for a JSX feature they may never use.
Samer is a software engineer and entrepreneur, founder of Draftbase and Ezi Home Services, building technology that simplifies home services. Passionate about software, APIs, automation, and creating products that solve real-world problems.


