Improving My Next.js MDX Blog
👳🏽♂️
Ekam Singh / March 29, 2020
5 min read
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
Simplified Design
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.
<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.
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.
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.
import { frontMatter as blogPosts } from './blog/**/*.mdx';
const Blog = () => (
blogPosts.map((frontMatter) => (
<BlogPost key={frontMatter.title} {...frontMatter} />
))}
);
MDX Plugins
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. - Use
language:title
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 🎉
import { css } from '@emotion/react';
import { theme } from '@chakra-ui/react';
const prismBaseTheme = css`
// Base styling
`;
export const prismLightTheme = css`
// Light mode
`;
export const prismDarkTheme = css`
// Dark mode
`;
const { colorMode } = useColorMode();
const prismTheme = colorMode === 'light' ? prismLightTheme : prismDarkTheme;
Summary
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 🚀
Subscribe to the newsletter
Get emails from me about web development, tech, and early access to new articles.
- subscribers – 28 issues