next-mdx-remote-client

repository·main·Indexed 42 days ago

https://github.com/ipikuka/next-mdx-remote-client

A wrapper of @mdx-js/mdx for Next.js applications to load MDX content, serving as a fork of next-mdx-remote. It provides optimized support for both the App Router (RSC) and Pages Router (CSR) with version 2.1.12 supporting React 19 and version 1.1 supporting React 18. Key features include the MDXRemote component and evaluate function for RSC, and a serialize/hydrate workflow for CSR.

Tokens
17.1K
Snippets
52
Records
64
Agent score
51%

What's inside next-mdx-remote-client

  1. Explore ecosystem plugins for Remark, Rehype, and Recma

    main

    The author of next-mdx-remote-client maintains a variety of plugins for the Unified/Remark/MDX ecosystem that can be used to extend Markdown processing. These include Remark plugins for content structure, Rehype plugins for HTML transformation, and Recma plugins specifically designed to improve MDX compatibility in Next.js applications.

    ### Remark Plugins
    - `remark-flexible-code-titles` (code block titles/containers)
    - `remark-flexible-containers` (custom containers)
    - `remark-ins` (ins element)
    - `remark-flexible-paragraphs` (custom paragraphs)
    - `remark-flexible-markers` (custom mark element)
    - `remark-flexible-toc` (table of contents via Vfile.data)
    - `remark-mdx-remove-esm` (remove import/export statements)
    - `remark-mdx-remove-expressions` (remove MDX expressions in {})
    
    ### Rehype Plugins
    - `rehype-pre-language` (language info on pre elements)
    - `rehype-highlight-code-lines` (line numbers and highlighting)
    - `rehype-code-meta` (copy meta to properties)
    - `rehype-image-toolkit` (enhanced image/media handling)
    
    ### Recma Plugins
    - `recma-mdx-escape-missing-components` (default missing components to `() => null`)
    - `recma-mdx-change-props` (renames `props` to `_props` for safer expression usage in Next.js)
    - `recma-mdx-change-imports` (converts relative asset imports to string URLs)
    - `recma-mdx-import-media` (converts media paths to import declarations)
    - `recma-mdx-import-react` (ensures React runtime availability in compiled MDX)
    - `recma-mdx-html-override` (override raw HTML via MDX components)
    - `recma-mdx-interpolate` (enable interpolation in link/image attributes)
  2. Use Recma plugins to improve MDX in Next.js

    main

    When using next-mdx-remote or next-mdx-remote-client within Next.js applications, certain Recma plugins are specifically useful for handling MDX compilation quirks:

    • recma-mdx-change-props: Renames the props parameter to _props in the compiled _createMdxContent function. This allows you to safely use {props.foo} style expressions without conflict.
    • recma-mdx-import-react: Ensures that the React instance and runtime props ({React, jsx, jsxs, jsxDev, Fragment}) are available to dynamically imported components in the compiled MDX source.
    • recma-mdx-escape-missing-components: Prevents runtime errors by setting the default value of missing MDX components to () => null.
  3. Understand the CSR and RSC distinction

    main

    The package distinguishes between two primary environments to ensure complete isolation between client-side and server-side logic:

    • csr (Client Side Rendering): Related to the Next.js pages router. Use this for client-side logic.
    • rsc (React Server Component): Related to the Next.js app router. Use this for server-side logic.

    When choosing imports, ensure you are using the subpath that matches your router architecture.

  4. Use MDXClientLazy and hydrateLazy for deferred hydration

    main

    The package provides MDXClientLazy and hydrateLazy for performance optimization. These versions defer the hydration process to the browser using window.requestIdleCallback within a useEffect.

    Benefits:

    • Improves initial load time by serving static markup immediately.
    • Defers heavy component hydration until the browser is idle.

    Trade-offs:

    • Adds a wrapping <div> around the rendered MDX to prevent hydration mismatches.
    • If using hydrateLazy, the mod object (exports) will be empty {} on the first render and will only contain data on the second render.
    • May introduce delays in interactivity for dynamic content.
    import { MDXClientLazy } from 'next-mdx-remote-client/csr';
  5. Migrate to `next-mdx-remote-client` in Next.js Pages Router

    main

    When migrating from next-mdx-remote to next-mdx-remote-client in the Next.js Pages Router:

    1. Update Component: Replace <MDXRemote /> with <MDXClient />.
    2. Update Imports: Use next-mdx-remote-client/csr for the client-side component.
    3. Update serialize function:
      • It now accepts a single object argument instead of multiple positional arguments.
      • The rsc parameter has been removed.
      • Generic type parameters are now passed in the order <Frontmatter, Scope> (reversed from the old version).
      • It now returns an error object in the result.
    4. Lazy Hydration: Instead of the lazy prop, use the hydrateLazy function or the <MDXClientLazy /> component.
    // For "pages" router, it is a client component
    - import { MDXRemote } from "next-mdx-remote"; 
    + import { MDXClient } from "next-mdx-remote-client/csr"; 
    
    <MDXClient
      compiledSource={compiledSource}
      frontmatter={frontmatter}
      scope={scope}
      components={components}
    + onError={onError}
    />
  6. Migrate to `next-mdx-remote-client` in Next.js App Router

    main

    When migrating from next-mdx-remote to next-mdx-remote-client in the Next.js App Router, follow these key changes:

    1. Update Imports: Change next-mdx-remote/rsc to next-mdx-remote-client/rsc.
    2. Use evaluate instead of compileMDX: The evaluate function replaces compileMDX. It supports two generic type parameters: <Frontmatter, Scope> (note the order change). It returns content, frontmatter, scope, mod, and error.
    3. Error Handling: Use the onError prop on the <MDXRemote /> component to handle errors gracefully.
    4. Component Naming: In the App Router, continue using <MDXRemote /> (which is a Server Component).
    - import { MDXRemote } from 'next-mdx-remote/rsc'
    + import { MDXRemote } from 'next-mdx-remote-client/rsc'
    + import { ErrorComponent } from '../components'
    
    export default function Home() {
      return (
        <MDXRemote
          source={`# Hello`}
    +     onError={ErrorComponent}    
        />
      )
    }
  7. Install next-mdx-remote-client

    main

    Install the package using npm or yarn. Note that the version you install depends on your React version:

    • React 18 users: Install version 1 (v1.1).
    • React 19 users: Install version 2 (v2.1).

    This package is ESM only and requires Node.js version 18.18+ (though version 20.9.0+ is recommended to align with Next.js requirements).

    # General installation
    npm install next-mdx-remote-client
    
    # Specifically for React 18 users
    npm install next-mdx-remote-client@^1
    
    # Specifically for React 19 users
    npm install next-mdx-remote-client@^2
    
    # Using yarn
    yarn add next-mdx-remote-client
  8. Migrate TypeScript types for serialization

    main

    When using TypeScript, the type for the serialization result has changed. Replace MDXRemoteSerializeResult from next-mdx-remote with SerializeResult from next-mdx-remote-client/serialize.

    - import { MDXRemote, type MDXRemoteSerializeResult } from 'next-mdx-remote'
    + import { MDXClient } from 'next-mdx-remote-client'
    + import { serialize, type SerializeResult } from 'next-mdx-remote-client/serialize'
    
    - interface Props { mdxSource: MDXRemoteSerializeResult }
    + type Props = { mdxSource: SerializeResult }
    
    - export const getStaticProps: GetStaticProps<{ mdxSource: MDXRemoteSerializeResult }> = ...
    + export const getStaticProps: GetStaticProps<{ mdxSource: SerializeResult }> = ...
  9. Use MDXClientLazy for lazy hydration

    main

    To implement lazy hydration (similar to the lazy prop in next-mdx-remote), use the <MDXClientLazy /> component from next-mdx-remote-client instead of <MDXClient />.

    - import { MDXRemote } from 'next-mdx-remote'
    + import { MDXClientLazy } from 'next-mdx-remote-client'
    
    - <MDXRemote {...source} components={components} lazy />
    + <MDXClientLazy {...source} components={components} />
  10. Migrate from next-mdx-remote to next-mdx-remote-client in Pages Router

    main

    When migrating from next-mdx-remote to next-mdx-remote-client in a Next.js Pages Router project, you must update your imports and change how the serialize function is called.

    Key changes:

    1. Imports: Change next-mdx-remote to next-mdx-remote-client and next-mdx-remote/serialize to next-mdx-remote-client/serialize.
    2. Component: Replace <MDXRemote /> with <MDXClient />.
    3. Serialization: The serialize function now expects an object containing the source string, rather than the string itself. Use serialize({ source }).
    4. Error Handling: The source prop (the result of serialization) may now contain an error key. You should check for this before rendering to prevent crashes.
    - import { serialize } from 'next-mdx-remote/serialize'
    - import { MDXRemote } from 'next-mdx-remote'
    
    + import { serialize } from 'next-mdx-remote-client/serialize'
    + import { MDXClient } from 'next-mdx-remote-client'
    
    export default function TestPage({ source }) {
    + if ("error" in source) {
    +   return <ErrorComponent error={source.error} />;
    + }
    
      return (
        <div className="wrapper">
    -     <MDXRemote {...source} components={components} />
    +     <MDXClient {...source} components={components} />
        </div>
      )
    }
    
    export async function getStaticProps() {
      const source = 'Some **mdx** text'
    - const mdxSource = await serialize(source)
    + const mdxSource = await serialize({source})
      return { props: { source: mdxSource } }
    }
  11. Use MDX in Next.js App Router

    main

    For Next.js App Router, next-mdx-remote-client provides two main ways to render MDX content on the server side via the next-mdx-remote-client/rsc subpath:

    1. MDXRemote component: Use this for simple rendering when you don't need to access MDX exports, frontmatter, or the mutated scope in your JSX.
    2. evaluate function: Use this when you need to extract exports, frontmatter, or the mutated scope (e.g., data added by remark plugins) to use within your React component logic.

    Both are designed for React Server Components (RSC).

    import { evaluate, MDXRemote } from "next-mdx-remote-client/rsc";
  12. Secure untrusted MDX content

    main

    Because MDX compiles to executable JavaScript, rendering untrusted MDX can lead to Cross-Site Scripting (XSS) and Remote Code Execution (RCE).

    Mitigation Strategies

    1. Remove Dangerous Expressions: Use the remark-mdx-remove-expressions plugin at the remark stage to strip executable or dangerous MDX expressions (like JavaScript logic) before compilation.
    2. Disable ESM Blocks: If you do not control the content, disable import and export declarations (mdxjsEsm) via MDX options to prevent unauthorized module loading.
    3. Sanitize Input: Always treat MDX as executable code. If the source is not strictly trusted, apply sanitization or restrictions before passing it to the renderer.
    4. Content Security Policy (CSP): If your site uses a strict CSP, you may need to allow unsafe-eval in your script-src policy, as the MDX runtime uses Reflect.construct (similar to eval) for evaluation. Be aware of the security implications of enabling unsafe-eval.
    import remarkMdxRemoveExpressions from "remark-mdx-remove-expressions";
    
    // Example: Removing dangerous expressions during compilation
    {
      mdxOptions: {
        remarkPlugins: [
          [remarkMdxRemoveExpressions, { onlyDangerousExpressions: true }]
        ]
      }
    }