$ npm install gatsby-plugin-mdx
gatsby-plugin-mdx
is the official integration for using MDX with Gatsby.
MDX is markdown for the component era. It lets you write JSX embedded inside markdown. It’s a great combination because it allows you to use markdown’s often terse syntax (such as # heading
) for the little things and JSX for more advanced components.
npm install gatsby-plugin-mdx gatsby-source-filesystem @mdx-js/react
After installing gatsby-plugin-mdx
you can add it to your plugins list in your
gatsby-config.js
. You'll also want to configure gatsby-source-filesystem
to point at your src/pages
directory (even if you don't want to create MDX pages from src/pages
).
module.exports = {
plugins: [
`gatsby-plugin-mdx`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages`,
},
},
],
}
By default, this configuration will allow you to automatically create pages with .mdx
files in src/pages
and will process any Gatsby nodes with Markdown media types into MDX content.
Please Note:
gatsby-plugin-mdx
requires gatsby-source-filesystem
to be present and configured to process local markdown files in order to generate the resulting Gatsby nodes.mdxOptions
instructions).To automatically create pages from MDX files outside of src/pages
you'll need to configure gatsby-plugin-page-creator
and gatsby-source-filesystem
to point to this folder of files.
module.exports = {
plugins: [
`gatsby-plugin-mdx`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
{
resolve: `gatsby-plugin-page-creator`,
options: {
path: `${__dirname}/src/posts`,
},
},
],
}
Also check out the guide Adding MDX Pages for more details.
gatsby-plugin-mdx
exposes a configuration API that can be used similarly to any other Gatsby plugin. You can define MDX extensions, layouts, global scope, and more.
Key | Default | Description |
---|---|---|
extensions |
[".mdx"] |
Configure the file extensions that gatsby-plugin-mdx will process |
gatsbyRemarkPlugins |
[] |
Use Gatsby-specific remark plugins |
mdxOptions |
{} |
Options directly passed to compile() of @mdx-js/mdx |
By default, only files with the .mdx
file extension are treated as MDX when
using gatsby-source-filesystem
. To use .md
or other file extensions, you can
define an array of file extensions in the gatsby-plugin-mdx
section of your
gatsby-config.js
.
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
},
},
],
}
gatsby-remark-*
pluginsThis config option is used for compatibility with a set of plugins many people use with remark that require the Gatsby environment to function properly. In some cases, like gatsby-remark-prismjs, it makes more sense to use a library like prism-react-renderer to render codeblocks using a React component. In other cases, like gatsby-remark-images, the interaction with the Gatsby APIs is well deserved because the images can be optimized by Gatsby and you should continue using it.
When using these gatsby-remark-*
plugins, be sure to also install their required peer dependencies. You can find that information in their respective README.
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 590,
},
},
],
},
},
],
}
Using a string reference is also supported for gatsbyRemarkPlugins
.
gatsbyRemarkPlugins: [`gatsby-remark-images`]
These configuration options are directly passed into the MDX compiler.
See all available options in the official documentation of @mdx-js/mdx
.
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
mdxOptions: {
remarkPlugins: [
// Add GitHub Flavored Markdown (GFM) support
require(`remark-gfm`),
// To pass options, use a 2-element array with the
// configuration in an object in the second element
[require(`remark-external-links`), { target: false }],
],
rehypePlugins: [
// Generate heading ids for rehype-autolink-headings
require(`rehype-slug`),
// To pass options, use a 2-element array with the
// configuration in an object in the second element
[require(`rehype-autolink-headings`), { behavior: `wrap` }],
],
},
},
},
],
}
The following note will be removed once Gatsby fully supports ESM
Please Note: Most of the remark ecosystem is ESM which means that packages like remark-gfm
currently don't work out of the box with Gatsby. You have two options until Gatsby fully supports ESM:
Use an older version of the remark-*
/rehype-*
package that is not ESM. Example: remark-gfm
needs to be installed like this: npm install remark-gfm@^1
.
Wrap the plugin with an async function (which doesn't work with every plugin):
const wrapESMPlugin = name =>
function wrapESM(opts) {
return async (...args) => {
const mod = await import(name)
const plugin = mod.default(opts)
return plugin(...args)
}
}
You then can use it like this:
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
mdxOptions: {
rehypePlugins: [
wrapESMPlugin(`rehype-slug`),
],
},
},
},
],
}
When importing a React component into your MDX, you can import it using the import
statement like in JavaScript.
import { SketchPicker } from "react-color"
# Hello, world!
Here's a color picker!
<SketchPicker />
Note: You should restart gatsby develop
to update imports in MDX files. Otherwise, you'll get a ReferenceError
for new imports. You can use the shortcodes approach if that is an issue for you.
You can use regular layout components to apply layout to your sub pages.
To inject them, you have several options:
wrapPageElement
API including its SSR counterpart.export default Layout
statement to your MDX file, see MDX documentation on Layout.createPage
action to programatically create pages, you should use the following URI pattern for your page component: your-layout-component.js?__contentFilePath=absolute-path-to-your-mdx-file.mdx
. To learn more about this, head to the programmatically creating pages guide.Read the MDX documentation on programmatically creating pages to learn more.
In your GraphQL schema, you will discover several additional data related to your MDX content. While your local GraphiQL will give you the most recent data, here are the most relevant properties of the Mdx
entities:
Property | Description |
---|---|
frontmatter |
Sub-entity with all frontmatter data. Regular Gatsby transformations apply, like you can format dates directly within the query. |
excerpt |
A pruned variant of your content. By default trimmed to 140 characters. Based on rehype-infer-description-meta. |
tableOfContents |
Generates a recursive object structure to reflect a table of contents. Based on mdast-util-toc. |
body |
The raw MDX body (so the MDX file without frontmatter) |
internal.contentFilePath |
The absolute path to the MDX file (useful for passing it to ?__contentFilePath query param for layouts). Equivalent to the absolutePath on File nodes. |
Use the createNodeField
action to extend MDX nodes. All new items will be placed under the fields
key. You can alias your fields
to have them at the root of the GraphQL node.
Install reading-time
into your project:
npm install reading-time
In your gatsby-node
add a new field:
const readingTime = require(`reading-time`)
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
createNodeField({
node,
name: `timeToRead`,
value: readingTime(node.body)
})
}
}
You're now able to query the information on the MDX node:
query {
mdx {
fields {
timeToRead {
minutes
text
time
words
}
}
}
}
See timeToRead. It returns timeToRead.words
.
This largely comes down to your own preference and how you want to wire things up. This here is one of many possible solutions to this:
Install @sindresorhus/slugify
into your project (v1 as v2 is ESM-only):
npm install @sindresorhus/slugify@^1
In your gatsby-node
add a new field:
const slugify = require(`@sindresorhus/slugify`)
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
createNodeField({
node,
name: `slug`,
value: `/${slugify(node.frontmatter.title)}`
})
}
}
You're now able to query the information on the MDX node:
query {
mdx {
fields {
slug
}
}
}
If you don't want to use the frontmatter.title
, adjust what you input to slugify()
. For example, if you want information from the File
node, you could use getNode(node.parent)
.
Install necessary dependencies into your project:
npm install mdast-util-to-string@^2 unist-util-visit@^2
Create a new file called remark-headings-plugin.js
at the site root:
const visit = require(`unist-util-visit`)
const toString = require(`mdast-util-to-string`)
exports.remarkHeadingsPlugin = function remarkHeadingsPlugin() {
return async function transformer(tree, file) {
let headings = []
visit(tree, `heading`, heading => {
headings.push({
value: toString(heading),
depth: heading.depth,
})
})
const mdxFile = file
if (!mdxFile.data.meta) {
mdxFile.data.meta = {}
}
mdxFile.data.meta.headings = headings
}
}
Add a new headings
field resolver to your Mdx
nodes through createSchemaCustomization
API:
const { compileMDXWithCustomOptions } = require(`gatsby-plugin-mdx`)
const { remarkHeadingsPlugin } = require(`./remark-headings-plugin`)
exports.createSchemaCustomization = async ({ getNode, getNodesByType, pathPrefix, reporter, cache, actions, schema }) => {
const { createTypes } = actions
const headingsResolver = schema.buildObjectType({
name: `Mdx`,
fields: {
headings: {
type: `[MdxHeading]`,
async resolve(mdxNode) {
const fileNode = getNode(mdxNode.parent)
if (!fileNode) {
return null
}
const result = await compileMDXWithCustomOptions(
{
source: mdxNode.body,
absolutePath: fileNode.absolutePath,
},
{
pluginOptions: {},
customOptions: {
mdxOptions: {
remarkPlugins: [remarkHeadingsPlugin],
},
},
getNode,
getNodesByType,
pathPrefix,
reporter,
cache,
}
)
if (!result) {
return null
}
return result.metadata.headings
}
}
}
})
createTypes([
`
type MdxHeading {
value: String
depth: Int
}
`,
headingsResolver,
])
}
You're now able to query the information on the MDX node:
query {
mdx {
headings {
value
depth
}
}
}
MDX and gatsby-plugin-mdx
use components for different things like rendering and component mappings.
MDXProvider
is a React component that allows you to replace the
rendering of tags in MDX content. It does this by providing a list of
components via context to the internal MDXTag
component that handles
rendering of base tags like p
and h1
.
import { MDXProvider } from "@mdx-js/react"
const MyH1 = props => <h1 style={{ color: `tomato` }} {...props} />
const MyParagraph = props => (
<p style={{ fontSize: "18px", lineHeight: 1.6 }} {...props} />
)
const components = {
h1: MyH1,
p: MyParagraph,
}
export const ComponentsWrapper = ({ children }) => (
<MDXProvider components={components}>{children}</MDXProvider>
)
The following components can be customized with the MDXProvider
:
Tag | Name | Syntax |
---|---|---|
p |
Paragraph | |
h1 |
Heading 1 | # |
h2 |
Heading 2 | ## |
h3 |
Heading 3 | ### |
h4 |
Heading 4 | #### |
h5 |
Heading 5 | ##### |
h6 |
Heading 6 | ###### |
thematicBreak |
Thematic break | *** |
blockquote |
Blockquote | > |
ul |
List | - |
ol |
Ordered list | 1. |
li |
List item | |
table |
Table | --- | --- | --- |
tr |
Table row | This | is | a | table row |
td /th |
Table cell | |
pre |
Pre | |
code |
Code | |
em |
Emphasis | _emphasis_ |
strong |
Strong | **strong** |
delete |
Delete | ~~strikethrough~~ |
hr |
Break | --- |
a |
Link | <https://mdxjs.com> or [MDX](https://mdxjs.com) |
img |
Image | ![alt](https://mdx-logo.now.sh) |
It's important to define the components
you pass in a stable way
so that the references don't change if you want to be able to navigate
to a hash. That's why we defined components
outside of any render
functions in these examples.
If you want to allow usage of a component from anywhere (often referred to as a shortcode), you can pass it to the MDXProvider.
import React from "react"
import { MDXProvider } from "@mdx-js/react"
import { Link } from "gatsby"
import { YouTube, Twitter, TomatoBox } from "./ui"
const shortcodes = { Link, YouTube, Twitter, TomatoBox }
export const Layout = ({ children }) => (
<MDXProvider components={shortcodes}>{children}</MDXProvider>
)
Then, in any MDX file, you can navigate using Link
and render YouTube
, Twitter
, and TomatoBox
components without
an import.
# Hello, world!
Here's a YouTube embed
<TomatoBox>
<YouTube id="123abc" />
</TomatoBox>
Read more about injecting your own components in the official MDX provider guide.
gatsby-plugin-mdx@^4.0.0
is a complete rewrite of the original plugin with the goal of making the plugin faster, compatible with MDX v2, leaner, and more maintainable. While doing this rewrite we took the opportunity to fix long-standing issues and remove some functionalities that we now think should be handled by the user, not the plugin. In doing so there will be of course breaking changes you'll have to handle – but with the help of this migration guide and the codemods you'll be on the new version in no time!
Please Note: Loading MDX from other sources as the file system is not yet supported in gatsby-plugin-mdx@^4.0.0
.
npm remove @mdx-js/mdx
npm install gatsby@latest gatsby-plugin-mdx@latest @mdx-js/react@latest
If you used any related plugins like gatsby-remark-images
, also update them to their @latest
version.
gatsby-config
remarkPlugins
and rehypePlugins
keys into the new mdxOptions
config option:module.exports = {
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
- remarkPlugins: [],
- rehypePlugins: [],
+ mdxOptions: {
+ remarkPlugins: [],
+ rehypePlugins: [],
+ },
},
},
],
}
mdxOptions
which is passed directly to the MDX compiler. See all available options in the official documentation of @mdx-js/mdx
.extensions
, gatsbyRemarkPlugins
, and mdxOptions
exist as options now. Every other option got removed, including defaultLayouts
. See the layouts guide to learn how to use layouts with gatsby-plugin-mdx@^4.0.0
.gatsby-remark-*
plugins are only listed inside the gatsbyRemarkPlugins
array of gatsby-plugin-mdx
, not inside the plugins
array of gatsby-config
or in any other place.mdxOptions
(you have to install remark-gfm@^1
)remark-*
/rehype-*
most probably won't work. Check out the workarounds mentioned in mdxOptions
createPage
action in gatsby-node
In most cases the changes necessary here are related to the removal of defaultLayouts
option and the new way how layouts are done. See the layouts guide to learn how to use layouts with gatsby-plugin-mdx@^4.0.0
.
You'll need to do two things to continue using your old layout file:
__contentFilePath
query param to your layout fileconst postTemplate = path.resolve(`./src/templates/post.jsx`)
actions.createPage({
- component: postTemplate,
+ component: `${postTemplate}?__contentFilePath=/path/to/content.mdx`,
})
Or a more complete example:
const postTemplate = path.resolve(`./src/templates/post.jsx`)
// Rest of createPages API...
const { data } = await graphql(`
{
allMdx {
nodes {
id
frontmatter {
slug
}
// highlight-start
internal {
contentFilePath
}
// highlight-end
}
}
}
`)
data.allMdx.nodes.forEach(node => {
actions.createPage({
path: node.frontmatter.slug,
component: `${postTemplate}?__contentFilePath=${node.internal.contentFilePath}`, // highlight-line
context: {
id: node.id,
},
})
})
You'll also need to update your template file itself, see the next section Updating page templates.
Note: You could also directly pass the MDX file to the component
like this:
actions.createPage({
component: `/path/to/content.mdx`,
// If you don't want to construct it yourself, use internal.contentFilePath
// component: node.internal.contentFilePath
})
However, we'd recommend placing such files into the src/pages
directory and gatsby-plugin-mdx
will handle the page creation itself.
body
on the MDX node, the page template now receives the transformed MDX as children
property.<MDXRenderer>
as you can use {children}
directly.query PostTemplate
) you have to remove the name. Gatsby automatically creates a name internally, but if a name is provided you'll see a "Duplicate query" warning.import React from "react"
import { graphql } from "gatsby"
- import { MDXRenderer } from "gatsby-plugin-mdx"
- function PostTemplate({ data: { mdx } }) {
+ function PostTemplate({ data: { mdx }, children }) {
return (
<main>
<h1>{mdx.frontmatter.title}</h1>
- <MDXRenderer>
- {mdx.body}
- </MDXRenderer>
+ {children}
</main>
)
}
export const pageQuery = graphql`
- query PostTemplate($id: String!) {
+ query ($id: String!) {
mdx(id: { eq: $id }) {
frontmatter {
title
}
- body
}
}
`
export default PostTemplate
As MDX v2 changed the way it handles content you might need to update your MDX files to be valid MDX now. See their Update MDX content guide for all details. In What is MDX? it is also laid out which features don't work. Most importantly for this migration:
<img>
to <img />
). Instead of HTML comments, you can use JavaScript comments in braces: {/* comment! */}
<
) and left curly brace ({
) have to be escaped: \<
or \{
(or use expressions: {'<'}
, {'{'}
)
enableCustomId
option from gatsby-remark-autolink-headers
you'll run into problems due to the above. You need to disable this option and use rehype-slug-custom-id
instead.In our testing, most of the time the issue were curly brackets that needed to be escaped with backticks:
- You can upload this to Git{Hub,Lab}
+ You can upload this to `Git{Hub,Lab}`
Since most MDX nodes are moved to userland you'll have to extend the GraphQL MDX nodes and update your queries accordingly. However, you can alias your fields
to have them at the root of the GraphQL node.
Here's an example of an updated query (if you re-implemented most features):
{
- timeToRead
- rawBody
- slug
headings
- html
- mdxAST
- wordCount
- fileAbsolutePath
+ body
+ fields {
+ timeToRead
+ slug
+ }
+ internal {
+ contentFilePath
+ }
}
Here's an example on how you'd alias your fields
to keep the shape of the MDX node the same:
const readingTime = require(`reading-time`)
exports.onCreateNode = ({ node, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
createNodeField({
node,
name: `timeToRead`,
value: readingTime(node.body)
})
}
}
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
createTypes(`#graphql
type Mdx implements Node {
# You can also use other keys from fields.timeToRead if you don't want "minutes"
timeToRead: Float @proxy(from: "fields.timeToRead.minutes")
wordCount: Int @proxy(from: "fields.timeToRead.words")
}
`)
}
defaultLayouts
, mediaTypes
, lessBabel
, shouldBlockNodeFromTransformation
, commonmark
remarkPlugins
and rehypePlugins
into mdxOptions
timeToRead
, rawBody
, slug
, headings
, html
, mdxAST
, wordCount
, fileAbsolutePath
from the query result. You can check Extending the GraphQL MDX nodes to learn how to re-implement some of them on your own. Also check Updating MDX nodes for guidance on changing your queriesgatsby-plugin-mdx
only applies to local files (that are sourced with gatsby-source-filesystem
)js
and json
in frontmatterAs mentioned above the html
field was removed from the GraphQL node. We know that some of you used this for e.g. gatsby-plugin-feed
. Unfortunately, for compatibility and performance reasons we had to remove it. We recommend using the excerpt
field in the meantime until we find a feasible solution to provide MDX rendered as HTML. If you have any suggestions, please comment on the GitHub Discussion.
Before MDX, some of the benefits of writing Markdown were lost when integrating with JSX. Implementations were often template string-based which required lots of escaping and cumbersome syntax.
MDX seeks to make writing with Markdown and JSX simpler while being more expressive. Writing is fun again when you combine components, that can even be dynamic or load data, with the simplicity of Markdown for long-form content.
© 2010 - cnpmjs.org x YWFE | Home | YWFE