Moving blog posts to Sanity
Update: I have since moved my blog posts back to markdown files, but I'm leaving this post up as a record of learning how to use Sanity etc
I have now updated this site to fetch my blog posts from a CMS called Sanity, a move that's completely overkill for a little personal blog like this, but it was a good opportunity to learn something new, especially with the challenge of writing my posts in MDX.
I'm using the Next.js framework which has built in support for MDX files being statically generated at build time - but a nice benefit of moving my posts to Sanity is I can write new posts (or edit existing ones) and have changes appear on my site without having to build and deploy every time. I can even keep the benefits of using static pages with Next's Incremental Static Regeneration feature.
Setting up Sanity and fetching posts
The first step was moving my posts into Sanity and being able to fetch them from my Next.js site. I won't focus too long on this step, but I followed their documentation on setting up a Sanity Studio project and adding a schema for my blog posts, then adding my existing blog posts and deploying it. Back in my Next site, I installed the next-sanity
toolkit so that I could query for my blog posts and fetch their content.
Some query examples to fetch my blog posts:
export const indexQuery = `*[_type == "post"]`
export const postSlugs = `*[_type == "post"]{slug}`
export const postBySlug = (slug) => `*[_type == "post" && slug == '${slug}']`
My src/pages/blog/[slug].jsx
now fetches the blog posts for static generation like this:
export async function getStaticPaths() {
const slugs = await sanityClient.fetch(postSlugs)
return {
paths: slugs.map(({ slug }) => ({ params: { slug } })),
fallback: 'blocking',
}
}
export async function getStaticProps({ params }) {
const response = await sanityClient.fetch(postBySlug(params.slug))
const [post] = response
const code = await prepareMDX({ slug: params.slug, content: post.content })
return {
props: { post, code },
revalidate: 60,
}
}
Note the use of the options for incremental static generation i.e.
revalidate: 60
- which means Next.js will attempt to re-generate the page:- When a request comes in
- At most once every 60 seconds
fallback: 'blocking'
- which will server-render pages on-demand if the path doesn't exist at build time.
I can definitely do more in this area, for example I'd like to set up the ability to preview content in my frontend without publishing it live, but for now I wanted to focus on the next step - how can I convert this string of MDX content fetched from Sanity into the HTML and JS to run in the browser.
Compiling and bundling MDX
For this step I used a package called mdx-bundler to convert my MDX string into code to run in the browser.
Snippet of prepareMDX.js
:
import { bundleMDX } from "mdx-bundler";
import remarkGfm from 'remark-gfm'
import rehypePrism from '@mapbox/rehype-prism'
export async function prepareMDX({slug, content}) {
const result = await bundleMDX({
source: content,
...
mdxOptions(options) {
options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkGfm]
options.rehypePlugins = [...(options.rehypePlugins ?? []), rehypePrism]
return options
},
globals: {
"next/image": "Image",
}
})
const { code } = result;
return code
}
This doesn't show all the setup as there are some bits I'm unsure of that I want to come back to - but it returns me some code I can then in my component like so:
import { getMDXComponent } from 'mdx-bundler/client'
import Image from "next/image"
...
export default function Post({post, code}) {
const Component = React.useMemo(() => getMDXComponent(code, {Image}), [code])
return (
...
<Component />
...
)
}
This hasn't been a comprehensive guide on how I made this change - but it highlights some of the bits I found most interesting. I'd like to keep improving this blog, adding new features and learning along the way, next on the list:
- Adding tags to posts
- Previewing posts in draft
- Trying out some of the new Next.js v13 features e.g Turbopack or the
app/
directory.