React + Vite

React + Vite

Integrate MDX UI with a React + Vite project using @mdx-js/rollup and MDXProvider

Overview

Vite has no built-in MDX convention like Next.js, so you wire things up manually — @mdx-js/rollup handles MDX compilation, and MDXProvider from @mdx-js/react injects your components globally.

Best for: SPAs, dashboards, internal tools, interactive docs without SSR.


1. Create a Vite + React Project

Skip this step if you already have a project.

pnpm create vite my-app --template react-ts
cd my-app
pnpm install

2. Install MDX Support

pnpm add @mdx-js/rollup @mdx-js/react remark-gfm
pnpm add -D @types/mdx

3. Configure Vite

Update vite.config.ts:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mdx from "@mdx-js/rollup";
import remarkGfm from "remark-gfm";
 
export default defineConfig({
  plugins: [
    mdx({
      remarkPlugins: [remarkGfm],
      providerImportSource: "@mdx-js/react",
    }),
    react(),
  ],
});

Add MDX type declarations to src/vite-env.d.ts:

/// <reference types="vite/client" />
declare module "*.mdx" {
  import type { ComponentType } from "react";
  const Component: ComponentType;
  export default Component;
}

4. Install Tailwind CSS

MDX UI components use Tailwind CSS for styling.

bash pnpm add tailwindcss @tailwindcss/vite

Update vite.config.ts:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mdx from "@mdx-js/rollup";
import remarkGfm from "remark-gfm";
import tailwindcss from "@tailwindcss/vite";
 
export default defineConfig({
  plugins: [
    mdx({ remarkPlugins: [remarkGfm], providerImportSource: "@mdx-js/react" }),
    react(),
    tailwindcss(),
  ],
});

Add at the top of src/index.css:

src/index.css
@import "tailwindcss";
 
/* rest of your existing styles stay below */

If you have existing @tailwind base, @tailwind components, or @tailwind utilities lines, remove those and use this single import instead.


5. Initialize MDX UI

bash pnpm dlx @ravikumarsurya/mdx-ui init

This creates mdx-ui.json and installs the cn() utility.


6. Add Components

bash pnpm dlx @ravikumarsurya/mdx-ui add callout accordion code-block

Auto-updated for you

The CLI automatically copies the component file and updates src/components/mdx-components.tsx with the correct imports and mappings. You never need to edit that file manually.


7. Set Up MDXProvider

The init command creates src/components/mdx-components.tsx with the marker comments the CLI uses to auto-patch it. You only need to wire it up once in src/main.tsx — every subsequent add command keeps it up to date.

Wrap your app with MDXProvider in src/main.tsx:

import React from "react";
import ReactDOM from "react-dom/client";
import { MDXProvider } from "@mdx-js/react";
import { mdxComponents } from "./components/mdx-ui/mdx-components";
import App from "./App";
import "./index.css";
 
ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <MDXProvider components={mdxComponents}>
      <App />
    </MDXProvider>
  </React.StrictMode>,
);

8. Set Up Routing

If your app has multiple MDX pages, use React Router to map URLs to files.

Install React Router

bash pnpm add react-router-dom

Create your MDX pages

Create these three files with some starter content:

Wire up routes in src/App.tsx

src/App.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { MDXProvider } from "@mdx-js/react";
import { mdxComponents } from "./components/mdx-components";
 
import Index from "./content/index.mdx";
import About from "./content/about.mdx";
import Guide from "./content/guide.mdx";
 
export default function App() {
  return (
    <MDXProvider components={mdxComponents}>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Index />} />
          <Route path="/about" element={<About />} />
          <Route path="/guide" element={<Guide />} />
        </Routes>
      </BrowserRouter>
    </MDXProvider>
  );
}

Move <MDXProvider> here and remove it from main.tsx — one provider wrapping all routes is enough.

Add a Navbar

Create src/components/Navbar.tsx to navigate between your MDX pages:

src/components/Navbar.tsx
import { NavLink } from "react-router-dom";
 
const links = [
  { label: "Home", href: "/" },
  { label: "Guide", href: "/guide" },
  { label: "About", href: "/about" },
];
 
export function Navbar() {
  return (
    <nav className="border-b px-6 py-3 flex items-center gap-6">
      <span className="font-bold text-sm mr-4">My App</span>
      {links.map(({ label, href }) => (
        <NavLink
          key={href}
          to={href}
          end={href === "/"}
          className={({ isActive }) =>
            isActive
              ? "text-sm font-medium text-green-600"
              : "text-sm text-gray-500 hover:text-gray-900"
          }
        >
          {label}
        </NavLink>
      ))}
    </nav>
  );
}

Add <Navbar /> to the src/App.tsx from the previous step:

src/App.tsx
  import { BrowserRouter, Routes, Route } from "react-router-dom";
  import { MDXProvider } from "@mdx-js/react";
  import { mdxComponents } from "./components/mdx-ui/mdx-components";
+ import { Navbar } from "./components/Navbar";
  import Index from "./content/index.mdx";
  import About from "./content/about.mdx";
  import Guide from "./content/guide.mdx";
 
  export default function App() {
    return (
      <MDXProvider components={mdxComponents}>
        <BrowserRouter>
+         <Navbar />
+         <main className="max-w-3xl mx-auto px-6 py-10">
            <Routes>
              <Route path="/" element={<Index />} />
              <Route path="/about" element={<About />} />
              <Route path="/guide" element={<Guide />} />
            </Routes>
+         </main>
        </BrowserRouter>
      </MDXProvider>
    );
  }

NavLink from React Router automatically applies the active class when the URL matches — no manual useLocation needed.

Handle client-side routing in Vite

By default, Vite's dev server only serves index.html for /. Deep links like /guide will 404 on refresh without this fix.

Add the server block to your existing vite.config.ts:

vite.config.ts
  import { defineConfig } from "vite";
  import react from "@vitejs/plugin-react";
  import mdx from "@mdx-js/rollup";
  import remarkGfm from "remark-gfm";
 
  export default defineConfig({
    plugins: [
      mdx({ remarkPlugins: [remarkGfm], providerImportSource: "@mdx-js/react" }),
      react(),
    ],
+   server: {
+     historyApiFallback: true,
+   },
  });

For production, configure your host (Vercel, Netlify, Nginx) to redirect all paths to index.html:


Project Structure

📂my-app
├── 📂src
├── 📂components
│ │ ├── 📁mdx-ui
│ │ ├── 📄mdx-components.tsx
├── 📁content
├── 📄App.tsx
├── 📄main.tsx
├── 📄index.css
├── 📄vite-env.d.ts
├── 📄vite.config.ts
├── 📄tailwind.config.ts
├── 📄mdx-ui.json
├── 📄package.json

Math Components

Math uses pure JSX primitives — no KaTeX or LaTeX required. Install and add to components:

pnpm dlx @ravikumarsurya/mdx-ui add math-primitives math-solution
// src/components/mdx-components.tsx
import {
  Frac,
  Pow,
  Integral,
  Sum,
  Alpha /* ...all primitives */,
} from "./mdx-ui/math-primitives";
import {
  Solution,
  SolutionStep,
  SolutionAnswer,
  SolutionNote,
} from "./mdx-ui/math-solution";
 
export const mdxComponents = {
  // ...existing
  Frac,
  Pow,
  Integral,
  Sum,
  Alpha,
  Solution,
  SolutionStep,
  SolutionAnswer,
  SolutionNote,
};

See Math Primitives for the full list of 370+ components.


Troubleshooting

Cannot find module @/lib/primitives or @/lib/motion

MDX UI components use the @/ path alias. You need to configure it in vite.config.ts and in every tsconfig that TypeScript compiles directly.

1. Install the Node path helper:

bash pnpm add -D @types/node

2. Add the alias to vite.config.ts:

vite.config.ts
+ import path from "path";
  import { defineConfig } from "vite";
  import react from "@vitejs/plugin-react";
  import mdx from "@mdx-js/rollup";
  import remarkGfm from "remark-gfm";
 
  export default defineConfig({
    plugins: [
      mdx({ remarkPlugins: [remarkGfm], providerImportSource: "@mdx-js/react" }),
      react(),
    ],
+   resolve: {
+     alias: {
+       "@": path.resolve(__dirname, "./src"),
+     },
+   },
  });

3. Add the alias to tsconfig.app.json (and tsconfig.json if present):

Vite projects use TypeScript project references. tsc -b compiles each reference using its own config file — so paths must be in tsconfig.app.json, not just the root tsconfig.json. If you only add it to the root, the dev server works (Vite has its own resolver) but tsc builds fail.

tsconfig.app.json
  {
    "compilerOptions": {
+     "paths": {
+       "@/*": ["./src/*"]
+     }
    }
  }
Note on baseUrl

baseUrl is deprecated in TypeScript 5.5+. paths works standalone without it.

After this, @/lib/primitives resolves to src/lib/primitives.ts which the CLI copies during init.

Components not rendering in MDX

Make sure providerImportSource: "@mdx-js/react" is set in vite.config.ts and your app is wrapped in <MDXProvider>.

TypeScript error on .mdx imports

Add the module declaration to vite-env.d.ts:

declare module "*.mdx" {
  import type { ComponentType } from "react";
  const Component: ComponentType;
  export default Component;
}

Tailwind classes not applying

Ensure src/**/*.mdx is in your Tailwind content array so component class names are not purged.


Use with Claude Code (MCP)

Once your React + Vite project is set up, you can connect the mdx-ui MCP server so Claude knows exactly which components are available — no system prompt needed.

Create .claude/settings.json in your project root:

{
  "mcpServers": {
    "mdx-ui": {
      "command": "pnpm",
      "args": ["dlx", "@ravikumarsurya/mdx-ui@latest", "mcp"]
    }
  }
}
npm or yarn

Replace pnpm dlx with npx or yarn dlx depending on your package manager.

Open the project in VS Code with Claude Code. The server starts automatically and gives Claude live access to your component registry. You can then ask Claude to generate MDX content — it will call list_components, convert_latex, and validate_mdx automatically before returning output.

See the MCP Server doc for the full list of tools and prompts.


Next Steps