Skip to content

Commit 67a221f

Browse files
authored
next-mdx-remote v5 (#448)
* feat: update to MDX v3 * feat: remove rollup * feat: replace jest with vitest * ci: run tests on all PRs * fix: move @babel/code-frame to deps * fix: add extension to polyfill import * fix: add extension to type imports * feat: restore useDynamicImport * feat: add stable release workflow * docs: update README * feat: allow react v19 * chore: update changeset
1 parent 5ca1064 commit 67a221f

23 files changed

+7067
-28931
lines changed

.changeset/empty-experts-change.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'next-mdx-remote': major
3+
---
4+
5+
Update to MDX v3 and add support for React 19. Fix various TypeScript errors. Remove usage of Rollup.

.github/workflows/release.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
release:
10+
uses: hashicorp/web-platform-packages/.github/workflows/release.yml@466e1433855584b2768858f3ef9915d1a2a81cd1
11+
secrets:
12+
CHANGESETS_PAT: ${{ secrets.CHANGESETS_PAT }}
13+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/test.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
name: 'Test'
22
on:
33
pull_request:
4-
branches: [main]
54
push:
65
branches: [main]
76

@@ -21,5 +20,5 @@ jobs:
2120
- name: Install and build
2221
run: |
2322
npm ci
24-
- name: Run Jest
23+
- name: Run Tests
2524
run: npm run test

README.md

+102-19
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
# next-mdx-remote
66
7-
A set of light utilities allowing mdx to be loaded within `getStaticProps` or `getServerSideProps` and hydrated correctly on the client.
7+
A set of light utilities allowing MDX to be loaded within `getStaticProps` or `getServerSideProps` and hydrated correctly on the client.
88
99
-->
1010

@@ -15,11 +15,15 @@ A set of light utilities allowing mdx to be loaded within `getStaticProps` or `g
1515
## Installation
1616

1717
```sh
18-
# using npm
19-
npm i next-mdx-remote
18+
npm install next-mdx-remote
19+
```
20+
21+
If using with Turbopack, you'll need to add the following to your `next.config.js` until [this issue](https://github.com/vercel/next.js/issues/64525) is resolved:
2022

21-
# using yarn
22-
yarn add next-mdx-remote
23+
```diff
24+
const nextConfig = {
25+
+ transpilePackages: ['next-mdx-remote'],
26+
}
2327
```
2428

2529
## Examples
@@ -50,7 +54,7 @@ export async function getStaticProps() {
5054

5155
While it may seem strange to see these two in the same file, this is one of the cool things about Next.js -- `getStaticProps` and `TestPage`, while appearing in the same file, run in two different places. Ultimately your browser bundle will not include `getStaticProps` at all, or any of the functions it uses only on the server, so `serialize` will be removed from the browser bundle entirely.
5256

53-
> **IMPORTANT**: Be very careful about putting any `mdx-remote` code into a separate "utilities" file. Doing so will likely cause issues with nextjs' code splitting abilities - it must be able to cleanly determine what is used only on the server side and what should be left in the client bundle. If you put `mdx-remote` code into an external utilities file and something is broken, remove it and start from the simple example above before filing an issue.
57+
> **IMPORTANT**: Be very careful about putting any `next-mdx-remote` code into a separate "utilities" file. Doing so will likely cause issues with Next.js' code splitting abilities - it must be able to cleanly determine what is used only on the server side and what should be left in the client bundle. If you put `next-mdx-remote` code into an external utilities file and something is broken, remove it and start from the simple example above before filing an issue.
5458
5559
### Additional Examples
5660

@@ -290,15 +294,15 @@ This library exposes a function and a component, `serialize` and `<MDXRemote />`
290294

291295
- **`serialize(source: string, { mdxOptions?: object, scope?: object, parseFrontmatter?: boolean })`**
292296

293-
**`serialize`** consumes a string of MDX. It can also optionally be passed options which are [passed directly to MDX](https://mdxjs.com/docs/extending-mdx/), and a scope object that can be included in the mdx scope. The function returns an object that is intended to be passed into `<MDXRemote />` directly.
297+
**`serialize`** consumes a string of MDX. It can also optionally be passed options which are [passed directly to MDX](https://mdxjs.com/docs/extending-mdx/), and a scope object that can be included in the MDX scope. The function returns an object that is intended to be passed into `<MDXRemote />` directly.
294298

295299
```ts
296300
serialize(
297301
// Raw MDX contents as a string
298302
'# hello, world',
299303
// Optional parameters
300304
{
301-
// made available to the arguments of any custom mdx component
305+
// made available to the arguments of any custom MDX component
302306
scope: {},
303307
// MDX's available options, see the MDX docs for more info.
304308
// https://mdxjs.com/packages/mdx/#compilefile-options
@@ -307,7 +311,7 @@ This library exposes a function and a component, `serialize` and `<MDXRemote />`
307311
rehypePlugins: [],
308312
format: 'mdx',
309313
},
310-
// Indicates whether or not to parse the frontmatter from the mdx source
314+
// Indicates whether or not to parse the frontmatter from the MDX source
311315
parseFrontmatter: false,
312316
}
313317
)
@@ -342,9 +346,9 @@ Note: `th/td` won't work because of the "/" in the component name.
342346

343347
## Background & Theory
344348

345-
There isn't really a good default way to load mdx files in a Next.js app. Previously, we wrote [`next-mdx-enhanced`](https://github.com/hashicorp/next-mdx-enhanced) in order to be able to render your MDX files into layouts and import their front matter to create index pages.
349+
There isn't really a good default way to load MDX files in a Next.js app. Previously, we wrote [`next-mdx-enhanced`](https://github.com/hashicorp/next-mdx-enhanced) in order to be able to render your MDX files into layouts and import their front matter to create index pages.
346350

347-
This workflow from mdx-enhanced was fine, but introduced a few limitations that we have removed with `next-mdx-remote`:
351+
This workflow from `next-mdx-enhanced` was fine, but introduced a few limitations that we have removed with `next-mdx-remote`:
348352

349353
- **The file content must be local.** You cannot store MDX files in another repo, a database, etc. For a large enough operation, there will end up being a split between those authoring content and those working on presentation of the content. Overlapping these two concerns in the same repo makes a more difficult workflow for everyone.
350354
- **You are bound to filesystem-based routing.** Your pages are generated with urls according to their locations. Or maybe you remap them using `exportPathMap`, which creates confusion for authors. Regardless, moving pages around in any way breaks things -- either the page's url or your `exportPathMap` configuration.
@@ -353,13 +357,13 @@ This workflow from mdx-enhanced was fine, but introduced a few limitations that
353357

354358
So, `next-mdx-remote` changes the entire pattern so that you load your MDX content not through an import, but rather through `getStaticProps` or `getServerProps` -- you know, the same way you would load any other data. The library provides the tools to serialize and hydrate the MDX content in a manner that is performant. This removes all of the limitations listed above, and does so at a significantly lower cost -- `next-mdx-enhanced` is a very heavy library with a lot of custom logic and [some annoying limitations](https://github.com/hashicorp/next-mdx-enhanced/issues/17). Our informal testing has shown build times reduced by 50% or more.
355359

356-
Since this project was initially created, Kent C Dodds has made a similar project, [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler). This library supports imports and exports within a mdx file (as long as you manually read each imported file and pass its contents) and automatically processes frontmatter. If you have a lot of files that all import and use different components, you may benefit from using `mdx-bundler`, as `next-mdx-remote` currently only allows components to be imported and made available across all pages. It's important to note that this functionality comes with a cost though - `mdx-bundler`'s output is at least 400% larger than the output from `next-mdx-remote` for basic markdown content.
360+
Since this project was initially created, Kent C. Dodds has made a similar project, [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler). This library supports imports and exports within a MDX file (as long as you manually read each imported file and pass its contents) and automatically processes frontmatter. If you have a lot of files that all import and use different components, you may benefit from using `mdx-bundler`, as `next-mdx-remote` currently only allows components to be imported and made available across all pages. It's important to note that this functionality comes with a cost though - `mdx-bundler`'s output is at least 400% larger than the output from `next-mdx-remote` for basic markdown content.
357361

358362
### How Can I Build A Blog With This?
359363

360-
Data has shown that 99% of use cases for all developer tooling are building unnecessarily complex personal blogs. Just kidding. But seriously, if you are trying to build a blog for personal or small business use, consider just using normal html and css. You definitely do not need to be using a heavy full-stack javascript framework to make a simple blog. You'll thank yourself later when you return to make an update in a couple years and there haven't been 10 breaking releases to all of your dependencies.
364+
Data has shown that 99% of use cases for all developer tooling are building unnecessarily complex personal blogs. Just kidding. But seriously, if you are trying to build a blog for personal or small business use, consider just using normal HTML and CSS. You definitely do not need to be using a heavy full-stack JavaScript framework to make a simple blog. You'll thank yourself later when you return to make an update in a couple years and there haven't been 10 breaking releases to all of your dependencies.
361365

362-
If you really insist though, check out [our official nextjs example implementation](https://github.com/vercel/next.js/tree/canary/examples/with-mdx-remote). 💖
366+
If you really insist though, check out [our official Next.js example implementation](https://github.com/vercel/next.js/tree/canary/examples/with-mdx-remote). 💖
363367

364368
## Caveats
365369

@@ -388,9 +392,9 @@ This project does include native types for TypeScript use. Both `serialize` and
388392
Below is an example of a simple implementation in TypeScript. You may not need to implement the types exactly in this way for every configuration of TypeScript - this example is just a demonstration of where the types could be applied if needed.
389393

390394
```tsx
391-
import { GetStaticProps } from 'next'
395+
import type { GetStaticProps } from 'next'
392396
import { serialize } from 'next-mdx-remote/serialize'
393-
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
397+
import { MDXRemote, type MDXRemoteSerializeResult } from 'next-mdx-remote'
394398
import ExampleComponent from './example'
395399

396400
const components = { ExampleComponent }
@@ -417,9 +421,6 @@ export const getStaticProps: GetStaticProps<{
417421

418422
## React Server Components (RSC) & Next.js `app` Directory Support
419423

420-
> **Warning**
421-
> We consider the `next-mdx-remote/rsc` API to be unstable. Use at your own discretion, and be aware that the API and behavior might change between minor and/or patch releases.
422-
423424
Usage of `next-mdx-remote` within server components, and specifically within Next.js's `app` directory, is supported by importing from `next-mdx-remote/rsc`. Previously, the serialization and render steps were separate, but going forward RSC makes this separation unnecessary.
424425

425426
Some noteworthy differences:
@@ -543,6 +544,88 @@ This is from Server Components!
543544
}
544545
```
545546

547+
## Alternatives
548+
549+
`next-mdx-remote` is opinionated in what features it supports. If you need additional features not provided by `next-mdx-remote`, here are a few alternatives to consider:
550+
551+
- [`mdx-bundler`](https://github.com/kentcdodds/mdx-bundler)
552+
- [`next-mdx-remote-client`](https://github.com/ipikuka/next-mdx-remote-client)
553+
- [`remote-mdx`](https://github.com/devjiwonchoi/remote-mdx)
554+
555+
### You Might Not Need `next-mdx-remote`
556+
557+
If you're using React Server Components and just trying to use basic MDX with custom components, you don't need anything other than the core MDX library.
558+
559+
```js
560+
import { compile, run } from '@mdx-js/mdx'
561+
import * as runtime from 'react/jsx-runtime'
562+
import ClientComponent from './components/client'
563+
564+
// MDX can be retrieved from anywhere, such as a file or a database.
565+
const mdxSource = `# Hello, world!
566+
<ClientComponent />
567+
`
568+
569+
export default async function Page() {
570+
// Compile the MDX source code to a function body
571+
const code = String(
572+
await compile(mdxSource, { outputFormat: 'function-body' })
573+
)
574+
// You can then either run the code on the server, generating a server
575+
// component, or you can pass the string to a client component for
576+
// final rendering.
577+
578+
// Run the compiled code with the runtime and get the default export
579+
const { default: MDXContent } = await run(code, {
580+
...runtime,
581+
baseUrl: import.meta.url,
582+
})
583+
584+
// Render the MDX content, supplying the ClientComponent as a component
585+
return <MDXContent components={{ ClientComponent }} />
586+
}
587+
```
588+
589+
You can also simplify this approach with `evaluate`, which compiles and runs code in a single call if you don't plan on passing the compiled string to a database or client component.
590+
591+
```js
592+
import { evaluate } from '@mdx-js/mdx'
593+
import * as runtime from 'react/jsx-runtime'
594+
import ClientComponent from './components/client'
595+
596+
// MDX can be retrieved from anywhere, such as a file or a database.
597+
const mdxSource = `
598+
export const title = "MDX Export Demo";
599+
600+
# Hello, world!
601+
<ClientComponent />
602+
603+
export function MDXDefinedComponent() {
604+
return <p>MDX-defined component</p>;
605+
}
606+
`
607+
608+
export default async function Page() {
609+
// Run the compiled code
610+
const {
611+
default: MDXContent,
612+
MDXDefinedComponent,
613+
...rest
614+
} = await evaluate(mdxSource, runtime)
615+
616+
console.log(rest) // logs { title: 'MDX Export Demo' }
617+
618+
// Render the MDX content, supplying the ClientComponent as a component, and
619+
// the exported MDXDefinedComponent.
620+
return (
621+
<>
622+
<MDXContent components={{ ClientComponent }} />
623+
<MDXDefinedComponent />
624+
</>
625+
)
626+
}
627+
```
628+
546629
## License
547630

548631
[Mozilla Public License Version 2.0](./LICENSE)

0 commit comments

Comments
 (0)