Setting Up MDX With Tanstack Start: All You Need To Know
Written By: Enoch Klu
Publish Date: Saturday, 3 January 2026
#mdx
#tanstack
#react
#web
This article assumes you have already setup a Tanstack Start project or at least have the knowledge to do it. Because of this, I won’t go into that today since it exceeds the scope of this post, but you can visit the Tanstack Start website to bootstrap your app before returning here. All the best!
My Motivation
I began playing around with Tanstack Start in late 2025 for a website I was working on. For the most part, everything went on smoothly until I had to setup the docs pages! If you have worked on documentation, you know just how content-heavy it can be, and it wasn’t that I didn’t know how to build them: I just didn’t have enough time. Now, I needed a way to create these pages as quickly as I could, which is exactly when MDX came to mind.
Since I was pretty new to Tanstack Start, I didn’t quite know how to set it up. So naturally, I visited the oracle that goes by the name of Google. After a while of serious digging, I didn’t have much to show for it, and it wasn’t long before I realised what was wrong with my approach.
I took a step back to rethink the process and then I asked myself “what if I set up MDX with Vite instead?” Since my Tanstack Start project was running on Vite, this path felt promising. So I gave it a shot, it worked, and that is why I wrote this article for you.
What The Heck Is MDX?
MDX or Markdown eXtended as it is rarely known, allows you to write JSX inside Markdown. Imagine having the flexibility of Markdown with the power of your predefined components; that’s the idea.
If you’re not familiar with MDX, this example should show you how it looks in practice:
blog-post-1.mdx
// Define the frontmatter (used for content metadata like the post title for instance)---title: 'Blog Post 1'published_at: '2026-01-01'---// Import your componentimport { Banner } from '@/components/banner'// Use Markdown-style elements# Welcome To Blog Post 1!// Use your component (it works!)<Banner src="http://test.com/banner.png" />
Now that the introduction is out of the way, let’s install MDX.
Installing & Configuring MDX
Since we are setting up MDX with Tanstack Start running on Vite, here’s how we’ll install MDX:
script.sh
npm install @mdx-js/react @mdx-js/rollup
After installation, we’ll configure vite.config.ts like so:
vite.config.ts
/* Import Vite config dependencies */import { defineConfig } from 'vite'import { devtools } from '@tanstack/devtools-vite'import { tanstackStart } from '@tanstack/react-start/plugin/vite'import viteReact from '@vitejs/plugin-react'import viteTsConfigPaths from 'vite-tsconfig-paths'import tailwindcss from '@tailwindcss/vite'import { nitro } from 'nitro/vite'import mdx from '@mdx-js/rollup' // [!code highlight]/* Define the config */const config = defineConfig({ plugins: [ devtools(), nitro(), viteTsConfigPaths({ projects: ['./tsconfig.json'], }), tailwindcss(), tanstackStart(), { // [!code highlight] enforce: 'pre', // [!code highlight] ...mdx({ // [!code highlight] providerImportSource: '@mdx-js/react', // [!code highlight] /* Other MDX config options... */ // [!code highlight] }), // [!code highlight] }, // [!code highlight] viteReact(), ],})export default config
Set Up Table Rendering
The original setup does not support tables and will not render those defined purely in Markdown. In order to have Markdown-style tables converted into React components, we’ll need to install a plugin. For this guide we will install remark-gfm which enables support for tables, footnotes, and other Markdown elements.
script.sh
npm install remark-gfm
Finally, let’s update our vite.config.ts file with the remark-gfm plugin:
vite.config.ts
/* Import Vite config dependencies */import { defineConfig } from 'vite'import { devtools } from '@tanstack/devtools-vite'import { tanstackStart } from '@tanstack/react-start/plugin/vite'import viteReact from '@vitejs/plugin-react'import viteTsConfigPaths from 'vite-tsconfig-paths'import tailwindcss from '@tailwindcss/vite'import { nitro } from 'nitro/vite'import mdx from '@mdx-js/rollup'/* Import `remark-gfm` */import remarkGfm from 'remark-gfm' // [!code highlight]/* Define the config */const config = defineConfig({ plugins: [ devtools(), nitro(), viteTsConfigPaths({ projects: ['./tsconfig.json'], }), tailwindcss(), tanstackStart(), { enforce: 'pre', ...mdx({ providerImportSource: '@mdx-js/react', remarkPlugins: [[remarkGfm, { strict: true }]], // [!code highlight] }), }, viteReact(), ],})export default config
That’s it! Now you should have tables rendering like they should.
More details on how to configure @mdx-js/rollup can be found here. For guides on how to set up MDX with other build systems or bundlers, see the MDX docs.
Since we have MDX installed and configured, let’s add some custom components.
Using Custom Components
We can render custom components in 2 ways:
Option 1: Wrapping MDXProvider Around Your MDX Content
I personally prefer and recommend this approach where possible. Since it is a context provider, it allows you to pass your custom components once to be used especially in layout routes/components. Here’s how you use it:
The Outlet component renders the next potentially matching child route. For this example, that means any matching route that is visited under /blog (in our case, the rendered MDX) in the URL will replace <Outlet /> in the layout component. If a route is not found, <Outlet /> renders null.
Option 2: Importing The MDX File To Pass Your Custom Components Directly
This approach allows you to pass your custom components directly as props to each MDX file. That’s because MDX files are components, and this method enables you to pass unique components to each MDX file wherever you see fit. Here’s how you use it:
Notice how we’re passing different components for Post 1 and Post 2? If having different components for different pages benefits you, then this is definitely the way to go.
For a complete list of all the HTML components you can override, see this page.
Final Thoughts
MDX offers a lot of benefits when crafting pages that are easy to edit and manage. That said, I hope you successfully set up your app, and I can’t wait to see what you build with this.