- Published on
Excerpts with Gatsby and MDX
A simple solution to extract an excerpt from Markdown in a Gatsby + MDX site.
I built my website using Gatsby and it used the
gatsby-transformer-remark
plugin to extract excerpts from Markdown
files. After migrating the website to use MDX, I lost the built-in excerpt
functionality. This articles covers my work-around.
Before MDX
The basic Gatsby setup to process Markdown files begins by configuring
gatsby-transformer-remark
plugin in gatsby-config.js
.
module.exports = {
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
excerpt_separator: `<!-- excerpt -->`,
},
},
]
}
Next, I create a page with a GraphQL data query for the excerpt field. In the
example below, blog.js
renders a summary of all blog posts at the /blog
url
path.
const query = graphql`
query {
allMarkdownRemark(
filter: { frontmatter: { template: { eq: "post" } } }
) {
edges {
node {
frontmatter { title }
excerpt(format: HTML)
fields { slug }
}
}
}
`
const BlogPage = () => {
const { allMarkdownRemark: { edges: post } } = useStaticQuery(query)
return (
<ul>
{
posts.map(post => (
<li key={post.node.fields.slug}>
<h1>{post.node.frontmatter.title}</h1>
<div
dangerouslySetInnerHTML={{ __html: post.node.excerpt }}
/>
</li>
))
}
</ul>
)
}
Lastly, we write the blog post and include the excerpt delimiter.
---
title: Brew Coffee The Right Way
---
In today's article, I'll talk about the pros and cons of 5 typical coffee
brewing methods. Then, I'll present my approach and why I think everyone should
use it.
<!-- excerpt -->
With the setup outlined above, Gatsby extracts all Markdown between the last
frontmatter delimiter(---
) and the excerpt separator (<!-- excerpt -->
) and
makes it available in the GraphQL data layer and thus accessible - as HTML - for
a component to render.
Using MDX
Adding MDX to my website meant removing the
gatsby-transformer-remark
plugin - and with it the ability to automatically
extract an excerpt of Markdown and render it to the screen.
Here's a snippet of the updated gatsby-config.js
.
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`]
},
},
]
}
To solve my dilemma, I began with a change to the excerpt separator used in Markdown files. Below you can see the new separator is positioned before and after the excerpt text.
---
title: Top 5 Cat Videos
---
<!-- excerpt -->
In today's article, I discuss the top 5 cat videos of all time.
<!-- excerpt -->
Next, I added logic in Gatsby's onCreateNode
API function to extract the
entire post's raw markdown, parse the excerpt out, convert it to HTML and store
the result in a field in the GraphQL data layer. Here's the function in
gatsby-node-js
.
const remark = require('remark')
const remarkHTML = require('remark-html')
const EXCERPT_SEPARATOR = '<!-- excerpt -->'
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
const [, rawExcerpt] = node.rawBody.split(EXCERPT_SEPARATOR)
const excerpt = rawExcerpt
? remark().use(remarkHTML).processSync(rawExcerpt.trim()).toString()
: ''
createNodeField({
name: `excerpt`,
node,
value: excerpt,
})
}
}
Then I made three changes to blog.js
:
- Replace
allMarkdownRemark
withallMdx
- Add
excerpt
to thefields
path in the GraphQL query - Extract
excerpt
from thefields
path to render the HTML
const query = graphql`
query {
allMdx(
filter: { frontmatter: { template: { eq: "post" } } }
) {
edges {
node {
frontmatter { title }
fields { slug, excerpt }
}
}
}
`
const BlogPage = () => {
const { allMdx: { edges: posts } } = useStaticQuery(query)
return (
<ul>
{
posts.map(post => (
<li key={post.node.fields.slug}>
<h1>{post.node.frontmatter.title}</h1>
<div
dangerouslySetInnerHTML={{ __html: post.node.fields.excerpt }}
/>
</li>
))
}
</ul>
)
}
Wrap-up
This implementation works like a charm.👍 There is one rule to follow - Markdown authors cannot include a React component in the excerpt. I can live with that.