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
.
Update 2020: I've switched to Tailwind CSS now.
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 at3studio.tech/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: '/images/2019/banner.jpg',
slug: '2019',
title: '2019 Year in Review',
};
export default ({ children }) => <Post meta={meta}>{children}</Post>;
I decided to use Markdown front matter instead. Now, each .mdx
file has a top section like this:
title: '2019 Year in Review';
publishedAt: '2019-12-26';
summary: 'Highlights and reflections on 2019 and a look forward to 2020.';
image: '/images/2019/banner.jpg';
Now, I can use native Next.js functionality like getStaticProps
and getStaticPaths
to fetch my MDX content:
export async function getStaticPaths() {
const posts = await getFiles('blog');
return {
paths: posts.map((p) => ({
params: {
slug: p.replace(/\.mdx/, ''),
},
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const post = await getFileBySlug('blog', params.slug);
return { props: { ...post } };
}
Update 2021: I've moved to Contentlayer.
MDX Plugins
mdx-bundler
allows you to extend remark and rehype, providing external plugins to hook into the compilation process. Some plugins I've added:
import { join } from 'path';
import { readFileSync } from 'fs';
import { bundleMDX } from 'mdx-bundler';
import readingTime from 'reading-time';
import rehypeSlug from 'rehype-slug';
import rehypeCodeTitles from 'rehype-code-titles';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypePrism from 'rehype-prism-plus';
export async function getFileBySlug(type, slug) {
const source = readFileSync(
join(process.cwd(), 'data', type, `${slug}.mdx`),
'utf8'
);
const { code, frontmatter } = await bundleMDX(source, {
xdmOptions(options) {
options.rehypePlugins = [
...(options?.rehypePlugins ?? []),
rehypeSlug,
rehypeCodeTitles,
rehypePrism,
[
rehypeAutolinkHeadings,
{
properties: {
className: ['anchor'],
},
},
],
];
return options;
},
});
return {
code,
frontMatter: {
wordCount: source.split(/\s+/gu).length,
readingTime: readingTime(source),
slug: slug || null,
...frontmatter,
},
};
}
This added some nice additional features:
- Hover over a heading and click on
#
to link directly to it. - Use
language:title
to add titles to code snippets. - 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 rehype-prism-plus
and created two prism themes for dark/light mode. rehype-prism-plus
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;
Update 2020: I've switched to Tailwind CSS now and use the native Dark Mode support. I also recommend Shiki.
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!
The best part? It's all open source 🚀