Getting Started

MDXTS is a collection of components and utilities for rendering MDX content, type documentation, and code examples in React.

In some cases this library uses React Server Components which are currently limited to only a few frameworks. First-class support is offered for Next.js through a plugin. Please open an issue or PR if you are interested in supporting other frameworks.

Automated Setup

The CLI tool is the quickest way to get started using MDXTS.

Run the following command, optionally in the root of your Next.js project, to initialize the blog example or walk through configuring the plugin and rendering a source in an existing project:

npm create mdxts

Manual Setup

Alternatively, follow the manual setup guide below to learn how to configure the Next.js plugin and render a source.

Install

In your Next.js project, run the following command to install the mdxts package:

npm install mdxts
pnpm add mdxts
bun add mdxts
yarn add mdxts

Configuring the Next.js Plugin

The Next.js plugin sets up everything needed for authoring MDX files and collecting type documentation from TypeScript source files throughout your projects.

The plugin includes default remark plugins, MDX components, and a Webpack loader for gathering collections of modules and their related metadata.

next.config.mjs
import { createMdxtsPlugin } from 'mdxts/next'

const withMdxts = createMdxtsPlugin({
  theme: 'nord',
  gitSource: 'https://github.com/souporserious/mdxts',
})

export default withMdxts({
  // Next.js config options here...
})

The theme option is used for syntax highlighting code blocks and is powered by Shiki. The theme value should correspond to either a built-in theme or a source path to a custom JSON theme that adheres to the VS Code theme specification.

Defining a Data Source

Use the createSource utility to generate helpers for gathering metadata about MDX and TypeScript source files. This can be used to build routes for rendering content, documentation, and examples.

data.ts
import { createSource } from 'mdxts'

export const allDocs = createSource('docs/**/*.mdx')
This file can be rendered anywhere in your project and split into multiple files for organization if needed.

Routing

The paths helper provides a list of paths to generate static routes:

data.ts
import { createSource } from 'mdxts'

export const allDocs = createSource('docs/**/*.mdx')

allDocs.paths() // => [['docs', 'getting-started'], ['docs, 'authoring']]

Visit the routing section for more information on how to use paths to generate routes.

Rendering Content

Use the get method from the allDocs source we created above to retrieve the metadata for a specific document and render its content:

app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
import { allDocs } from '../../../data'

type Props = { params: { slug: string } }

export default async function Page({ params }: Props) {
  const doc = await allDocs.get(params.slug)

  if (!doc) {
    return notFound()
  }

  const { Content } = doc

  return <Content />
}

Rendering Type Documentation

If the targeted source files are TypeScript files, the exported types will also be analyzed and included along with other metadata like associated MDX content and examples:

app/components/[slug]/page.tsx
import { createSource } from 'mdxts'
import { notFound } from 'next/navigation'

type Props = { params: { slug: string } }

const allComponents = createSource('components/**/*.tsx')

export default async function Page({ params }: Props) {
  const component = await allComponents.get(params.slug)

  if (component === undefined) {
    return notFound()
  }

  const { Content, exportedTypes } = component

  return (
    <>
      {Content ? <Content /> : null}
      <ul>
        {exportedTypes.map((type) => (
          <li key={type.name}>
            <h4>{type.name}</h4>
            <p>{type.description}</p>
            {type.types.map((type) =>
              type.properties ? (
                <ul>
                  {type.properties.map((property) => (
                    <li key={property.name}>
                      <h5>{property.name}</h5>
                      <p>{property.description}</p>
                    </li>
                  ))}
                </ul>
              ) : (
                type.text
              )
            )}
          </li>
        ))}
      </ul>
    </>
  )
}
When targeting JavaScript or TypeScript files, heuristics are used to infer public metadata. First, package exports will be used if present, otherwise, the presence of an index file will be used to infer public exports. If these both fail, the entire directory will be analyzed.

Type Checking Code Blocks

All code blocks will automatically be type-checked using the TypeScript compiler to ensure code works as expected.

Notice the following will throw a type error because b is not defined:

const a = 1
a + b

This can be configured by passing allowErrors to disable type checking like in the following example:

```tsx allowErrors
const a = 1
a + b
```
const a = 1
a + b

Alternatively, override the pre component in a top-level mdx-components.tsx file to allow errors by default in MDX content:

mdx-components.tsx
import { MDXComponents } from 'mdxts/components'

export function useMDXComponents() {
  return {
    ...MDXComponents,
    pre: (props) => <MDXComponents.pre allowErrors {...props} />,
  } satisfies MDXComponents
}

Allowing errors is useful to show incomplete or incorrect code examples in documentation for educational purposes. It’s highly encouraged to disallow errors if possible so code examples are always using correct code that can compile.

Read more about type checking to learn how the TypeScript compiler is used for providing type information for code blocks and validating front matter fields.

Examples

To associate examples with a source file, create a sibling file with the same name and a .examples.tsx extension:

components/Button.tsx
export const Button = ({ children }) => <button>{children}</button>
components/Button.examples.tsx
import { Button } from './Button'

export const Basic = () => <Button>Click Me</Button>

When collecting information about a TypeScript source file (Button.tsx in this case), the examples will be included in the metadata. Read more about examples to learn how to generate interactive documentation and render them to a route.

Next Steps

Check out the routing section to learn how to generate routes for your content and documentation.

Last updated