MDX Guide

Setting up MDX for web

In building out this beautiful website, and the equally beautiful tamagui.dev, one of the trickier parts was getting MDX to work well.

Since many blogs, apps, and documentation sites want to display Markdown content, we figured a guide showing how we did it will be useful.

The @vxrn/mdx package bundles together Rehype plugins alongside mdx-bundler to provide a batteries-included MDX experience with:

  • Syntax highlighting via refractor
  • GitHub Flavored Markdown support
  • Smart typography via remark-smartypants (with code blocks properly preserved)
  • Auto-linked headings with slugified IDs
  • Image metadata extraction including blur placeholders
  • Reading time calculation
  • Frontmatter with heading extraction for table of contents

Setting up your data dir

The first step is to create a directory for your mdx files to live:

data

hello-world.mdx

Your MDX content goes here

Throw some stuff in that file:

Terminal

--- title: MDX Guide description: Setting up MDX for web --- In building out this beautiful website, and the equally beautiful [tamagui.dev](https://tamagui.dev), the surprising most difficult part was getting MDX to work well...

Setting up @vxrn/mdx

Next, we add our mdx dependencies:

npm install @vxrn/mdx mdx-bundler

We set up our MDX components (using Tamagui in this example):

features/MDXComponents.tsx

import { Text } from "react-native";
export const components = {
h1: ({ children }) => (
<Text style={{ fontSize: 24, fontWeight: "bold", marginBottom: 10 }}>
{children}
</Text>
),
h2: ({ children }) => (
<Text style={{ fontSize: 20, fontWeight: "bold", marginBottom: 8 }}>
{children}
</Text>
),
h3: ({ children }) => (
<Text style={{ fontSize: 18, fontWeight: "bold", marginBottom: 6 }}>
{children}
</Text>
),
p: ({ children }) => <Text style={{ marginBottom: 10 }}>{children}</Text>,
};

And then all we need is a new route:

app/docs/[slug].tsx

import { getMDXComponent } from "mdx-bundler/client";
import { useMemo } from "react";
import { useLoader } from "one";
import { Text, View } from "react-native";
import { components } from "~/features/MDXComponents";
export async function generateStaticParams() {
const { getAllFrontmatter } = await import("@vxrn/mdx");
const frontmatters = getAllFrontmatter("data");
const paths = frontmatters.map(({ slug }) => ({
slug: slug.replace(/.*docs\//, ""),
}));
return paths;
}
export async function loader({ params }) {
const { getMDXBySlug } = await import("@vxrn/mdx");
const { frontmatter, code } = await getMDXBySlug("data", params.slug);
return {
frontmatter,
code,
};
}
export function DocsPage() {
const { code, frontmatter } = useLoader(loader);
const Component = useMemo(() => getMDXComponent(code), [code]);
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 48, fontWeight: "bold", marginBottom: 10 }}>
{frontmatter.title}
</Text>
<Component components={components} />
</View>
);
}

Note that we await import the @vxrn/mdx library - this is because One by default builds out your server and API routes to CommonJS, but some of the rehype/remak dependencies are ESM. Long story short - this makes it work.

Configuring Vite for MDX Support

To ensure that MDX works correctly with your One project, you’ll need to configure Vite. Update your vite.config.ts file with the following content:

vite.config.ts

import { defineConfig } from 'vite'
import { one } from 'one/vite'
export default defineConfig({
ssr: {
noExternal: true,
external: ['@vxrn/mdx'],
},
plugins: [
one({
web: {
defaultRenderMode: 'ssg',
},
}),
],
})

This configuration does a few important things:

  1. It sets noExternal: true in the SSR options, which tells Vite to bundle all dependencies for server-side rendering.
  2. It explicitly marks @vxrn/mdx as an external dependency, which is necessary because it contains ESM modules that shouldn’t be bundled.
wwwwwwwwwwwwwwwwwww

That should do it. We say should, because there’s a lot going on here, and many sub-deps inside @vxrn/mdx that need to work together. Let us know if this works for you!

Edit this page on GitHub.