The magic of MDX lies in its ability to blend the simplicity of Markdown with the power of React components. But what if your content doesn't live in static .mdx files? What if it's stored in a headless CMS, a database, or needs to be fetched from a third-party API?
This is where the true power of mdx.do shines. By providing a simple API for on-the-fly content compilation, mdx.do decouples your content's source from its presentation. You can pull Markdown JSX from anywhere and seamlessly render it as a rich, interactive React component.
This post explores how to integrate mdx.do with your favorite data sources, transforming your data into dynamic, component-driven content.
No matter where your content lives, the integration pattern remains elegantly simple and follows three steps:
Let's see this pattern in action with a few common use cases.
A headless CMS is a perfect match for mdx.do. Your content team can write rich, component-based articles in a familiar interface, and your front end can render it dynamically without requiring a redeployment.
Imagine you have a "Post" content type in your CMS with a "Rich Text" or "Markdown" field called content.
Here’s how you might fetch and render it in a Next.js application:
// pages/blog/[slug].js
import { getMDXComponent } from 'next-mdx-remote/rsc';
import { mdx } from ".do"; // Your helper for the mdx.do API
// Your custom components available to MDX
import { Callout, Chart } from '@/components/mdx';
const components = {
Callout,
Chart,
};
// This function runs on the server
export async function getServerSideProps({ params }) {
// 1. FETCH from your Headless CMS
const response = await fetch(`https://my-cms.api/posts?slug=${params.slug}`);
const post = await response.json();
const mdxSource = post.data.attributes.content; // The raw MDX string
// 2. COMPILE the MDX string with mdx.do
const { code, frontmatter } = await mdx.compile(mdxSource);
// 3. Pass compiled code to the page component
return {
props: {
code,
frontmatter,
},
};
}
// Your page component renders the result
export default function BlogPost({ code, frontmatter }) {
const Component = getMDXComponent(code);
return (
<article>
<h1>{frontmatter.title}</h1>
<p>By {frontmatter.author}</p>
<hr />
{/* 3. RENDER the component, passing in your component map */}
<Component components={components} />
</article>
);
}
What about user-generated content? If you're building a platform that allows users to create their own profiles, documentation, or comments with rich formatting, storing their input in a database is the way to go. mdx.do can then safely compile and render it on demand.
Consider an Express.js backend that serves page content stored in a PostgreSQL database.
// server/routes/pages.js
import express from 'express';
import db from '../database'; // Your database client (e.g., Prisma, Knex)
import { mdx } from ".do";
const router = express.Router();
router.get('/:id', async (req, res) => {
try {
// 1. FETCH from the database
const page = await db.page.findUnique({
where: { id: req.params.id },
});
if (!page) {
return res.status(404).json({ error: 'Page not found' });
}
const mdxSource = page.content;
// 2. COMPILE with mdx.do
const { code, frontmatter } = await mdx.compile(mdxSource);
// 3. Send compiled code to the client
res.status(200).json({ code, frontmatter });
} catch (error) {
res.status(500).json({ error: 'Failed to process content' });
}
});
export default router;
Your React front end can then call this API route (/api/pages/some-id), receive the code, and render it using the same technique shown in the first pattern.
Sometimes, the content you want to display comes from an entirely different service. A classic example is rendering a project's README.md file directly from its GitHub repository.
Using a serverless function or an API route as a proxy is a best practice. This keeps your API keys secure and centralizes the logic.
Here’s a Next.js API route that fetches a README from GitHub, compiles it, and serves the result.
// pages/api/github-readme.js
import { mdx } from ".do";
export default async function handler(req, res) {
const { owner, repo } = req.query; // e.g., ?owner=vercel&repo=next.js
if (!owner || !repo) {
return res.status(400).json({ error: 'Owner and repo are required.' });
}
try {
// 1. FETCH from the GitHub API
const readmeUrl = `https://api.github.com/repos/${owner}/${repo}/readme`;
const response = await fetch(readmeUrl, {
headers: {
Accept: 'application/vnd.github.v3.raw',
// Use an auth token to avoid rate limiting
Authorization: `token ${process.env.GITHUB_TOKEN}`,
},
});
if (!response.ok) {
throw new Error(`GitHub API responded with ${response.status}`);
}
const mdxSource = await response.text();
// 2. COMPILE with mdx.do
const { code } = await mdx.compile(mdxSource);
// 3. SEND compiled code to the client
res.status(200).json({ code });
} catch (error) {
res.status(502).json({ error: 'Failed to fetch or compile README.' });
}
}
A component on your front end can now call /api/github-readme?owner=...&repo=... to get the renderable code for any public repository's README file.
By treating MDX compilation as a service, mdx.do frees you to focus on what matters: creating compelling user experiences with content from any source. Whether you're building a blog powered by a CMS, a documentation site from database records, or a developer portal that pulls from GitHub, the pattern is the same.
Fetch your string, compile it with a single API call, and render a fully-featured React component. Your content is no longer static—it's alive, dynamic, and ready to be integrated anywhere.