Improving My Next.js MDX Blog LR Lee Robinson / March 29, 2020 5 min read • ––– views

I recently decided to redesign and revamp my site. I had a few main goals:

Easier content management for blog posts Simplified, minimal design Dark mode support

Before this redesign, I hand-rolled all of my components using styled-components. I was trying to maintain a consistent design, so I'd extract shared values out into a theme.

export const colors = { accent : '#ff5252' , background : '#0a6159' , border : '#dcdcdc' , grey : { 100 : '#F5F7FA' , 200 : '#E4E7EB' , 300 : '#CBD2D9' , 400 : '#9AA5B1' , 500 : '#7B8794' , 600 : '#616E7C' , 700 : '#52606D' , 800 : '#3E4C59' , 900 : '#323F4B' , 1000 : '#1F2933' } , light : '#606060' , text : '#101010' } ; export const spacing = { extrasmall : '0.5em' , small : '1em' normal : '1.5em' , large : '2em' , extralarge : '2.5em' , } ;

Front-end tooling has rapidly progressed, and projects like styled-system and Theme UI make it easy to create components that easily adhere to your design system.

My site isn't anything crazy – mostly a few simple static pages and blog posts. While coding everything myself is a fun learning experience, there are plenty of component libraries that contain everything necessary to achieve the design I was aiming for. That's why I chose to adopt Chakra UI for my redesign.

It uses styled-system under the hood, allowing me to use style props

The theme is extendable, allowing me to change fonts and add icons easily

It includes a great set of accessible components out of the box

Works well with Next.js and supports dark mode

Here's a quick example of Chakra UI and styled-system. This is part of the source for the newsletter subscription at the bottom of this post.

components/Subscribe.js

< Box border = "1px solid" borderColor = "blue.200" bg = "blue.50" borderRadius = { 4 } padding = { 6 } my = { 4 } > < Heading as = "h5" size = "lg" mb = { 2 } > Subscribe to the newsletter < / Heading > < Text > Get emails from me about web development , tech , and early access to new articles . < / Text > < InputGroup size = "md" mt = { 4 } > < Input aria - label = "Email for newsletter" placeholder = "tim@apple.com" type = "email" / > < InputRightElement width = "6.75rem" > < Button fontWeight = "bold" h = "1.75rem" size = "sm" > Subscribe < / Button > < / InputRightElement > < / InputGroup > < / Box >

Using style props, I'm able to easily style my components while pulling values directly from my design system. For example, mb (short for margin-bottom ) of 2 will translate to 0.5rem or ~8px .

Improved Content Management #

I wanted to decrease the amount of friction it took to create new articles, as well as improve maintainability over time.

Previously, I maintained a JSON file containing all my articles.

data/articles.json

export default [ { date : 'February 24, 2020' , slug : 'fetching-data-with-swr' , title : 'Create a Dashboard with Next.js API Routes - Fetching Data with SWR' } , { date : 'February 18, 2020' , slug : 'google-analytics-api-nextjs' , title : 'Create a Dashboard with Next.js API Routes - Google Analytics API' } ] ;

Then, I iterated over this list to display all articles when viewing leerob.io/blog . This approach worked, but it meant that I had two sources of truth.

Each .mdx blog post already contained this information, as well as other metadata passed to <Post /> . Every time I added a new article, I had to change two places.

export const meta = { date : '2019-12-26' , description : 'Highlights and reflections on 2019 and a look forward to 2020.' , image : '/static/images/2019/banner.jpg' , slug : '2019' , title : '2019 Year in Review' } ; export default ( { children } ) => < Post meta = { meta } > { children } < / Post > ;

We can do better. I decided to use next-mdx-enhanced for front matter and layout support. Now, each .mdx file looks like this:

-- - title : '2019 Year in Review' publishedAt : '2019-12-26' summary : 'Highlights and reflections on 2019 and a look forward to 2020.' image : '/static/images/2019/banner.jpg' -- -

You'll notice I removed slug . That's because I'm able to dynamically retrieve the value using next-mdx-enhanced inside the layout. Anything added to your front matter will be converted into an object and passed to the layout.

layouts/index.js

import React from 'react' ; export default ( frontMatter ) => { return ( { children } ) => { const slug = frontMatter . __resourcePath . replace ( 'blog/' , '' ) . replace ( '.mdx' , '' ) ; return ( < > < h1 > { frontMatter . title } < / h1 > { children } < / > ) ; } ; } ;

Thanks to babel-plugin-import-glob-array, I can make the file system the source of truth.

pages/blog.js

import { frontMatter as blogPosts } from './blog/**/*.mdx' ; const Blog = ( ) => ( blogPosts . map ( ( frontMatter ) => ( < BlogPost key = { frontMatter . title } { ... frontMatter } / > ) ) } ) ;

MDX uses remark and rehype under the hood and allows you to provide external plugins to hook into the compilation process. Some of the plugins I added were:

I also extended next-mdx-enhanced's front matter using reading-time. This gave me some features I had wanted.

Hover over a heading and click on # to link directly to it.

to link directly to it. Use language:title to add titles to code snippets.

to add titles to code snippets. Run title over headings to auto-capitalize based on The Chicago Manual of Style.

Reading time of articles.

Better Syntax Highlighting #

Previously, I directly imported a prism.css theme alongside react-syntax-highlighter to provide syntax highlighting. This approach did not allow me to easily change styles based on the theme. Thus, I kept the code style always dark.

Instead, I switched to mdx-prism (which is a fork of @mapbox/rehype-prism ) and created two prism themes for dark/light mode. mdx-prism also adds line highlighting capabilities 🎉

styles/prism.js

import { css } from '@emotion/core' ; import { theme } from '@chakra-ui/core' ; const prismBaseTheme = css ` // Base styling ` ; export const prismLightTheme = css ` // Light mode ` ; export const prismDarkTheme = css ` // Dark mode ` ;

_app.js

const { colorMode } = useColorMode ( ) ; const prismTheme = colorMode === 'light' ? prismLightTheme : prismDarkTheme ;

Outside of my main goals, I was able to sneak in some other fun additions:

Show view counts for all blog posts, dynamically pulled from Firebase

Faster page load times

Switched to Inter as my main font

Fewer files, and a lot of deleted code!

Switched to Next SEO instead of doing it manually

The best part? It's all open source 🚀