Why a Live MDX Playground
MDX is Markdown with JSX. That makes it perfect for writing content and sprinkling in React components, but it is hard to iterate on MDX without a fast preview loop. A live editor solves that problem by compiling MDX in the browser and rendering it instantly.
In this project, we built a dedicated route at src/app/mdx/page.tsx that splits the screen into a CodeMirror editor on the left and a live MDX preview on the right. It runs inside the Next.js App Router as a client component.
Packages We Installed
We installed a small set of packages to support editing, Vim mode, and MDX compilation:
@uiw/react-codemirrorfor the React wrapper around CodeMirror 6.@codemirror/lang-markdownto enable Markdown syntax.@replit/codemirror-vimto enable Vim keybindings.@mdx-js/mdxto compile MDX strings into a React component at runtime.remark-gfmto support GitHub Flavored Markdown features like tables and task lists.
Next.js already ships with React, so the rest is just wiring.
Building the Editor
The editor lives in a client component because CodeMirror and MDX compilation are browser-only. In the page we:
- Import
CodeMirrorand configure it withmarkdown()andvim()extensions. - Track the editor content in
useState. - Debounce compilation to avoid running
evaluateon every keystroke.
CodeMirror gives us a fast, fully featured editor, and Vim mode makes it feel natural for keyboard-first workflows.
Compiling MDX on the Client
For the live preview, we compile MDX with evaluate:
tsxconst { default: MDXComponent } = await evaluate(source, { ...runtime, Fragment, remarkPlugins: [remarkGfm], providerImportSource: "@mdx-js/react", });
Two pieces matter here:
remarkPluginslets us opt into GitHub Flavored Markdown.providerImportSourceensures the compiled MDX can receive acomponentsprop for custom rendering.
The result is a React component we can render immediately.
Matching Blog Styling with mdx-components.tsx
The blog uses src/mdx-components.tsx to define custom renderers for headings, links, tables, code blocks, and more. To keep the live preview consistent with blog posts, we pass those same components into the compiled MDX component:
tsxconst mdxComponents = useMDXComponents({}); <Component components={mdxComponents} />
This ensures h1, h2, and other elements render with the same styles and spacing as real posts. The preview also wraps content in a prose container to match the reading layout.
Putting It All Together
The final /mdx page is a two-column layout:
- Left: CodeMirror editor with Markdown and Vim support.
- Right: A live preview that uses the exact same MDX component mapping as the blog.
This creates a tight feedback loop for authors and keeps the preview honest. If a component exists in mdx-components.tsx, it works the same way in the preview.
Where to Go Next
From here, you can:
- Add syntax highlighting or themes to CodeMirror.
- Save drafts to local storage or a database.
- Add a toolbar with insertions for common MDX components.
The core workflow is already in place: edit MDX, compile on the client, and render with your real MDX component system.