Est. reading time: 6 minutes

A nice thing to have ⭐

You know, this is the really nice thing about being a developer and having a blog "on the side". You do things in personal projects, stumble over a bug or improvement at work and you want to share it with the world. There's always something floating around your mind that would fit in a blog post. Even if it's just a quick note.

So what's up? 🤔

Well, MDX had a 2.0 release - quite a while ago, to be honest - and I wanted to upgrade. This also gave me a chance to improve some things that I did not quite like about how this whole setup here works.

For example I was using this snippet I found somewhere on the internet to get a list of all my blogposts and their metadata and show them under /blog

function importAll(r: any) {
return r.keys().map((fileName: string) => ({
link: fileName.substr(1).replace(/.mdx$/, ''),
module: r(fileName),
}));
}
export const posts: { link: string, module: any }[] = importAll(
require.context('.', true, /\.\/.*.mdx/),
);

I'm sorry that I cannot give credit to the creator anymore, since it was a long time ago and I don't remember where I picked it up 🙇‍♂️ But this always was a bit of a "meh" thing, since NextJS gives you all those nice features like getStaticProps that make this so much nicer to look at...plus I saw this require.context thing as a bit of a hack 🤷‍♂️. So I ditched it. I'm using @next/mdx which allows me to use .mdx files as pages directly, so all my blogposts live in the /pages/blog directory and get turned into blogposts automagically.

export const getStaticProps: GetStaticProps = async () => {
const postsDirectory = join(process.cwd(), 'pages/blog');
const postSlugs = fs
.readdirSync(postsDirectory)
.filter((filename) => filename.endsWith('.mdx'));
const posts = postSlugs.map((slug) => {
const fileContents = fs.readFileSync(
join(`${postsDirectory}/${slug}`),
'utf8',
);
const { data } = matter(fileContents);
return { slug: slug.replace(/\.mdx$/, ''), ...data };
});
return {
props: { posts },
};
};

Another thing that changed was the blogpost metadata. Previously this was an javascript meta object embedded in the blogpost:

export const meta = {
title: 'How to use TailwindCSS with Angular 11+',
description:
"Let's see how much work it actually is to get Angular 11 working with TailwindCSS...",
date: '15.02.2021',
};

Now it's a YAML frontmatter block that get's parsed by a simple one-liner const { data } = matter(fileContents); using gray-matter.

---
title: 'How to use TailwindCSS with Angular 11+'
description: "Let's see how much work it actually is to get Angular 11 working with TailwindCSS..."
date: '15.02.2021'
---

This works fine for the blogpost list overview where I'm doing this manually (in getStaticProps) but frontmatter is not supported by default in @next/mdx. It can be enabled by adding 2 remark plugins though - remarkFrontmatter and remarkMdxFrontmatter. This also made it necessary to move next.config.js over to ESM land since remarkFrontmatter is ESM only. This meant slapping a .mjs file extension on to the config file and porting the whole thing over to ESM syntax. Done 👍

// nextjs.config.mjs
import nextMdx from '@next/mdx';
import remarkFrontmatter from 'remark-frontmatter';
import { remarkMdxFrontmatter } from 'remark-mdx-frontmatter';
const withMDX = nextMdx({
extension: /\.mdx?$/,
options: {
remarkPlugins: [
remarkFrontmatter,
[remarkMdxFrontmatter, { name: 'meta' }],
],
rehypePlugins: [],
providerImportSource: '@mdx-js/react',
},
});
export default withMDX({
images: {
domains: ['pbs.twimg.com'],
},
pageExtensions: ['ts', 'tsx', 'mdx'],
});

This gives my BlogPost component, which I use as a wrapper for to content defined in the .mdx files, direct access to the metadata of the blogpost without much hassle.

<!--- example blogpost .mdx file -->
---
title: 'How to use TailwindCSS with Angular 11+'
description: "Let's see how much work it actually is to get Angular 11 working with TailwindCSS..."
date: '15.02.2021'
---
import BlogPost from 'components/BlogPost';
export default ({ children }) => <BlogPost meta={meta}>{children}</BlogPost>;
# Angular 11(+) and TailwindCSS 🎐
blogpost content goes here...
// BlogPost component wrapper used for every blogpost
const BlogPost = ({ children, meta }: { children: any, meta: any }) => {
const router = useRouter();
const readingTime = Math.ceil(countWords(children) / 200);
return (
<>
<NextSeo
title={`csim | blog - ${meta.title}`}
description={meta.description}
canonical={`https://www.csim.xyz${router.pathname}`}
openGraph={{
url: `https://www.csim.xyz${router.pathname}`,
title: `csim | blog - ${meta.title}`,
description: `${meta.description}`,
site_name: 'csim | blog',
}}
/>
<div className="flex justify-center px-4 dark:bg-night-500 dark:text-white min-h-screen">
<div className="w-full max-w-3xl pt-20 pb-24">
<p className="text-sm text-gray-400">
Est. reading time: {readingTime} minutes
</p>
{children}
</div>
</div>
</>
);
};
export default BlogPost;

It took a while ⏳

This whole thing took me an evening. The upgrade was not much of an issue but I hit a bit of a wall when it came to the new way of handling the metadata. I wanted to get rid of the old snippet but keep the implementation of having my .mdx files as pages. Luckily my Github-fu did not forsake me and I stumbled upon this PR that nudged me in the right direction 🎉