From 08b136159d6129825423949fba3ac5f3a7d9aeb6 Mon Sep 17 00:00:00 2001 From: molebox Date: Wed, 3 Nov 2021 10:19:02 +0100 Subject: [PATCH 1/6] Added guides for setting up Next.js with MDX --- docs/advanced-features/using-mdx/index.md | 50 ++++ .../using-mdx/mdx-bundler.md | 232 ++++++++++++++++++ .../using-mdx/next-mdx-remote.md | 174 +++++++++++++ docs/advanced-features/using-mdx/next-mdx.md | 143 +++++++++++ 4 files changed, 599 insertions(+) create mode 100644 docs/advanced-features/using-mdx/index.md create mode 100644 docs/advanced-features/using-mdx/mdx-bundler.md create mode 100644 docs/advanced-features/using-mdx/next-mdx-remote.md create mode 100644 docs/advanced-features/using-mdx/next-mdx.md diff --git a/docs/advanced-features/using-mdx/index.md b/docs/advanced-features/using-mdx/index.md new file mode 100644 index 0000000000000..25c0b3ced6510 --- /dev/null +++ b/docs/advanced-features/using-mdx/index.md @@ -0,0 +1,50 @@ +--- +description: Using MDX with your Next.js site. +--- + +# Using MDX with Next.js + +Next.js supports MDX through a number of different means, this page will outline some of the ways you can begin intergrating MDX into your Next.js project. + +## What is MDX? + +MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life. + +### MDX plugins + +Internally MDX uses remark and rehype. Remark is a markdown processor powered by a plugins ecosystem. This plugin ecosystem lets you parse code, transform `HTML` elements, change syntax, extract frontmatter, and more. + +Rehype is an `HTML` processor, also powered by a plugin ecosystem. Similar to remark, these plugins let you manipulate, sanitize, compile and configure all types of data, elements and content. + +To use a plugin from either remark or rehype, you will need to add it to the MDX packages config. Each package has a different way to configure its API. + +## MDX packages + +The following guides show how you can integrate MDX with your Next.js project. + +
+ + @next/mdx + Learn how to use @next/mdx in Next.js. + +
+ +
+ + next-mdx-remote + Learn how to use next-mdx-remote in Next.js. + +
+ +
+ + mdx-bundler + Learn how to use mdx-bundler in Next.js. + +
+ +## Helpful links + +- [MDX](https://mdxjs.com) +- [remark](https://github.com/remarkjs/remark) +- [rehype](https://github.com/rehypejs/rehype) diff --git a/docs/advanced-features/using-mdx/mdx-bundler.md b/docs/advanced-features/using-mdx/mdx-bundler.md new file mode 100644 index 0000000000000..b94905b54b592 --- /dev/null +++ b/docs/advanced-features/using-mdx/mdx-bundler.md @@ -0,0 +1,232 @@ +--- +description: Learn how to use mdx-bundler in your Next.js project. +--- + +# `mdx-bundler` + +The `mdx-bundler` package does **not** require a global configuration, instead it bundles the dependencies of your MDX files allowing you to directly import components in the MDX file. `mdx-bundler` also supports soucing data from anywhere, including local files, external APIs or databases. + +Note that it uses [esbuild](https://esbuild.github.io) under the hood, meaning this will need to be installed as a dependency. + +Also note that one of `mdx-bundler`'s dependencies requires a working node-gyp setup to be able to install correctly. + +## Setup `mdx-bundler` in Next.js + +The following shows how to setup `mdx-bundler` for use with a blog. + +1. Install the required packages: + + ```terminal + npm i mdx-bundler esbuild gray-matter + + #or + + yarn add mdx-bundler esbuild gray-matter + ``` + +2. Create an `mdx.js` file. The name is arbitrary and the file can be placed anywhere within your projects file structure. In this file you will setup `mdx-bundler` and create some utility functions that will get your MDX files content, frontmatter, and as a nice touch, provide a previous and next path. + +3. The below example assumes you have a directory within `/pages` called `/posts`. Begin by creating a function that will read the file contents from a specified path. In this case, a path to your blog posts: + + ```js + import fs from 'fs' + import path from 'path' + import matter from 'gray-matter' + import { bundleMDX } from 'mdx-bundler' + + export const ROOT = process.cwd() + export const POSTS_PATH = path.join(process.cwd(), 'pages/posts') + + export function getFileContent(contentPath, filename) { + return fs.readFileSync(path.join(contentPath, filename), 'utf8') + } + ``` + +4. Because esbuild relies upon `__dirname` to find the executable file, you have to override the esbuild executable path with an environment variable, otherwise your development builds will fail. To solve this you can set the variable so that it only applies for the duration of the process: + + ```js + const getCompiledMDX = async (content: string) => { + if (process.platform === 'win32') { + process.env.ESBUILD_BINARY_PATH = path.join( + ROOT, + 'node_modules', + 'esbuild', + 'esbuild.exe' + ) + } else { + process.env.ESBUILD_BINARY_PATH = path.join( + ROOT, + 'node_modules', + 'esbuild', + 'bin', + 'esbuild' + ) + } + } + ``` + +5. Inside the same `getCompiledMDX` function, return the `bundleMDX` function specifying the `xdmOptions`. These options include adding any remark or rehype plugins you want to use, as well as applying any esbuild configurations: + + ```js + const getCompiledMDX = async (content: string) => { + if (process.platform === 'win32') { + process.env.ESBUILD_BINARY_PATH = path.join( + ROOT, + 'node_modules', + 'esbuild', + 'esbuild.exe' + ) + } else { + process.env.ESBUILD_BINARY_PATH = path.join( + ROOT, + 'node_modules', + 'esbuild', + 'bin', + 'esbuild' + ) + } + // Add your remark and rehype plugins here + const remarkPlugins = [] + const rehypePlugins = [] + + try { + return await bundleMDX(content, { + xdmOptions(options) { + options.remarkPlugins = [ + ...(options.remarkPlugins ?? []), + ...remarkPlugins, + ] + options.rehypePlugins = [ + ...(options.rehypePlugins ?? []), + ...rehypePlugins, + ] + + return options + }, + }) + } catch (error) { + throw new Error(error) + } + } + ``` + +6. Create two new utility functions which will be used to get the MDX file content based on its path, and return the content and frontmatter: + + ```js + export const getSingleArticle = async (contentPath, slug) => { + // Get the content of the MDX file by its path + const source = getFileContent(contentPath, `${slug}.mdx`) + // Using another utility function, get all the post files and calculate any next or previous posts + const allArticles = getAllArticles(contentPath) + const articleIndex = allArticles.findIndex( + (article) => article.slug === slug + ) + const previousArticle = allArticles[articleIndex - 1] + const nextArticle = allArticles[articleIndex + 1] + // Destructure the code (file content) and frontmatter + const { code, frontmatter } = await getCompiledMDX(source) + // Return the frontmatter, file content, next and previous slugs if they exist + return { + frontmatter, + code, + previousArticle: previousArticle || null, + nextArticle: nextArticle || null, + } + } + + export const getAllArticles = (contentPath: string) => { + return fs + .readdirSync(POSTS_PATH) + .filter((path) => /\.mdx?$/.test(path)) + .map((fileName) => { + const source = getFileContent(contentPath, fileName) + const slug = fileName.replace(/\.mdx?$/, '') + + const { data } = matter(source) + + return { + frontmatter: data, + slug: slug, + } + }) + } + ``` + +7. These utility functions can now be used to create static pages from your MDX files. Inside your [dynamic route](/docs/routing/dynamic-routes) (`[slug].js`) import that `getSingleArticle` utility function and pass in the path to where your MDX files are, and the slug: + + ```js + import { getSingleArticle, POSTS_PATH } from 'utlis' + + export const getStaticProps = async ({ params }) => { + const post = await getSingleArticle(POSTS_PATH, params.slug) + return { + props: { + ...post, + slug: params.slug, + }, + } + } + ``` + +8. Finally, inside the same dynamic route file, import the `getMDXComponent` function and pass in the MDX file contents, retrieved and spread in the previous step as `...post` from the components props as `code`. Using Reacts `useMemo` hook create a component which will render the contents of your MDX file. + + ```jsx + import { getMDXComponent } from 'mdx-bundler/client' + import { getSingleArticle, POSTS_PATH } from 'utlis' + + export default function Post({ code, frontmatter }) { + const Component = React.useMemo(() => getMDXComponent(code), [code]) + + return ( +
+

{frontmatter.title}

+ +
+ ) + } + + export const getStaticProps = async ({ params }) => { + const post = await getSingleArticle(POSTS_PATH, params.slug) + return { + props: { + ...post, + slug: params.slug, + }, + } + } + ``` + +## Adding custom components + +You can now import a React component directly inside your MDX page. When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name. + +```jsx +// Other imports +import { Heading, Text, Pre, Code, Table } from 'my-components' + +const ResponsiveImage = (props) => ( + {props.alt} +) + +const components = { + img: ResponsiveImage, + h1: Heading.H1, + h2: Heading.H2, + p: Text, + code: Pre, + inlineCode: Code, +} + +export default function Post({ code, frontmatter }) { + const Component = React.useMemo(() => getMDXComponent(code), [code]) + + return ( +
+

{frontmatter.title}

+ +
+ ) +} + +// Data fetching functions... +``` diff --git a/docs/advanced-features/using-mdx/next-mdx-remote.md b/docs/advanced-features/using-mdx/next-mdx-remote.md new file mode 100644 index 0000000000000..3886b7a51086e --- /dev/null +++ b/docs/advanced-features/using-mdx/next-mdx-remote.md @@ -0,0 +1,174 @@ +--- +description: Learn how to use next-mdx-remote in your Next.js project. +--- + +# `next-mdx-remote` + +The `next-mdx-remote` package does **not** require a global configuration, instead it can be loaded on a page by page basis. It uses the Next.js [data fetching](/docs/basic-features/data-fetching.md) techniques to source data from anywhere, this includes local files, external APIs or databases. + +## Setup `next-mdx-remote` in Next.js + +The following steps outline how to setup a static page with MDX content using `next-mdx-remote` in your Next.js project: + +1. Install the required packages: + + ```terminal + npm i next-mdx-remote gray-matter + + #or + + yarn add next-mdx-remote gray-matter + ``` + +2. Inside a page component that will be [statically rendered](/docs/basic-features/data-fetching#getstaticprops-static-generation), import the `serialize` function and `MDXRemote` component from `next-mdx-remote`. The following adds the `mdxOptions` object key to the `serialize` function, allowing you to pass in any plugins: + + ```jsx + import { serialize } from 'next-mdx-remote/serialize' + import { MDXRemote } from 'next-mdx-remote' + + export default function Post({ source }) { + return ( +
+ +
+ ) + } + + export async function getStaticProps() { + // MDX text - can be from a local file, database, anywhere + const source = 'Some **mdx** text, with a component ' + const mdxSource = await serialize(source, { + mdxOptions: { + remarkPlugins: [], + rehypePlugins: [], + }, + }) + return { props: { source: mdxSource } } + } + ``` + +## Using frontmatter, and custom elements + +To access and use frontmatter in your MDX page the `gray-matter` package can be used to parse the data. + +Frontmatter is a yaml like key, value pairing that can be used to store data about a page. + +```md +--- +title: My MDX page +author: Rich Haines +--- + +# My MDX Page +``` + +To parse this data and make it available in your page, import the `gray-matter` package and deconstruct the `content` and `data`. The `content` is the content of the MDX page as a string, and the `data` is the frontmatter. This is then passed to the `serialize` function via the `scope` object and available as a prop on the page component. + +Note that if you are pulling in the MDX content from the file system, then you would use the `content` destructured from `gray-matter`, and this would be passed into the `serialize` function. + +The following example shows how to parse frontmatter data, add custom elements as shortcodes, and get the MDX content from the file system: + +```jsx +import { serialize } from 'next-mdx-remote/serialize' +import { MDXRemote } from 'next-mdx-remote' +import matter from 'gray-matter' +import Heading from 'my-components' +import fs from 'fs' + +const components = { + h1: Heading.H1, +} + +export default function Post({ source, frontmatter }) { + return ( +
+

{frontmatter.title}

+ +
+ ) +} + +export async function getStaticProps({ params }) { + const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`) + const source = fs.readFileSync(postFilePath) + + const { content, data } = matter(source) + + const mdxSource = await serialize(source, { + mdxOptions: { + remarkPlugins: [], + rehypePlugins: [], + }, + scope: data, + }) + return { + props: { + source: mdxSource, + frontMatter: data, + }, + } +} + +export const getStaticPaths = async () => { + const paths = postFilePaths + // Remove file extensions for page paths + .map((path) => path.replace(/\.mdx?$/, '')) + // Map the path into the static paths object required by Next.js + .map((slug) => ({ params: { slug } })) + + return { + paths, + fallback: false, + } +} +``` + +## Caveats + +With `next-mdx-remote` you are unable to directly import components into your MDX file. To work around this, and supply your MDX file with custom components, you can pass them in as shortcodes the same way you would define the custom `HTML` elements mapping: + +```js + // Other imports... + import MyCustomComponent from 'my-components' + import MyCustomComponentWithChildren from 'my-components' + + const components = { + h1: Heading.H1 + MyCustomComponent, + MyCustomComponentWithChildren: (props) => {props.children} + } + + export default function Post({ source, frontmatter }) { + return ( +
+

{frontmatter.title}

+ +
+ ) + } + + // Data fetching functions... +``` + +These two components are now available in your MDX file without needed to import: + +```md +--- +title: My MDX page +author: Rich Haines +--- + +# My MDX page + + + + + +Some markdown content here + + +``` + +## Learn more + +The [`next-mdx-remote`](https://github.com/hashicorp/next-mdx-remote) repository contains more information on how to setup and configure MDX with Next.js. diff --git a/docs/advanced-features/using-mdx/next-mdx.md b/docs/advanced-features/using-mdx/next-mdx.md new file mode 100644 index 0000000000000..bc22e341d863d --- /dev/null +++ b/docs/advanced-features/using-mdx/next-mdx.md @@ -0,0 +1,143 @@ +--- +description: Learn how to use @next/mdx in your Next.js project. +--- + +# `@next/mdx` + +The `@next/mdx` package is configured in the `next.config.js` file at your projects root. **It sources data from local files**, allowing you to create pages with a `.mdx` extension, directly in your `/pages` directory. + +### Setup `@next/mdx` in Next.js + +The following steps outline how to setup `@next/mdx` in your Next.js project: + +1. Install the required packages: + + ```terminal + npm install @next/mdx @mdx-js/loader + + #or + + yarn add @next/mdx @mdx-js/loader + ``` + +2. Require the package and configure to support top level `.mdx` pages. The following adds the `options` object key allowing you to pass in any plugins: + + ```js + // next.config.js + + const withMDX = require('@next/mdx')({ + extension: /\.mdx?$/, + options: { + remarkPlugins: [], + rehypePlugins: [], + }, + }) + module.exports = withMDX({ + pageExtensions: ['js', 'jsx', 'md', 'mdx'], + }) + ``` + +3. Create a new MDX page within the `/pages` directory: + + ```bash + - /pages + - my-mdx-page.mdx + - package.json + ``` + +## Using components, layouts and custom elements + +You can now import a React component directly inside your MDX page. Note that `@next/mdx` does **not** support frontmatter (frontmatter is a yaml like key, value pairing that can be used to store data about a page), instead, you can export data from within the `.mdx` file: + +```md +import { CoolComponent } from 'my-components' + +export const metaData = { +author: 'Rich Haines' +} + +# My MDX page + +This is a list in markdown: + +- One +- Two +- Three + +Checkout my React component: + + +``` + +### Layouts + +To add a layout to your MDX page, create a new component and import it into the MDX page. Then you can wrap the MDx page with your layout component: + +```md +import { CoolComponent, CoolLayoutComponent } from 'my-components' + +export const meta = { +author: 'Rich Haines' +} + +# My MDX page with a layout + +This is a list in markdown: + +- One +- Two +- Three + +Checkout my React component: + + + +export default = ({ children }) => {children} +``` + +### Custom elements + +One of the pleasant aspects of using markdown, is that it maps to native `HTML` elements, making writing fast, and intuitive: + +```md +# H1 heading + +## H2 heading + +This is a list in markdown: + +- One +- Two +- Three +``` + +When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name. + +```jsx +// pages/index.js + +import { MDXProvider } from '@mdx-js/react' +import Image from 'next/image' +import { Heading, Text, Pre, Code, Table } from 'my-components' + +const ResponsiveImage = (props) => ( + {props.alt} +) + +const components = { + img: ResponsiveImage, + h1: Heading.H1, + h2: Heading.H2, + p: Text, + code: Pre, + inlineCode: Code, +} + +export default function Post(props) { + return ( + +
+ + ) +} +``` From 81e0fe5fad867095e1613e118a886a6605cb3299 Mon Sep 17 00:00:00 2001 From: molebox Date: Wed, 3 Nov 2021 12:07:06 +0100 Subject: [PATCH 2/6] Only recommend official package --- .../{using-mdx/next-mdx.md => using-mdx.md} | 27 +- docs/advanced-features/using-mdx/index.md | 50 ---- .../using-mdx/mdx-bundler.md | 232 ------------------ .../using-mdx/next-mdx-remote.md | 174 ------------- 4 files changed, 25 insertions(+), 458 deletions(-) rename docs/advanced-features/{using-mdx/next-mdx.md => using-mdx.md} (73%) delete mode 100644 docs/advanced-features/using-mdx/index.md delete mode 100644 docs/advanced-features/using-mdx/mdx-bundler.md delete mode 100644 docs/advanced-features/using-mdx/next-mdx-remote.md diff --git a/docs/advanced-features/using-mdx/next-mdx.md b/docs/advanced-features/using-mdx.md similarity index 73% rename from docs/advanced-features/using-mdx/next-mdx.md rename to docs/advanced-features/using-mdx.md index bc22e341d863d..ccc9ed8d9a2ec 100644 --- a/docs/advanced-features/using-mdx/next-mdx.md +++ b/docs/advanced-features/using-mdx.md @@ -2,7 +2,23 @@ description: Learn how to use @next/mdx in your Next.js project. --- -# `@next/mdx` +# Using MDX with Next.js + +Next.js supports MDX through a number of different means, this page will outline some of the ways you can begin integrating MDX into your Next.js project. + +## What is MDX? + +MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life. + +### MDX plugins + +Internally MDX uses remark and rehype. Remark is a markdown processor powered by a plugins ecosystem. This plugin ecosystem lets you parse code, transform `HTML` elements, change syntax, extract frontmatter, and more. + +Rehype is an `HTML` processor, also powered by a plugin ecosystem. Similar to remark, these plugins let you manipulate, sanitize, compile and configure all types of data, elements and content. + +To use a plugin from either remark or rehype, you will need to add it to the MDX packages config. + +## `@next/mdx` The `@next/mdx` package is configured in the `next.config.js` file at your projects root. **It sources data from local files**, allowing you to create pages with a `.mdx` extension, directly in your `/pages` directory. @@ -52,7 +68,7 @@ You can now import a React component directly inside your MDX page. Note that `@ ```md import { CoolComponent } from 'my-components' -export const metaData = { +export const meta = { author: 'Rich Haines' } @@ -141,3 +157,10 @@ export default function Post(props) { ) } ``` + +## Helpful links + +- [MDX](https://mdxjs.com) +- [`@next/mdx`](https://www.npmjs.com/package/@next/mdx) +- [remark](https://github.com/remarkjs/remark) +- [rehype](https://github.com/rehypejs/rehype) diff --git a/docs/advanced-features/using-mdx/index.md b/docs/advanced-features/using-mdx/index.md deleted file mode 100644 index 25c0b3ced6510..0000000000000 --- a/docs/advanced-features/using-mdx/index.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -description: Using MDX with your Next.js site. ---- - -# Using MDX with Next.js - -Next.js supports MDX through a number of different means, this page will outline some of the ways you can begin intergrating MDX into your Next.js project. - -## What is MDX? - -MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life. - -### MDX plugins - -Internally MDX uses remark and rehype. Remark is a markdown processor powered by a plugins ecosystem. This plugin ecosystem lets you parse code, transform `HTML` elements, change syntax, extract frontmatter, and more. - -Rehype is an `HTML` processor, also powered by a plugin ecosystem. Similar to remark, these plugins let you manipulate, sanitize, compile and configure all types of data, elements and content. - -To use a plugin from either remark or rehype, you will need to add it to the MDX packages config. Each package has a different way to configure its API. - -## MDX packages - -The following guides show how you can integrate MDX with your Next.js project. - - - - - - - -## Helpful links - -- [MDX](https://mdxjs.com) -- [remark](https://github.com/remarkjs/remark) -- [rehype](https://github.com/rehypejs/rehype) diff --git a/docs/advanced-features/using-mdx/mdx-bundler.md b/docs/advanced-features/using-mdx/mdx-bundler.md deleted file mode 100644 index b94905b54b592..0000000000000 --- a/docs/advanced-features/using-mdx/mdx-bundler.md +++ /dev/null @@ -1,232 +0,0 @@ ---- -description: Learn how to use mdx-bundler in your Next.js project. ---- - -# `mdx-bundler` - -The `mdx-bundler` package does **not** require a global configuration, instead it bundles the dependencies of your MDX files allowing you to directly import components in the MDX file. `mdx-bundler` also supports soucing data from anywhere, including local files, external APIs or databases. - -Note that it uses [esbuild](https://esbuild.github.io) under the hood, meaning this will need to be installed as a dependency. - -Also note that one of `mdx-bundler`'s dependencies requires a working node-gyp setup to be able to install correctly. - -## Setup `mdx-bundler` in Next.js - -The following shows how to setup `mdx-bundler` for use with a blog. - -1. Install the required packages: - - ```terminal - npm i mdx-bundler esbuild gray-matter - - #or - - yarn add mdx-bundler esbuild gray-matter - ``` - -2. Create an `mdx.js` file. The name is arbitrary and the file can be placed anywhere within your projects file structure. In this file you will setup `mdx-bundler` and create some utility functions that will get your MDX files content, frontmatter, and as a nice touch, provide a previous and next path. - -3. The below example assumes you have a directory within `/pages` called `/posts`. Begin by creating a function that will read the file contents from a specified path. In this case, a path to your blog posts: - - ```js - import fs from 'fs' - import path from 'path' - import matter from 'gray-matter' - import { bundleMDX } from 'mdx-bundler' - - export const ROOT = process.cwd() - export const POSTS_PATH = path.join(process.cwd(), 'pages/posts') - - export function getFileContent(contentPath, filename) { - return fs.readFileSync(path.join(contentPath, filename), 'utf8') - } - ``` - -4. Because esbuild relies upon `__dirname` to find the executable file, you have to override the esbuild executable path with an environment variable, otherwise your development builds will fail. To solve this you can set the variable so that it only applies for the duration of the process: - - ```js - const getCompiledMDX = async (content: string) => { - if (process.platform === 'win32') { - process.env.ESBUILD_BINARY_PATH = path.join( - ROOT, - 'node_modules', - 'esbuild', - 'esbuild.exe' - ) - } else { - process.env.ESBUILD_BINARY_PATH = path.join( - ROOT, - 'node_modules', - 'esbuild', - 'bin', - 'esbuild' - ) - } - } - ``` - -5. Inside the same `getCompiledMDX` function, return the `bundleMDX` function specifying the `xdmOptions`. These options include adding any remark or rehype plugins you want to use, as well as applying any esbuild configurations: - - ```js - const getCompiledMDX = async (content: string) => { - if (process.platform === 'win32') { - process.env.ESBUILD_BINARY_PATH = path.join( - ROOT, - 'node_modules', - 'esbuild', - 'esbuild.exe' - ) - } else { - process.env.ESBUILD_BINARY_PATH = path.join( - ROOT, - 'node_modules', - 'esbuild', - 'bin', - 'esbuild' - ) - } - // Add your remark and rehype plugins here - const remarkPlugins = [] - const rehypePlugins = [] - - try { - return await bundleMDX(content, { - xdmOptions(options) { - options.remarkPlugins = [ - ...(options.remarkPlugins ?? []), - ...remarkPlugins, - ] - options.rehypePlugins = [ - ...(options.rehypePlugins ?? []), - ...rehypePlugins, - ] - - return options - }, - }) - } catch (error) { - throw new Error(error) - } - } - ``` - -6. Create two new utility functions which will be used to get the MDX file content based on its path, and return the content and frontmatter: - - ```js - export const getSingleArticle = async (contentPath, slug) => { - // Get the content of the MDX file by its path - const source = getFileContent(contentPath, `${slug}.mdx`) - // Using another utility function, get all the post files and calculate any next or previous posts - const allArticles = getAllArticles(contentPath) - const articleIndex = allArticles.findIndex( - (article) => article.slug === slug - ) - const previousArticle = allArticles[articleIndex - 1] - const nextArticle = allArticles[articleIndex + 1] - // Destructure the code (file content) and frontmatter - const { code, frontmatter } = await getCompiledMDX(source) - // Return the frontmatter, file content, next and previous slugs if they exist - return { - frontmatter, - code, - previousArticle: previousArticle || null, - nextArticle: nextArticle || null, - } - } - - export const getAllArticles = (contentPath: string) => { - return fs - .readdirSync(POSTS_PATH) - .filter((path) => /\.mdx?$/.test(path)) - .map((fileName) => { - const source = getFileContent(contentPath, fileName) - const slug = fileName.replace(/\.mdx?$/, '') - - const { data } = matter(source) - - return { - frontmatter: data, - slug: slug, - } - }) - } - ``` - -7. These utility functions can now be used to create static pages from your MDX files. Inside your [dynamic route](/docs/routing/dynamic-routes) (`[slug].js`) import that `getSingleArticle` utility function and pass in the path to where your MDX files are, and the slug: - - ```js - import { getSingleArticle, POSTS_PATH } from 'utlis' - - export const getStaticProps = async ({ params }) => { - const post = await getSingleArticle(POSTS_PATH, params.slug) - return { - props: { - ...post, - slug: params.slug, - }, - } - } - ``` - -8. Finally, inside the same dynamic route file, import the `getMDXComponent` function and pass in the MDX file contents, retrieved and spread in the previous step as `...post` from the components props as `code`. Using Reacts `useMemo` hook create a component which will render the contents of your MDX file. - - ```jsx - import { getMDXComponent } from 'mdx-bundler/client' - import { getSingleArticle, POSTS_PATH } from 'utlis' - - export default function Post({ code, frontmatter }) { - const Component = React.useMemo(() => getMDXComponent(code), [code]) - - return ( -
-

{frontmatter.title}

- -
- ) - } - - export const getStaticProps = async ({ params }) => { - const post = await getSingleArticle(POSTS_PATH, params.slug) - return { - props: { - ...post, - slug: params.slug, - }, - } - } - ``` - -## Adding custom components - -You can now import a React component directly inside your MDX page. When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name. - -```jsx -// Other imports -import { Heading, Text, Pre, Code, Table } from 'my-components' - -const ResponsiveImage = (props) => ( - {props.alt} -) - -const components = { - img: ResponsiveImage, - h1: Heading.H1, - h2: Heading.H2, - p: Text, - code: Pre, - inlineCode: Code, -} - -export default function Post({ code, frontmatter }) { - const Component = React.useMemo(() => getMDXComponent(code), [code]) - - return ( -
-

{frontmatter.title}

- -
- ) -} - -// Data fetching functions... -``` diff --git a/docs/advanced-features/using-mdx/next-mdx-remote.md b/docs/advanced-features/using-mdx/next-mdx-remote.md deleted file mode 100644 index 3886b7a51086e..0000000000000 --- a/docs/advanced-features/using-mdx/next-mdx-remote.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -description: Learn how to use next-mdx-remote in your Next.js project. ---- - -# `next-mdx-remote` - -The `next-mdx-remote` package does **not** require a global configuration, instead it can be loaded on a page by page basis. It uses the Next.js [data fetching](/docs/basic-features/data-fetching.md) techniques to source data from anywhere, this includes local files, external APIs or databases. - -## Setup `next-mdx-remote` in Next.js - -The following steps outline how to setup a static page with MDX content using `next-mdx-remote` in your Next.js project: - -1. Install the required packages: - - ```terminal - npm i next-mdx-remote gray-matter - - #or - - yarn add next-mdx-remote gray-matter - ``` - -2. Inside a page component that will be [statically rendered](/docs/basic-features/data-fetching#getstaticprops-static-generation), import the `serialize` function and `MDXRemote` component from `next-mdx-remote`. The following adds the `mdxOptions` object key to the `serialize` function, allowing you to pass in any plugins: - - ```jsx - import { serialize } from 'next-mdx-remote/serialize' - import { MDXRemote } from 'next-mdx-remote' - - export default function Post({ source }) { - return ( -
- -
- ) - } - - export async function getStaticProps() { - // MDX text - can be from a local file, database, anywhere - const source = 'Some **mdx** text, with a component ' - const mdxSource = await serialize(source, { - mdxOptions: { - remarkPlugins: [], - rehypePlugins: [], - }, - }) - return { props: { source: mdxSource } } - } - ``` - -## Using frontmatter, and custom elements - -To access and use frontmatter in your MDX page the `gray-matter` package can be used to parse the data. - -Frontmatter is a yaml like key, value pairing that can be used to store data about a page. - -```md ---- -title: My MDX page -author: Rich Haines ---- - -# My MDX Page -``` - -To parse this data and make it available in your page, import the `gray-matter` package and deconstruct the `content` and `data`. The `content` is the content of the MDX page as a string, and the `data` is the frontmatter. This is then passed to the `serialize` function via the `scope` object and available as a prop on the page component. - -Note that if you are pulling in the MDX content from the file system, then you would use the `content` destructured from `gray-matter`, and this would be passed into the `serialize` function. - -The following example shows how to parse frontmatter data, add custom elements as shortcodes, and get the MDX content from the file system: - -```jsx -import { serialize } from 'next-mdx-remote/serialize' -import { MDXRemote } from 'next-mdx-remote' -import matter from 'gray-matter' -import Heading from 'my-components' -import fs from 'fs' - -const components = { - h1: Heading.H1, -} - -export default function Post({ source, frontmatter }) { - return ( -
-

{frontmatter.title}

- -
- ) -} - -export async function getStaticProps({ params }) { - const postFilePath = path.join(POSTS_PATH, `${params.slug}.mdx`) - const source = fs.readFileSync(postFilePath) - - const { content, data } = matter(source) - - const mdxSource = await serialize(source, { - mdxOptions: { - remarkPlugins: [], - rehypePlugins: [], - }, - scope: data, - }) - return { - props: { - source: mdxSource, - frontMatter: data, - }, - } -} - -export const getStaticPaths = async () => { - const paths = postFilePaths - // Remove file extensions for page paths - .map((path) => path.replace(/\.mdx?$/, '')) - // Map the path into the static paths object required by Next.js - .map((slug) => ({ params: { slug } })) - - return { - paths, - fallback: false, - } -} -``` - -## Caveats - -With `next-mdx-remote` you are unable to directly import components into your MDX file. To work around this, and supply your MDX file with custom components, you can pass them in as shortcodes the same way you would define the custom `HTML` elements mapping: - -```js - // Other imports... - import MyCustomComponent from 'my-components' - import MyCustomComponentWithChildren from 'my-components' - - const components = { - h1: Heading.H1 - MyCustomComponent, - MyCustomComponentWithChildren: (props) => {props.children} - } - - export default function Post({ source, frontmatter }) { - return ( -
-

{frontmatter.title}

- -
- ) - } - - // Data fetching functions... -``` - -These two components are now available in your MDX file without needed to import: - -```md ---- -title: My MDX page -author: Rich Haines ---- - -# My MDX page - - - - - -Some markdown content here - - -``` - -## Learn more - -The [`next-mdx-remote`](https://github.com/hashicorp/next-mdx-remote) repository contains more information on how to setup and configure MDX with Next.js. From f416335f8c133668dbec93cb50af06a1417e4ab2 Mon Sep 17 00:00:00 2001 From: molebox Date: Fri, 12 Nov 2021 15:11:42 +0100 Subject: [PATCH 3/6] Added route --- docs/manifest.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/manifest.json b/docs/manifest.json index 1a1f8c38d0917..65d4fdbe2fc2b 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -148,6 +148,10 @@ "title": "Absolute Imports and Module Path Aliases", "path": "/docs/advanced-features/module-path-aliases.md" }, + { + "title": "Using MDX", + "path": "/docs/advanced-features/using-mdx.md" + }, { "title": "AMP Support", "routes": [ From a78ed09629f7ebdac940dab5783e309d2d123a99 Mon Sep 17 00:00:00 2001 From: molebox Date: Wed, 17 Nov 2021 11:51:19 +0100 Subject: [PATCH 4/6] Feedback changes, added why use mdx, html ouput --- docs/advanced-features/using-mdx.md | 51 +++++++++++++++++++---------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/docs/advanced-features/using-mdx.md b/docs/advanced-features/using-mdx.md index ccc9ed8d9a2ec..0d56bbb053a21 100644 --- a/docs/advanced-features/using-mdx.md +++ b/docs/advanced-features/using-mdx.md @@ -4,13 +4,17 @@ description: Learn how to use @next/mdx in your Next.js project. # Using MDX with Next.js +MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life. + Next.js supports MDX through a number of different means, this page will outline some of the ways you can begin integrating MDX into your Next.js project. -## What is MDX? +## Why use MDX? -MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life. +Authoring in markdown is an intuitive way to write content, its terse syntax, once adopted, can enable you to write content that is both readable and maintainable. Because you can use `HTML` elements in your markdown, you can also get creative when styling your markdown pages. -### MDX plugins +However, because markdown is essentially static content, you can't create _dynamic_ content based on user interactivity. Where MDX shines is in its ability to let you create and use your React components directly in the markup. This opens up a wide range of possibilities when composing your sites pages with interactivity in mind. + +## MDX Plugins Internally MDX uses remark and rehype. Remark is a markdown processor powered by a plugins ecosystem. This plugin ecosystem lets you parse code, transform `HTML` elements, change syntax, extract frontmatter, and more. @@ -30,10 +34,6 @@ The following steps outline how to setup `@next/mdx` in your Next.js project: ```terminal npm install @next/mdx @mdx-js/loader - - #or - - yarn add @next/mdx @mdx-js/loader ``` 2. Require the package and configure to support top level `.mdx` pages. The following adds the `options` object key allowing you to pass in any plugins: @@ -61,12 +61,12 @@ The following steps outline how to setup `@next/mdx` in your Next.js project: - package.json ``` -## Using components, layouts and custom elements +## Using Components, Layouts and Custom Elements -You can now import a React component directly inside your MDX page. Note that `@next/mdx` does **not** support frontmatter (frontmatter is a yaml like key, value pairing that can be used to store data about a page), instead, you can export data from within the `.mdx` file: +You can now import a React component directly inside your MDX page. Note that `@next/mdx` does **not** support frontmatter (frontmatter is a YAML like key/value pairing that can be used to store data about a page), instead, you can export data from within the `.mdx` file: ```md -import { CoolComponent } from 'my-components' +import { MyComponent } from 'my-components' export const meta = { author: 'Rich Haines' @@ -82,7 +82,7 @@ This is a list in markdown: Checkout my React component: - + ``` ### Layouts @@ -90,13 +90,13 @@ Checkout my React component: To add a layout to your MDX page, create a new component and import it into the MDX page. Then you can wrap the MDx page with your layout component: ```md -import { CoolComponent, CoolLayoutComponent } from 'my-components' +import { MyComponent, MyLayoutComponent } from 'my-components' export const meta = { author: 'Rich Haines' } -# My MDX page with a layout +# My MDX Page with a Layout This is a list in markdown: @@ -106,12 +106,12 @@ This is a list in markdown: Checkout my React component: - + -export default = ({ children }) => {children} +export default = ({ children }) => {children} ``` -### Custom elements +### Custom Elements One of the pleasant aspects of using markdown, is that it maps to native `HTML` elements, making writing fast, and intuitive: @@ -127,6 +127,22 @@ This is a list in markdown: - Three ``` +The above generates the following `HTML`: + +```html +

H1 heading

+ +

H2 heading

+ +

This is a list in markdown:

+ +
    +
  • One
  • +
  • Two
  • +
  • Three
  • +
+``` + When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name. ```jsx @@ -158,9 +174,10 @@ export default function Post(props) { } ``` -## Helpful links +## Helpful Links - [MDX](https://mdxjs.com) - [`@next/mdx`](https://www.npmjs.com/package/@next/mdx) - [remark](https://github.com/remarkjs/remark) - [rehype](https://github.com/rehypejs/rehype) +- [gray-matter: Adding frontmatter to your MDX file](https://github.com/jonschlinkert/gray-matter) From 18f8d1683117173ab5cf83919608151f89993919 Mon Sep 17 00:00:00 2001 From: Rich Haines Date: Mon, 22 Nov 2021 11:18:41 +0100 Subject: [PATCH 5/6] Update docs/advanced-features/using-mdx.md Co-authored-by: Steven --- docs/advanced-features/using-mdx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-features/using-mdx.md b/docs/advanced-features/using-mdx.md index 0d56bbb053a21..e6e0d7fba1aa0 100644 --- a/docs/advanced-features/using-mdx.md +++ b/docs/advanced-features/using-mdx.md @@ -32,7 +32,7 @@ The following steps outline how to setup `@next/mdx` in your Next.js project: 1. Install the required packages: - ```terminal + ```bash npm install @next/mdx @mdx-js/loader ``` From f1a24e5a703d1d266f2f44a0d88f482d3425b2fe Mon Sep 17 00:00:00 2001 From: molebox Date: Mon, 22 Nov 2021 11:34:00 +0100 Subject: [PATCH 6/6] Moved frontmatter link to new section --- docs/advanced-features/using-mdx.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/advanced-features/using-mdx.md b/docs/advanced-features/using-mdx.md index e6e0d7fba1aa0..3117e49e75cc8 100644 --- a/docs/advanced-features/using-mdx.md +++ b/docs/advanced-features/using-mdx.md @@ -63,15 +63,11 @@ The following steps outline how to setup `@next/mdx` in your Next.js project: ## Using Components, Layouts and Custom Elements -You can now import a React component directly inside your MDX page. Note that `@next/mdx` does **not** support frontmatter (frontmatter is a YAML like key/value pairing that can be used to store data about a page), instead, you can export data from within the `.mdx` file: +You can now import a React component directly inside your MDX page: ```md import { MyComponent } from 'my-components' -export const meta = { -author: 'Rich Haines' -} - # My MDX page This is a list in markdown: @@ -85,6 +81,20 @@ Checkout my React component: ``` +### Frontmatter + +Frontmatter is a YAML like key/value pairing that can be used to store data about a page. `@next/mdx` does **not** support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as [gray-matter](https://github.com/jonschlinkert/gray-matter). + +To access page metadata with `@next/mdx`, you can export a meta object from within the `.mdx` file: + +```md +export const meta = { +author: 'Rich Haines' +} + +# My MDX page +``` + ### Layouts To add a layout to your MDX page, create a new component and import it into the MDX page. Then you can wrap the MDx page with your layout component: @@ -180,4 +190,3 @@ export default function Post(props) { - [`@next/mdx`](https://www.npmjs.com/package/@next/mdx) - [remark](https://github.com/remarkjs/remark) - [rehype](https://github.com/rehypejs/rehype) -- [gray-matter: Adding frontmatter to your MDX file](https://github.com/jonschlinkert/gray-matter)