Jashn Maloo / October 12, 2020
When I thought of making a blog with Next.js, the first technology that came in my mind was MDX, because why not? With Next.js, it provides several features -
What about SEO ? Well, it's supported as well, add front-matter to it, parse it with gray matter or similar library and you're good to go. (I use next-seo for SEO in Next apps, but it won't be covered in this series. You can watch this amazing tutorial for next-seo.)
You should be well versed with basics of React. Knowledge of Next.js is a plus, but I will be discussing some foundations of Next.js.
I won't say it's super easy because I've spent days trying several ways to implement all the above discussed points. But once you know it, it's a cheesecake. Of the problems generally faced with SEO, components, parsing, I'll be discussing them as we move forward. Rest assured, it'll be a complete tutorial for building your own MDX blog.
Considering that I'll go in as much details as I can, it will be 2 part series. First one will definitely be long, but easy. We'll do the basic setup, with theming, essential pages, functions will be created in this post, while the next one will cover dynamic routing, handling components, front-matter in the post.
Repo: mdx-blog-nextjs
Bootstrap Next.js project with
This is how a typical Next.js folder structure looks like -
pages folder is the one where all the routes respond to in Next.js, so that's really important.
Let's install most of the dependencies now, You can use yarn or npm according to your choice, I'm going with npm. I'll discuss about these as we start using these.
npm i theme-ui @theme-ui/presets gray-matter @next/mdx
For this project, I'm using theme-ui as my UI library and for syntax highlighting in thee blog, you can go with whatever you like, Chakra UI or Material UI or any other. So We're installing
Now let's create
// ./theme.jsimport { roboto } from "@theme-ui/presets";const theme = {...roboto,containers: {page: {width: "98%",maxWidth: "840px",m: 0,mx: "auto",justifyContent: "center",height: "100%",py: "1rem",px: ["0.4rem", "2rem"]},postCard: {p: "2",borderRadius: "4px",border: "1px solid #eeeeee",width: "100%"}},text: {heading: {py: "0.4rem"}}};export default theme;
It contains basic styling enough to build a decent looking blog, rest we'll be doing inside our pages.
Okay, theme done. Now we goota make our
// ./_app.jsimport "../styles/globals.css";import { ThemeProvider } from "theme-ui";import theme from "../theme";function MyApp({ Component, pageProps }) {return (<ThemeProvider theme={theme}><Component {...pageProps} /></ThemeProvider>);}export default MyApp;
Once done, make Next.js config file
// ./next.config.jsconst withMDX = require("@next/mdx")();module.exports = withMDX({pageExtensions: ["js", "mdx"]});
This config file is required to tell Next.js that we're using MDX files, so it should treat them as regular pages. After this, you can literally start building pages in MDX in the pages folder and you'll be ablle to see them in your browser. But we're building a blog, so there are more things involved.
So we're ready to make MDX pages, make a folder named
// ./posts/getting-started-with-mdx.mdx---title: "Getting started with MDX"date: "2020-10-10"author: "MDX Team"excerpt: "Making this in MDX for simplicity and flexibility"---## Why MDX ?❤️ **Powerful**: MDX blends markdown and JSX syntax to fit perfectly inReact/JSX-based projects.💻 **Everything is a component**: Use existing components inside yourMDX and import other MDX files as plain components.🔧 **Customizable**: Decide which component is rendered for each markdownelement (`{ h1: MyHeading }`).📚 **Markdown-based**: The simplicity and elegance of markdown remains,you interleave JSX only when you want to.🔥 **Blazingly blazing fast**: MDX has no runtime, all compilation occursduring the build stage.[MDX](https://mdxjs.com) is markdown for component era.
// ./posts/some-random-points.mdx---title: "Writing random points"date: "2020-10-09"author: "Jashn Maloo"excerpt: "World is random in what it does, so let's write something random"---## Some random points- Isn't writing in markdown lovely?- Aren't components so reusable?- Why is Next.js so fantastic?- Please give me 2021.### Some random heading
Cool, but it's not yet ready to be shown on our site. Because it's outside our pages directory, first we'll make index page for all the posts we'll have and slug out of these file.
Make a
// ./lib/posts.jsimport fs from "fs";import path from "path";import matter from "gray-matter";//Finding directory named "posts" from the current working directory of Node.const postDirectory = path.join(process.cwd(), "posts");export const getSortedPosts = () => {//Reads all the files in the post directoryconst fileNames = fs.readdirSync(postDirectory);const allPostsData = fileNames.map((filename) => {const slug = filename.replace(".mdx", "");const fullPath = path.join(postDirectory, filename);//Extracts contents of the MDX fileconst fileContents = fs.readFileSync(fullPath, "utf8");const { data } = matter(fileContents);const options = { month: "long", day: "numeric", year: "numeric" };const formattedDate = new Date(data.date).toLocaleDateString("en-IN",options);const frontmatter = {...data,date: formattedDate};return {slug,...frontmatter};});return allPostsData.sort((a, b) => {if (new Date(a.date) < new Date(b.date)) {return 1;} else {return -1;}});};
Okay so it's a good amount of code, wait until we add more to it xD, but let's understand what happened here.
First, we fetched the directory named
Woof! So much here, but it's very much same to what you can find in Next's docs or some other articles. Now we're ready to show the posts on our index page, so let's create one.
I want my blog posts route to be
I'll now share the code, but beware if it's your first time working with dynamic pages in Next.js, high chances that few lines will go over your head, but it's alright, I'll describe everything that's happeneing. So, add the below content to your
// ./blog/index.js/** @jsx jsx */import { jsx, Flex, Heading, Box, Text } from "theme-ui";import Link from "next/link";import { getSortedPosts } from "../../lib/posts";const BlogIndex = ({ allPostsData }) => {return (<><Box sx={{ variant: "containers.page" }}><Heading>My Blog</Heading><Flexsx={{flexWrap: "wrap",mt: "2rem",direction: "column"}}>{allPostsData.map(({ slug, date, title, excerpt }) => (<Box variant="containers.postCard" sx={{ my: "0.5rem" }} key={slug}><lisx={{display: "flex",flexDirection: ["column", "row"],my: "1rem"}}><Box><Link key={slug} href="/blog/[slug]" as={`/blog/${slug}`}><a><Headingsx={{fontSize: "calc(1.6rem + 0.2vw)",fontWeight: "500"}}>{title}</Heading></a></Link><Box sx={{ my: "0.5rem" }}>{excerpt}</Box><Text>{date}</Text></Box></li></Box>))}</Flex></Box></>);};export default BlogIndex;export async function getStaticProps() {const allPostsData = getSortedPosts();return {props: {allPostsData}};}
So it's just the layout of the index page? How are we fetching posts here?
I've got the answers, don't worry. It's more than just the layout of index page. First, if you're curious what are those
So about fetching posts, we're importing
Now if you're new to Next.js, you might be asking what is this
Ah! so much of getStatic, getServerSide, right? Well, people like these.
Now I guess the last thing bothering you might be, "what's with the
Now it's still not clear, don't worry, once we will make the page for showing full post, it'll be more clear.
For now, let's run our code to see how it looks. In your terminal, make sure you're in the directory of your project, the run this
npm run dev
This will start next server on post 3000. And once you go to
And if there are some troubles, maybe just contact me and I'll try my best to make it work for you.
So that's the end of part-1 of Making MDX blog with Next.js. In the next and final part, we'll discuss some common issues of handling components, front-matter and MDX, while completing this blog.