Skip to content

Commit 8b15429

Browse files
authored
Merge pull request #87 from jasongerbes/improve-mdx-docs
Improve docs for MDX integration
2 parents 6b0cdd5 + 1621719 commit 8b15429

File tree

1 file changed

+127
-13
lines changed

1 file changed

+127
-13
lines changed

content/docs/300-sources/100-files/400-mdx.mdx

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,23 @@ excerpt: How Contentlayer processes MDX when using local files as the content so
66

77
MDX brings JSX components to markdown, which can provide power and flexibility to the main body area of a content piece.
88

9-
## MDX Content
9+
```md
10+
# Hello, World!
1011

11-
Contentlayer supports MDX processing via [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler). By default, Contentlayer processes the main content area of `.md` and `.mdx` files as markdown. You can enable this behavior using the `contentType` option in your document type definition.
12+
This is my first MDX file. Here's a button element <button>Click me!</button>.
13+
14+
<MyComponent />
15+
```
16+
17+
## MDX Content Type
18+
19+
Contentlayer supports MDX processing via [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler). By default, Contentlayer processes the main content area of `.md` and `.mdx` files as markdown.
20+
21+
You can enable MDX processing by setting the `contentType` option in your document type definition to `'mdx'` in your Contentlayer configuration.
1222

1323
```js
14-
defineDocumentType(() => ({
24+
// contentlayer.config.ts
25+
const Post = defineDocumentType(() => ({
1526
name: 'Post',
1627
filePathPattern: `**/*.mdx`,
1728
contentType: 'mdx',
@@ -21,44 +32,147 @@ defineDocumentType(() => ({
2132
}))
2233
```
2334

24-
## Usage from Next.js
35+
## Usage in Next.js
36+
37+
To parse the contents of a MDX file in a Next.js page, use the `useMDXComponent` hook provided by `next-contentlayer/hooks`.
38+
39+
### Pages Directory
40+
41+
Here's an example implementation in Next.js using the legacy `/pages` directory:
2542

2643
```tsx
27-
import React from 'react'
44+
// pages/posts/[slug].tsx
2845
import { allPosts, type Post } from 'contentlayer/generated'
2946
import { useMDXComponent } from 'next-contentlayer/hooks'
3047

31-
export const getStaticProps = () => {
32-
const post = allPosts[0]
48+
export async function getStaticPaths() {
49+
// Get a list of valid post paths.
50+
const paths = allPosts.map((post) => ({
51+
params: { slug: post._raw.flattenedPath },
52+
}))
53+
54+
return { paths, fallback: false }
55+
}
56+
57+
export async function getStaticProps(context) {
58+
// Find the post for the current page.
59+
const post = allPosts.find((post) => post._raw.flattenedPath === context.params.slug)
60+
61+
// Return notFound if the post does not exist.
62+
if (!post) return { notFound: true }
63+
64+
// Return the post as page props.
3365
return { props: { post } }
3466
}
3567

36-
const MyButton: React.FC = () => <button>Click me</button>
68+
export default function Page({ post }: { post: Post }) {
69+
// Parse the MDX file via the useMDXComponent hook.
70+
const MDXContent = useMDXComponent(post.body.code)
71+
72+
return (
73+
<div>
74+
{/* Some code ... */}
75+
<MDXContent />
76+
</div>
77+
)
78+
}
79+
```
80+
81+
In this example, the `getStaticPaths` function returns an array of all valid post slugs for pre-rendering and the `getStaticProps` function retrieves the relevant MDX post for the current page. The `useMDXComponent` hook then processes the MDX file via [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler). Finally, the rendered content is displayed using the `MDXContent` component returned by the `useMDXComponent` hook.
82+
83+
### App Directory
84+
85+
Here's an example implementation in Next.js version 13 and above using the new `/app` directory:
86+
87+
```tsx
88+
// app/posts/[slug]/page.tsx
89+
import { allPosts } from 'contentlayer/generated'
90+
import { useMDXComponent } from 'next-contentlayer/hooks'
91+
import { notFound } from 'next/navigation'
3792

38-
const Page: React.FC<{ post }> = ({ post }) => {
93+
export async function generateStaticParams() {
94+
return allPosts.map((post) => ({
95+
slug: post._raw.flattenedPath,
96+
}))
97+
}
98+
99+
export default async function Page({ params }: { params: { slug: string } }) {
100+
// Find the post for the current page.
101+
const post = allPosts.find((post) => post._raw.flattenedPath === params.slug)
102+
103+
// 404 if the post does not exist.
104+
if (!post) notFound()
105+
106+
// Parse the MDX file via the useMDXComponent hook.
39107
const MDXContent = useMDXComponent(post.body.code)
40-
108+
41109
return (
42110
<div>
43111
{/* Some code ... */}
44-
<MDXContent components={{ MyButton }} />
112+
<MDXContent />
45113
</div>
46114
)
47115
}
116+
```
117+
118+
In this example, the `generateStaticParams` function returns an array of all valid post slugs for pre-rendering. The `useMDXComponent` hook then processes the MDX file for the current page via [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler). Finally, the rendered content is displayed using the `MDXContent` component returned by the `useMDXComponent` hook.
48119

49-
export default Page
120+
## Custom MDX Components
121+
122+
You can override built-in HTML elements and create your own custom React components using the `components` prop of the `MDXContent` component returned by the `useMDXComponent` hook.
123+
124+
Here's an example implementation:
125+
126+
```tsx
127+
import { allPosts } from 'contentlayer/generated'
128+
import type { MDXComponents } from 'mdx/types'
129+
import { useMDXComponent } from 'next-contentlayer/hooks'
130+
import Link from 'next/link'
131+
132+
// Define your custom MDX components.
133+
const mdxComponents: MDXComponents = {
134+
// Override the default <a> element to use the next/link component.
135+
a: ({ href, children }) => <Link href={href as string}>{children}</Link>,
136+
// Add a custom component.
137+
MyComponent: () => <div>Hello World!</div>,
138+
}
139+
140+
export default async function Page({ params }: { params: { slug: string } }) {
141+
// Find the post for the current page.
142+
const post = allPosts.find((post) => post._raw.flattenedPath === params.slug)
143+
144+
// 404 if the post does not exist.
145+
if (!post) notFound()
146+
147+
// Parse the MDX file via the useMDXComponent hook.
148+
const MDXContent = useMDXComponent(post.body.code)
149+
150+
return (
151+
<div>
152+
{/* Some code ... */}
153+
<MDXContent components={mdxComponents} /> {/* <= Include your custom MDX components here */}
154+
</div>
155+
)
156+
}
50157
```
51158

159+
In this example, we define a custom `mdxComponents` object that overrides the default `<a>` element to use the `next/link` component and adds a custom `MyComponent` component. Then, we include our custom components by passing the `mdxComponents` object to the `components` prop of the `MDXContent` component.
160+
52161
## MDX Plugins (remark/rehype)
53162

54163
You can add [remark](https://github.com/remarkjs/remark) and [rehype](https://github.com/rehypejs/rehype) plugins inside the `mdx` property when calling `makeSource` in your Contentlayer configuration.
55164

56165
```js
166+
// contentlayer.config.ts
57167
import { makeSource } from 'contentlayer/source-files'
58168
import highlight from 'rehype-highlight'
169+
import remarkGfm from 'remark-gfm'
59170

60171
export default makeSource({
61172
// ...
62-
mdx: { rehypePlugins: [highlight] },
173+
mdx: {
174+
remarkPlugins: [remarkGfm],
175+
rehypePlugins: [highlight],
176+
},
63177
})
64178
```

0 commit comments

Comments
 (0)