Create a blog using Next.js and Markdown/MDX
usedevjet
Posted on December 30, 2021
In this article, we are going to learn how to build a blog and or add a blog section to an already existing Next.js project. To create the content of the blog we are going to be using an extension of the Markdown syntax: MDX.
When using Markdown in a website, there is always a compile step in which all the syntax markdown is converted into the HTML that the browser can understand. The problem with using plain Markdown is that you are limited to the handful amount of HTML elements that markdown is aware of. With MDX, you can extend those elements with your custom React components. It looks something like this:
import {Chart} from './snowfall.js'
export const year = 2018
# Last year's snowfall In {year}, the snowfall was above average.
It was followed by a warm spring which caused flood conditions in many of the nearby rivers.
<Chart year={year} color="#fcb32c" />
In this post, we're going to show you two ways of integrating MDX into your project to create blog pages. The traditional way is the one with which we are going to get started, and a bonus to show you how to get the job done in no time.
Setting up our app
First, you'll need to have Node.js installed on your computer. For this project we used Node.js 16 but anything up to 10.3 is going to be okay.
If you have already created your app and just want to add the blog section to it, skip this section, otherwise, create your app with:
npx create-next-app app-name
This should generate a new Next.js app that follows the following folder structure
src
├── components
├── pages
| ├── index.js
| └── _app.js
├── next.config.js
├── package.json
├── README.md
└── yarn.lock
Create the necessary files
Now that we have an app to work with, let's get started with the routes and components that are going to make up our blog:
-
pages/blog
- where our blog posts are stored -
pages/blog/index.js
- the page that lists all the blog posts that we have written and later redirects our readers to the corresponding pages -
pages/blog/post-1/index.mdx
- a simple example post -
next.config.js
- to work with mdx we have to tell nextjs how to work with the mdx pages, that's done here. -
utils/getAllPosts.js
- to retrieve all the blog posts data from thepages/blog
folder -
components/BlogPost.js
- the blog post itself -
components/PostCard.js
- a simple card to display post meta data used to index all posts
You can start opening and creating these files, we'll come back later. First, we need to install the required dependencies.
Install the necessary dependencies
To enable MDX in our app, we need to install the @mdx-js/loader
library. To do so navigate to the root folder and in a console run the following command:
npm install @mdx-js/loader
That's generic to mdx, now we have to add a dependency exclusive to the nextjs framework. Just like before run the following command
npm install @next/mdx
These are fundamental to the functionality that we want to provide. For our case we also want to add some styling with Tailwindcss so install:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Add the paths to all of your template files in your tailwind.config.js
file.
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
Add the @tailwind
directives for each of Tailwind's layers to your ./styles/globals.css
file.
@tailwind base;
@tailwind components;
@tailwind utilities;
We'll also we using the Tailwind CSS Typography plugin so let's install it:
npm install @tailwindcss/typography --save-dev
And add it to your tailwdind.config.js
:
//…
plugins: [
require('@tailwindcss/typography'),
],
//…
Add mdx support
By default, Next.js will just pick .js
and .jsx
files as routes from our pages directory, that's why we need to edit the next.config.js
, to make all our content visible to Next.js.
const withMDX = require("@next/mdx")({
extension: /\.mdx?$/,
});
module.exports = withMDX({
pageExtensions: ["js", "jsx", "md", "mdx"],
});
Now Next.js can not only render the js
and jsx
files inside our pages directory but also .md
and .mdx
.
Fetch the blog posts
To render our pages/blog/index.js
we'll need an array with all the pages that we've written and the links to them. To create it, in our utils/getAllPosts.js
file add the following:
function importAll(r) {
return r.keys().map((fileName) => ({
link: fileName.substr(1).replace(/\/index\.mdx$/, ""),
module: r(fileName),
}));
}
export const posts = importAll(
require.context("../pages/blog/", true, /\.mdx$/)
);
Building the components
Now that we have an array containing all the information about our pages, we are ready to create the pages/blog/index.js
page so that users can navigate trough them, but first let's abstract our posts cards in the PostCard component
The PostCard component is just the component that we'll use to render metadata about the post and create a link straight to the post. You will later learn how to create the metadata for each post, but for now let's asume that we already have it. So in components/PostCard.js
add:
import Link from "next/link";
export const PostCard = ({ post }) => {
const {
link,
module: { meta },
} = post;
return (
<article className="my-4">
<h1 className="text-xl font-bold text-center text-gray-900">
{meta.title}
</h1>
<div className="mt-4 mb-6">
<p className="text-center prose-p">{meta.description}</p>
<div className="mt-2 text-center">
<span className="text-sm text-gray-800">{meta.date}</span>
<span
className="text-sm text-gray-800"
role="img"
aria-label="one coffee"
>
☕ {meta.readTime + " min read"}
</span>
</div>
</div>
<div className="flex justify-center">
<Link href={"/blog" + link}>
<a className="font-bold text-blue-500">
<span className="text-sm underline uppercase">Read more</span>
</a>
</Link>
</div>
</article>
);
};
In case you want to add the styles yourself here is the unstyled version:
import Link from "next/link";
export const PostCard = ({ post }) => {
const {
link,
module: { meta },
} = post;
return (
<article>
<h1>{meta.title}</h1>
<p>{meta.description}</p>
<div>
<span>{meta.date}</span>
<span>☕ {meta.readTime + " min read"}</span>
</div>
<Link href={"/blog" + link}>
<a>Read more</a>
</Link>
</article>
);
};
Now that we have how to render each post card, we can take all the posts information that we retrived with getAllPosts and render them all togheter in the blog/index.js
page to let the reader explore the available posts. Let's do it:
import { PostCard } from "../../components/PostCard";
import { posts } from "../../utils/getAllPosts";
export default function () {
return (
<div>
<div className="max-w-3xl min-h-screen mx-auto">
<div className="px-4 py-12">
<h1 className="text-3xl font-bold text-center">Blog</h1>
<p className="mt-4 text-lg text-center text-gray-800">
All the lastest Devjet news from the devjet team itself
</p>
</div>
<div className="px-4">
<hr className="text-gray-300" />
{posts.map((post) => (
<div>
<PostCard key={post.link} post={post} />
<hr className="text-gray-300" />
</div>
))}
</div>
</div>
</div>
);
}
And the unstyled version:
import { PostCard } from "../../components/PostCard";
import { posts } from "../../utils/getAllPosts";
export default function () {
return (
<div>
<h1>Blog</h1>
<hr />
{posts.map((post) => (
<div>
<PostCard key={post.link} post={post} />
<hr />
</div>
))}
</div>
);
}
Good news!! we are almost there. We just have to create the BlogPost component and write some content.
Now, we previously talked about the metadata that each post would carry on, here is where we define it. Each post is going to look something like this:
import BlogPost from "../../../components/BlogPost";
import { MdxCodeBlock } from "../../../components/MyMdxComponents";
export const meta = {
title: "Create your blog with Next.js and MDX",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque maximus pellentesque dolor non egestas. In sed tristique elit. Cras vehicula, nisl vel ultricies gravida, augue nibh laoreet arcu, et tincidunt augue dui non elit. Vestibulum semper posuere magna.",
date: "Dec 04, 2021",
readTime: 2,
};
export default ({ children }) => <BlogPost meta={meta}>{children}</BlogPost>;
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque maximus pellentesque dolor non egestas. In sed tristique elit. Cras vehicula, nisl vel ultricies gravida, augue nibh laoreet arcu, et tincidunt augue dui non elit. Vestibulum semper posuere magna.
Sed vehicula libero pulvinar
tincidunt ligula non, luctus massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas arcu purus, aliquam ac molestie ac, luctus eget sem. Sed pellentesque massa eros, condimentum commodo ligula cursus ut. Mauris sit amet molestie ipsum. Nullam venenatis est at purus mollis consectetur. Phasellus a ipsum a quam ullamcorper aliquet. Nunc gravida bibendum placerat.
## My Headline
Fusce lacinia mauris vel massa tincidunt dignissim. Proin tempus nunc sed efficitur porta. Nunc ornare tellus scelerisque velit euismod, ut mollis diam tristique. Phasellus vel diam egestas augue ullamcorper gravida. Sed id mattis ligula, id suscipit nisl. Ut placerat.
<MdxCodeBlock
code={`const helloWorld = 'hello world'`}
language="javascript"
/>
The BlogPost
component is the one in charge of rendering each single post, and receives as parameter the metadata and the post content. Here is the code:
import { ArrowNarrowLeftIcon } from "@heroicons/react/solid";
import Link from "next/link";
export default function BlogPost({ children, meta }) {
return (
<div>
<div className="max-w-3xl min-h-screen px-4 mx-auto mb-14">
<div className="mt-4 mb-10 cursor-pointer">
<Link href="/blog">
<a className="flex items-center">
<ArrowNarrowLeftIcon className="h-4 mr-2" />
Back to blog
</a>
</Link>
</div>
<div className="py-10">
<h1 className="text-3xl font-bold text-center">{meta.title}</h1>
<div className="mt-2 text-sm text-center text-gray-800">
<span>{meta.date}</span>
<span role="img" aria-label="one coffee">
☕ {meta.readTime + " min read"}
</span>
</div>
</div>
<hr className="my-10 text-gray-300" />
<article className="max-w-3xl mx-auto prose text-justify">
{children}
</article>
</div>
</div>
);
}
And without any styles
import Link from "next/link";
export default function BlogPost({ children, meta }) {
return (
<div>
<Link href="/blog">
<a>Back to blog</a>
</Link>
<h1>{meta.title}</h1>
<div>
<span>{meta.date}</span>
<span role="img" aria-label="one coffee">
☕ {meta.readTime + " min read"}
</span>
</div>
<hr />
<article>{children}</article>
</div>
);
}
That's it!! Your blog is done, just open a console and run npm run dev
to explore it.
Bonus: How to get the job done in no time
If you've been following along the tutorial, you've probably notice how much work it all takes. Even more keeping in mind the fact that our blog is still missing a lot of very common features like sharing options, a search bar, commentary section, posts clasification, newsletter, etc.
What if i tell you that you integrate all these features and more in minutes instead of hours, just run a couple of commands and get a bunch on code injected in your project codebase to cover all these common solutions. You don't just save a lot of time and resources but also, given the fact that you are in control of the code, there's nothig stopping you from customizing every single bit of it to meet your goals and allow you to focus on what trully makes you unique.
The tool that allows you to do all that and more is devjet and here we show you how to use it to recreate all the code that we've described along this post and even add more feeatures.
For now we are gonna stick to the blog generators, but feel free to explore the entire catalogue at usedevjet.com/#modules.
Just like with create-next-app
we first have to create our boilerplate app, in this case with:
devjet new nextjs [appname]
Note that nextjs is not the only base that we can use, there are other very great frameworks as well, like vue or react or nuxt among others.
We are gonna end up with a codebase similar to the one generated by create-next-app
.
Now to add our blog pages we just have to get into the project folder and type in the console devjet add blog-mdx
devjet add blog-mdx
? Do you want to use styled components? - No
? Where do you want to store your blog posts? - pages/blog
? Do you want us to create an example blog or you prefer the docs? - yes
✓ Installed dependencies
✓ Created pages/blog folder
✓ Added utils/devjet/getAllPosts.js
✓ Added components/devjet/PostCard.js
✓ Added pages/blog/index.js
✓ Added components/devjet/BlogPost.js
✓ Edited next.config.js
✓ Created pages/blog/post-1 folder (example)
✓ Added pages/blog/post-1/index.mdx (example)
And that's all you need to do to either create a blog from the scratch or add it to your already existing project.
Also note that in this case we only generated the logic, just in case that you want to add the styles yourself but we also provide some beatifull premade components to make your work even easier.
The best part, that's not all you can do with devjet's generators, there are hundreds of applications!! Focusing on the blog thematic, these are some generators that you may be interested in:
-
devjet add mailchimp
To create a mailchimp newsletter and let your users get notified when you write new posts -
devjet add google-analytics
Use google analytics get information about your users behaivoir and improve their expirience -
devjet add algolia
To use algolia as a search engine for your posts -
devjet add google-ads
To monetize your blog with google ads -
devjet add auth
Add authentication with diferent providers like firebase or auth0 and let them save posts -
devjet add share
Components to let your users share your content and even comment on it. -
devjet add blog-mdx-prismjs
Highlight your code blocks with prismjs. - And much much more
Conclusion
In this tutorial we've learned how to create a blog in Nextjs using MDX to add the capability to use custom React components in the markdown syntax. We've also learned that when it comes to web development, "reinventing the wheel" typically takes a lot of time for wich we may have a better use, and so discovered that devjet can help us be much more efficient by generating a lot of the code necessary to achive our gols and surpase them.
Posted on December 30, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.