Migration guide from next-mdx-remote
January 29, 2025 ยท View on GitHub
Migration examples for "pages" router
All examples have been taken from next-mdx-remote to show exactly how to migrate the code.
Main example in the github page headline
- 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'
import { Test } from '../mdxComponents'
+ import { ErrorComponent } from '../components'
const components = { Test }
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() {
// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
- const mdxSource = await serialize(source)
+ const mdxSource = await serialize({source})
return { props: { source: mdxSource } }
}
Parsing frontmatter
- 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'
import { Test } from '../mdxComponents'
+ import { ErrorComponent } from '../components'
const components = { Test }
export default function TestPage({ mdxSource }) {
+ if ("error" in mdxSource) {
+ return <ErrorComponent error={mdxSource.error} />;
+ }
return (
<div className="wrapper">
<h1>{mdxSource.frontmatter.title}</h1>
- <MDXRemote {...mdxSource} components={components} />
+ <MDXClient {...mdxSource} components={components} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = `---
title: Test
---
Some **mdx** text, with a component <Test name={frontmatter.title}/>`
- const mdxSource = await serialize(source, { parseFrontmatter: true })
+ const mdxSource = await serialize({source, options: { parseFrontmatter: true }})
return { props: { mdxSource } }
}
Passing custom data to a component with `scope`
- 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'
import { Test } from '../mdxComponents'
+ import { ErrorComponent } from '../components'
const components = { Test }
const data = { product: 'next' }
export default function TestPage({ source }) {
+ if ("error" in source) {
+ return <ErrorComponent error={source.error} />;
+ }
return (
<div className="wrapper">
- <MDXRemote {...source} components={components} scope={data} />
+ <MDXClient {...source} components={components} scope={data} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source =
'Some **mdx** text, with a component using a scope variable <Test product={product} />'
- const mdxSource = await serialize(source)
+ const mdxSource = await serialize({source})
return { props: { source: mdxSource } }
}
Passing `scope` into the `serialize` function instead
- 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'
import { Test } from '../mdxComponents'
+ import { ErrorComponent } from '../components'
const components = { Test }
const data = { product: 'next' }
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() {
// MDX text - can be from a local file, database, anywhere
const source =
'Some **mdx** text, with a component <Test product={product} />'
- const mdxSource = await serialize(source, { scope: data })
+ const mdxSource = await serialize({source, options: { scope: data }})
return { props: { source: mdxSource } }
}
Custom components from MDXProvider
// pages/_app.jsx
- import { MDXProvider } from '@mdx-js/react'
+ import { MDXProvider } from 'next-mdx-remote-client'
import Test from '../mdxComponents/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'
+ import { serialize } from 'next-mdx-remote-client/serialize'
+ import { MDXClient } from 'next-mdx-remote-client'
+ import { ErrorComponent } from '../components'
export default function TestPage({ source }) {
+ if ("error" in source) {
+ return <ErrorComponent error={source.error} />;
+ }
return (
<div className="wrapper">
- <MDXRemote {...source} />
+ <MDXClient {...source} />
</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)
+ const mdxSource = await serialize({source})
return { props: { source: mdxSource } }
}
Component names with dot (e.g. motion.div)
import { motion } from 'framer-motion'
- import { MDXProvider } from '@mdx-js/react'
- import { serialize } from 'next-mdx-remote/serialize'
- import { MDXRemote } from 'next-mdx-remote'
+ import { serialize } from 'next-mdx-remote-client/serialize'
+ import { MDXClient, MDXProvider } from 'next-mdx-remote-client'
+ import { ErrorComponent } from '../components'
export default function TestPage({ source }) {
+ if ("error" in source) {
+ return <ErrorComponent error={source.error} />;
+ }
return (
<div className="wrapper">
- <MDXRemote {...source} components={{ motion }} />
+ <MDXClient {...source} components={{ motion }} />
</div>
)
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = `Some **mdx** text, with a component:
<motion.div animate={{ x: 100 }} />`
- const mdxSource = await serialize(source)
+ const mdxSource = await serialize({source})
return { props: { source: mdxSource } }
}
Lazy hydration
- import { serialize } from 'next-mdx-remote/serialize'
- import { MDXRemote } from 'next-mdx-remote'
+ import { serialize } from 'next-mdx-remote-client/serialize'
+ import { MDXClientLazy } from 'next-mdx-remote-client'
import { Test } from '../mdxComponents'
+ import { ErrorComponent } from '../components'
const components = { Test }
export default function TestPage({ source }) {
+ if ("error" in source) {
+ return <ErrorComponent error={source.error} />;
+ }
return (
<div className="wrapper">
- <MDXRemote {...source} components={components} lazy />
+ <MDXClientLazy {...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)
+ const mdxSource = await serialize({source})
return { props: { source: mdxSource } }
}
With typescript
import { type GetStaticProps } from 'next'
- import { serialize } from 'next-mdx-remote/serialize'
- import { MDXRemote, type MDXRemoteSerializeResult } from 'next-mdx-remote'
+ import { serialize, type SerializeResult } from 'next-mdx-remote-client/serialize'
+ import { MDXClient } from 'next-mdx-remote-client'
import { ExampleComponent } from './mdxComponents'
+ import { ErrorComponent } from '../components'
const components = { ExampleComponent }
- interface Props {
- mdxSource: MDXRemoteSerializeResult
- }
+ type Props = {
+ mdxSource: SerializeResult
+ }
export default function ExamplePage({ mdxSource }: Props) {
+ if ("error" in mdxSource) {
+ return <ErrorComponent error={mdxSource.error} />;
+ }
return (
<div>
+ <MDXRemote {...mdxSource} components={components} />
- <MDXClient {...mdxSource} components={components} />
</div>
)
}
export const getStaticProps: GetStaticProps<{
- mdxSource: MDXRemoteSerializeResult
+ mdxSource: SerializeResult
}> = async () => {
- const mdxSource = await serialize('some *mdx* content: <ExampleComponent />')
+ const mdxSource = await serialize({ source: 'some *mdx* content: <ExampleComponent />'})
return { props: { mdxSource } }
}
Migration examples for "app" router
All examples have been taken from next-mdx-remote to show exactly how to migrate the code.
Basic example for React Server Components (RSC)
- import { MDXRemote } from 'next-mdx-remote/rsc'
+ import { MDXRemote } from 'next-mdx-remote-client/rsc'
+ import { ErrorComponent } from '../components'
// app/page.js
export default function Home() {
return (
<MDXRemote
source={`# Hello from Server Components`}
+ onEror={ErrorComponent}
/>
)
}
Loading state in "app" router
import { Suspense } from 'react'
- import { MDXRemote } from 'next-mdx-remote/rsc'
+ import { MDXRemote } from 'next-mdx-remote-client/rsc'
+ import { ErrorComponent } from '../components'
// app/page.js
export default function Home() {
return (
// In Next.js you can also use `loading.js` instead of <Suspense />
<Suspense fallback={<>Loading...</>}>
<MDXRemote
source={`# Hello from Server Components`}
+ onEror={ErrorComponent}
/>
</Suspense>
)
}
Custom components
// components/mdx-remote.js
- import { MDXRemote } from 'next-mdx-remote/rsc'
+ import { MDXRemote } from 'next-mdx-remote-client/rsc'
+ import { ErrorComponent } from '../components'
const components = {
h1: (props) => (
<h1 {...props} className="large-text">
{props.children}
</h1>
),
}
export function CustomMDX(props) {
return (
<MDXRemote
{...props}
components={{ ...components, ...(props.components || {}) }}
+ onEror={ErrorComponent}
/>
)
}
// app/page.js
import { CustomMDX } from '../components/mdx-remote'
export default function Home() {
return (
<CustomMDX
// h1 now renders with `large-text` className
source={`# Hello from Server Components`}
/>
)
}
Access `frontmatter` and `scope` outside of MDX
// app/page.js
- import { compileMDX } from 'next-mdx-remote/rsc'
+ import { evaluate } from 'next-mdx-remote-client/rsc'
+ import { ErrorComponent } from '../components'
export default async function Home() {
- const { content, frontmatter } = await compileMDX<{ title: string }>({
+ const { content, frontmatter, scope, error } = await evaluate<{ title: string }>({
source: `---
title: RSC Frontmatter Example
---
# Hello from {product}!`,
options: {
parseFrontmatter: true,
scope: {
product: 'Server Components'
},
},
})
+ if (error) {
+ return <ErrorComponent error={error} />;
+ }
return (
<>
<h1>{frontmatter.title}</h1>
+ <h2>{scope.product}</h2>
{content}
</>
)
}
The differences between next-mdx-remote and next-mdx-remote-client
next-mdx-remote-client has additional exported subpaths
๐ฅ next-mdx-remote:
import /* */ from "next-mdx-remote";
import /* */ from "next-mdx-remote/serialize";
import /* */ from "next-mdx-remote/rsc";
๐ฉ next-mdx-remote-client:
import /* */ from "next-mdx-remote-client";
import /* */ from "next-mdx-remote-client/serialize";
import /* */ from "next-mdx-remote-client/rsc";
// additional exported subpaths
import /* */ from "next-mdx-remote-client/csr";
import /* */ from "next-mdx-remote-client/utils";
Note
The next-mdx-remote-client and the next-mdx-remote-client/csr refer to the same.
Use <MDXClient /> in the client side
๐ฅ next-mdx-remote:
It exports the components with the same name <MDXRemote /> for both "app" and "pages" router.
// for "pages" router, it is a client component
import { MDXRemote } from "next-mdx-remote";
// for "app" router, it is a server component
import { MDXRemote } from "next-mdx-remote/rsc";
๐ฉ next-mdx-remote-client:
It exports <MDXClient /> for "pages" router, <MDXRemote /> for "app" router.
// for "pages" router, it is a client component
import { MDXClient } from "next-mdx-remote-client/csr";
// for "app" router, it is a server component
import { MDXRemote } from "next-mdx-remote-client/rsc";
The part associated with Next.js app router
Go to the part associated with Next.js pages router
The differences in module exports
๐ฅ next-mdx-remote:
import { MDXRemote, compileMDX } from "next-mdx-remote/rsc";
๐ฉ next-mdx-remote-client:
import { MDXRemote, evaluate } from "next-mdx-remote-client/rsc";
The differences in type exports
๐ฅ next-mdx-remote:
import type { MDXRemoteProps, CompileMDXResult, MDXRemoteSerializeResult } from "next-mdx-remote/rsc";
๐ฉ next-mdx-remote-client:
import type { MDXRemoteProps, MDXRemoteOptions } from "next-mdx-remote-client/rsc";
import type { EvaluateProps, EvaluateOptions, EvaluateResult } from "next-mdx-remote-client/rsc";
The differences in props
๐ฅ next-mdx-remote:
The compileMDX and MDXRemote take the same props MDXRemoteProps.
type MDXRemoteProps = {
source: VFileCompatible
options?: SerializeOptions
components?: React.ComponentProps<typeof MDXProvider>['components']
}
๐ฉ next-mdx-remote-client:
The evaluate takes EvaluateProps which has a type argument, and the same prop keys but different types.
type EvaluateProps<TScope> = {
source: Compatible;
options?: EvaluateOptions<TScope>;
components?: MDXComponents;
}
The MDXRemote takes MDXRemoteProps which has onError prop in addition to EvaluateProps.
type MDXRemoteProps<TScope> = {
source: Compatible;
options?: EvaluateOptions<TScope>;
components?: MDXComponents;
onError?: React.ComponentType<{ error: Error }>;
}
Use evaluate instead of compileMDX
๐ฅ next-mdx-remote:
The compileMDX takes MDXRemoteProps and returns CompileMDXResult as a promise.
async function compileMDX<Frontmatter>({source, options, components}): Promise<CompileMDXResult> {}
The compileMDX takes one generic type parameter <Frontmatter>.
๐ฉ next-mdx-remote-client:
The evaluate takes EvaluateProps and returns EvaluateResult as a promise.
async function evaluate<Frontmatter, Scope>({source, options, components}): Promise<EvaluateResult> {}
The evaluate takes two generic type parameters <Frontmatter, Scope> (the order matters).
The evaluate and the compileMDX have different results
๐ฅ next-mdx-remote:
The compileMDX returns the MDX content and the frontmatter.
const { content, frontmatter } = await compileMDX<Frontmatter>({source, options, components});
type CompileMDXResult<TFrontmatter> = {
content: React.ReactElement
frontmatter: TFrontmatter
}
๐ฉ next-mdx-remote-client:
The evaluate returns mod object for the exports, scope, and error objects additional to MDX content and the frontmatter.
const { content, mod, frontmatter, scope, error } = await evaluate<Frontmatter, Scope>({source, options, components});
type EvaluateResult<TFrontmatter, TScope> = {
content: React.JSX.Element;
mod: Record<string, unknown>;
frontmatter: TFrontmatter;
scope: TScope;
error?: Error;
}
The differences in options
The next-mdx-remote-client has more options.
Please, note that, the mdxOptions are more or less similar in both but have differences in terms of opinionation for @mdx-js/mdx options.
๐ฅ next-mdx-remote:
The compileMDX and MDXRemote options:
{
mdxOptions?: /* mdx options to be passed into @mdx-js/mdx */
parseFrontmatter?: boolean
scope?: Record<string, unknown>
}
๐ฉ next-mdx-remote-client:
The evaluate and MDXRemote options:
{
mdxOptions?: /* mdx options to be passed into @mdx-js/mdx */
disableExports?: boolean; // <---
disableImports?: boolean; // <---
parseFrontmatter?: boolean;
scope?: Record<string, unknown>;
vfileDataIntoScope?: VfileDataIntoScope; // <---
}
The part associated with Next.js pages router
Go to the part associated with Next.js app router
Serialize function takes named parameters and rsc parameter is removed
๐ฅ next-mdx-remote:
const mdxSource = await serialize(source: VFileCompatible, options: SerializeOptions, rsc: boolean);
๐ฉ next-mdx-remote-client:
const mdxSource = await serialize({ source: Compatible, options: SerializeOptions });
Serialize function has additional options
๐ฅ next-mdx-remote:
type SerializeOptions = {
mdxOptions?: SerializeMdxOptions;
parseFrontmatter?: boolean;
scope?: Record<string, unknown>;
};
๐ฉ next-mdx-remote-client:
type SerializeOptions<TScope> = {
mdxOptions?: SerializeMdxOptions;
disableExports?: boolean; // <---
disableImports?: boolean; // <---
parseFrontmatter?: boolean;
scope?: TScope;
vfileDataIntoScope?: VfileDataIntoScope; // <---
};
Serialize function accepts the generic type parameters in reverse
๐ฅ next-mdx-remote:
const mdxSource = await serialize<Scope, Frontmatter>(/* */);
๐ฉ next-mdx-remote-client:
const mdxSource = await serialize<Frontmatter, Scope>(/* */);
SerializeResult takes the generic type parameters in reverse, as well
๐ฅ next-mdx-remote:
type Props = {
mdxSource: MDXRemoteSerializeResult<Scope, Frontmatter>;
};
๐ฉ next-mdx-remote-client:
type Props = {
mdxSource: SerializeResult<Frontmatter, Scope>;
};
Serialize function returns "error" object
๐ฅ next-mdx-remote:
It has no "error" object in return.
const { compiledSource, frontmatter, scope } = await serialize(/* */);
๐ฉ next-mdx-remote-client:
const { compiledSource, frontmatter, scope, error } = await serialize(/* */);
New function hydrate
๐ฅ next-mdx-remote:
It doesn't have the function
๐ฉ next-mdx-remote-client:
const { content, mod, error } = hydrate(props: HydrateProps): HydrateResult {}
type HydrateProps = {
compiledSource: string;
frontmatter?: Record<string, unknown>;
scope?: Record<string, unknown>;
components?: MDXComponents;
disableParentContext?: boolean;
};
type HydrateResult = {
content: React.JSX.Element;
mod: Record<string, unknown>;
error?: Error;
};
Use MDXClient instead of MDXRemote in "pages" router
๐ฅ next-mdx-remote:
<MDXRemote
compiledSource={compiledSource}
frontmatter={frontmatter}
scope={scope}
components={components}
lazy={lazy} // boolean, optional
/>
๐ฉ next-mdx-remote-client:
<MDXClient
compiledSource={compiledSource}
frontmatter={frontmatter}
scope={scope}
components={components}
onError={onError} // React.ComponentType<{ error: Error }>, optional
/>
For lazy hydration use hydrateLazy or <MDXClientLazy />
๐ฅ next-mdx-remote:
<MDXRemote
compiledSource={compiledSource}
// ..
lazy
/>
๐ฉ next-mdx-remote-client:
const { content, mod, error } = hydrateLazy({compiledSource, /* */ })
or
<MDXClientLazy
compiledSource={compiledSource}
// ..
/>