next-mdx-remote

repository·main·Indexed 25 days ago

https://github.com/hashicorp/next-mdx-remote

Utilities for loading MDX from any remote source as data rather than as a local import. It allows MDX content to be loaded within Next.js data-fetching methods like getStaticProps or getServerSideProps and hydrated on the client side. Version 6.0.0 supports both standard Next.js pages and React Server Components (RSC) via next-mdx-remote/rsc, providing features such as frontmatter parsing, custom component mapping, and configurable JavaScript security settings.

Tokens
7.2K
Snippets
16
Records
35
Agent score
84%

What's inside next-mdx-remote

  1. Use next-mdx-remote with React Server Components (RSC)

    main

    To use next-mdx-remote within the Next.js app directory or any React Server Component environment, import from next-mdx-remote/rsc.

    Key differences in RSC mode:

    • <MDXRemote /> is an async component and must be rendered on the server.
    • Use the source prop to pass the MDX string directly (the separate serialization step is no longer required).
    • The lazy prop is not supported because rendering occurs on the server.
    • Custom components must be passed via the components prop. The MDXProvider context from @mdx-js/react is not supported because RSC does not support React Context.
    • Client components can be included within the MDX markup.
    import { MDXRemote } from 'next-mdx-remote/rsc'
    
    // app/page.js
    export default function Home() {
      return (
        <MDXRemote
          source={`# Hello World
    
    This is from Server Components!`}
        />
      )
    }
  2. Configure JavaScript security in `serialize`

    main

    By default, JavaScript expressions (e.g., {variable}) are disabled in next-mdx-remote v6.0.0+ for security. If you need to enable them, follow these patterns:

    1. Trusted content with protection (Recommended): Set blockJS: false. The blockDangerousJS: true setting (default) will attempt to block dangerous globals like eval, Function, process, and require.
    2. Completely trusted content: Set both blockJS: false and blockDangerousJS: false.

    Warning: Only use blockDangerousJS: false if you completely trust the MDX source, as it removes critical protections against Remote Code Execution (RCE).

  3. Replace default HTML components in MDX

    main

    You can customize how MDX renders HTML elements by passing a components object to <MDXRemote />. This uses MDXProvider under the hood. This is useful for applying custom styling via libraries like Material UI.

    Note: You cannot replace th or td components because the forward slash in their names is not supported.

    import { Typography } from "@material-ui/core";
    
    const components = { Test, h2: (props) => <Typography variant="h2" {...props} /> }
  4. Provide global components using MDXProvider

    main

    To make components available to all <MDXRemote /> instances in your application without passing them explicitly every time, wrap your application (e.g., in _app.js) with <MDXProvider /> from @mdx-js/react.

    // pages/_app.jsx
    import { MDXProvider } from '@mdx-js/react'
    import Test from '../components/test'
    
    const components = { Test }
    
    export default function MyApp({ Component, pageProps }) {
      return (
        <MDXProvider components={components}>
          <Component {...pageProps} />
        </MDXProvider>
      )
    }
    
    // pages/test.jsx
    import { serialize } from 'next-mdx-remote/serialize'
    import { MDXRemote } from 'next-mdx-remote'
    
    export default function TestPage({ source }) {
      return (
        <div className="wrapper">
          <MDXRemote {...source} />
        </div>
      )
    }
    
    export async function getStaticProps() {
      const source = 'Some **mdx** text, with a component <Test />'
      const mdxSource = await serialize(source)
      return { props: { source: mdxSource } }
    }
  5. Use serialize() with React Server Components (RSC)

    main
    When using next-mdx-remote in a React Server Components environment, pass true as the third argument to serialize. This disables the providerImportSource (defaulting to @mdx-js/react), ensuring that useMDXComponents is not implemented, which is required for RSC compatibility.
  6. Render MDX with custom components in RSC

    main

    In RSC mode, you cannot use MDXProvider. Instead, pass your custom components directly to the components prop of <MDXRemote />. You can create a wrapper component to merge default components with user-provided ones.

    import { MDXRemote } from 'next-mdx-remote/rsc'
    
    const components = {
      h1: (props) => (
        <h1 {...props} className="large-text">
          {props.children}
        </h1>
      ),
    }
    
    export function CustomMDX(props) {
      return (
        <MDXRemote
          {...props}
          components={{ ...components, ...(props.components || {}) }}
        />
      )
    }
    
    // Usage in app/page.js
    export default function Home() {
      return (
        <CustomMDX
          source={`# Hello World\nThis is from Server Components!`}
        />
      )
    }
  7. Render components with dot notation (e.g. motion.div)

    main

    To render components that use dot notation (like framer-motion's motion.div), pass the base object (e.g., motion) into the components prop of <MDXRemote />.

    import { motion } from 'framer-motion'
    import { serialize } from 'next-mdx-remote/serialize'
    import { MDXRemote } from 'next-mdx-remote'
    
    export default function TestPage({ source }) {
      return (
        <div className="wrapper">
          <MDXRemote {...source} components={{ motion }} />
        </div>
      )
    }
    
    export async function getStaticProps() {
      const source = `Some **mdx** text, with a component:
    
    <motion.div animate={{ x: 100 }} />`
      const mdxSource = await serialize(source)
      return { props: { source: mdxSource } }
    }
  8. Implement `next-mdx-remote` with TypeScript

    main

    The library provides native types for TypeScript. You can use the MDXRemoteSerializeResult type to type the result of serialize and the props returned from getStaticProps.

    import type { GetStaticProps } from 'next'
    import { serialize } from 'next-mdx-remote/serialize'
    import { MDXRemote, type MDXRemoteSerializeResult } from 'next-mdx-remote'
    import ExampleComponent from './example'
    
    const components = { ExampleComponent }
    
    interface Props {
      mdxSource: MDXRemoteSerializeResult
    }
    
    export default function ExamplePage({ mdxSource }: Props) {
      return (
        <div>
          <MDXRemote {...mdxSource} components={components} />
        </div>
      )
    }
    
    export const getStaticProps: GetStaticProps<{
      mdxSource: MDXRemoteSerializeResult
    }> = async () => {
      const mdxSource = await serialize('some *mdx* content: <ExampleComponent />')
      return { props: { mdxSource } }
    }
  9. Basic usage of next-mdx-remote

    main

    To use next-mdx-remote, use serialize from next-mdx-remote/serialize inside getStaticProps or getServerSideProps to process the MDX source on the server. Then, pass the resulting source to the <MDXRemote /> component in your page component.

    import { serialize } from 'next-mdx-remote/serialize'
    import { MDXRemote } from 'next-mdx-remote'
    
    import Test from '../components/test'
    
    const components = { Test }
    
    export default function TestPage({ source }) {
      return (
        <div className="wrapper">
          <MDXRemote {...source} components={components} />
        </div>
      )
    }
    
    export async function getStaticProps() {
      // MDX text - can be from a local file, database, anywhere
      const source = 'Some **mdx** text, with a component <Test />'
      const mdxSource = await serialize(source)
      return { props: { source: mdxSource } }
    }
  10. Access MDX frontmatter using compileMDX

    main

    To access frontmatter data outside of the MDX rendering (e.g., to use a title in an <h1> tag elsewhere in your layout), use the compileMDX method from next-mdx-remote/rsc. You must set parseFrontmatter: true in the options object. This method returns an object containing both the content (the rendered MDX) and the frontmatter object.

    import { compileMDX } from 'next-mdx-remote/rsc'
    
    export default async function Home() {
      // Optionally provide a type for your frontmatter object
      const { content, frontmatter } = await compileMDX<{ title: string }>({
        source: `---\ntitle: RSC Frontmatter Example\n---\n# Hello World\nThis is from Server Components!`,
        options: { parseFrontmatter: true },
      })
    
      return (
        <>
          <h1>{frontmatter.title}</h1>
          {content}
        </>
      )
    }