Building a Markdown blog using Next.js and Tailwind Typography
Alfredo Baldoceda
Posted on May 24, 2023
Project Overview
In this tutorial, we will create a Markdown blog using Next.js, a powerful React framework for building web applications. We will use Tailwind CSS
, a utility-first CSS framework, to style the blog. Additionally, we will leverage the Tailwind Typography plugin
to enhance the typography of the Markdown content.
To parse the Markdown files and extract metadata, we will use the gray-matter package. The nextjs-mdx-remote package will help us seamlessly render the Markdown content on the server and client sides. Lastly, we will utilize the date-fns package for working with dates and formatting them in the blog posts.
The current webpage has been developed using the instructions provided in this blog.
You can find the updated code for this project here.
This guide follows Harry Wolf's YouTube tutorial video with some personal modifications we have added. Please watch the tutorial and consider subscribing to his YouTube Channel.
You will find the code for this page on my public repository.
Additionally, I have opened a pull request to the original author with the changes made to this blog.
Getting Started
To begin, ensure that you have the following software and packages installed on your machine:
- Node.js (version 19.4.0)
- Next.js (version 13.3.4)
- React (version 18.2.0)
- Tailwind CSS (version 3.3.2)
- @tailwindcss/typography (version 0.5)
- nextjs-mdx-remote (version 1.5.0)
- gray-matter (version 4.0.3)
date-fns (version 2.23.0)
Tailwind Typography: Tailwind Typography is a plugin for Tailwind CSS that provides a set of typographic styles and utilities for Markdown content. It helps improve the readability and visual appeal of text by applying consistent and responsive typographic styles.
nextjs-mdx-remote: The nextjs-mdx-remote package is a library that enables rendering of MDX (Markdown and JSX) content in Next.js applications. It allows you to fetch and render MDX files on both the server and client sides, making it suitable for building blogs and documentation websites.
gray-matter: gray-matter is a popular JavaScript library used for parsing and extracting metadata from Markdown files. It allows you to access front-matter data (metadata) in your Markdown files, such as titles, dates, or custom fields. It simplifies working with Markdown files that contain additional structured data.
date-fns: date-fns is a lightweight JavaScript library for working with dates. It provides various utility functions for parsing, formatting, manipulating, and comparing dates. In the context of a blog, date-fns can be used to format dates from metadata in a human-readable format, handle time zones, calculate time differences, and perform other date-related operations.
The setup
Make sure you have the latest version of Node installed:
$ node --version
v19.4.0
Creating Next.js project
We recommend starting from a new project and adding the changes described in the YouTube video. However, you can also clone the original author's GitHub.
To create a new project just run the following for creating new Next.js
Project with Tailwind
:
$ npx create-next-app@latest dev-blog --typescript --eslint
Installing and configuration for tailwindcss
Next install tailwind
and other dependencies by running:
$ cd dev-blog
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
Change the file styles/globals.css
with the following content:
@tailwind base;
@tailwind components;
@tailwind utilities;
Optionally, I added some base style to styles/globals.css for links and headings:
@layer base {
a {
@apply text-blue-600 hover:text-blue-500 dark:text-sky-400;
}
h1 {
@apply dark:text-gray-300 text-3xl;
}
h2 {
@apply text-2xl;
}
h3 {
@apply text-xl;
}
}
Tailwindcss Typography setup
The Tailwind CSS Typography plugin will allow us to have more control over the styling of Markdown.
Run the following command to install the plugin:
$ npm install -D @tailwindcss/typography
Also, add the plugin to the Tailwind CSS configuration file, tailwind.config.js
, to look like this:
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/typography")],
};
Install nextjs-mdx-remote and other npm packages
Now install nextjs-mdx-remote and all other NPM package dependencies:
$ npm install gray-matter --save
$ npm install date-fns --save
$ npm install next-mdx-remote --save
The Code
Here we will explain the changes we made to improve and fix current issues due to some package upgrades.
First, we follow almost the same changes as the original author.
Second, during the video instruction the author was using renderString
and hydrate
functions, that changed on the future releases of Next-MDX-Remote.
Finally, we added Tailwind Typography which helps to improve the styles of the markdown and show a more stylish look to our blog.
See all the code changes for this project at my github repo.
Generating date and creating mdx file:
To generate the date for the MDX content, Harry uses a Node command line, which we will also be using. However, we want to create the file with a name that can be sorted by date on the home page.
Using the command below, we can get the date from the toISOString()
output and the name for the file from the getTime()
output:
$ node -e 'console.log(new Date().toISOString(), new Date().getTime())'
2022-04-27T23:34:37.161Z 1651102477161
Create a directory called mdxfiles
and place there all your markdowns.
We can then create the file using the name from getTime()
and the date from toISOString()
:
$ vim mdxfiles/1651102477161.mdx
---
title: Blog with NextJs, Tailwind and Markdown
summary: |
Short description
date: 2022-04-27T23:34:37.161Z
---
Some content here....
Now let's modify the index.js
file and reverse array order, so we get the latest blog on top.
Use slice(0).reverse.map
to reverse the array:
To display the latest blog post on top, we need to modify the index.js
file and reverse the order of the array. We can use slice(0).reverse().map() to reverse the array, as shown below:
posts: allPosts
.slice(0)
.reverse()
.map(({ data, slug }) => ({
...data,
date: data.date.toISOString(),
content: data.summary,
slug,
})),
Get Posts
And let's create the GetAllPost library
that will get all post from our mdxfiles directory.
export function getAllPosts() {
const allPosts = fs.readdirSync(contentDirectory);
// console.log(allPosts);
return allPosts.map((fileName) => {
const slug = fileName.replace(".mdx", "");
const fileContents = fs.readFileSync(
path.join(contentDirectory, fileName),
"utf8"
);
const { data, content } = matter(fileContents);
// console.log(data, content);
return {
data,
content,
slug,
};
});
}
List Posts
Now we edit our index
page to list all posts, we use getAllPost library on the getStaticProps.
export default function Home({ posts }) {
return (
<div className="h-screen">
<Head>
<title>albac: home</title>
<meta
name="description"
content="Generated by create next app"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>Projects</h1>
</main>
<div className="space-y-8 mt-4">
{posts.map((item) => (
<BlogListItem key={item.slug} {...item} />
))}
</div>
</div>
);
}
export async function getStaticProps() {
const allPosts = getAllPosts();
return {
props: {
posts: allPosts
.slice(0)
.reverse()
.map(({ data, slug }) => ({
...data,
date: data.date.toISOString(),
content: data.summary,
slug,
})),
},
};
}
Changes for next-mdx-remote
The breaking release Next-mdx-remote V3
included internal rewrites from V2. This blog is using next-mdx-remote 4.4.1, so we need to change the following file: pages/blog/[slug].js
.
-import renderToString from 'next-mdx-remote/render-to-string';
-import hydrate from 'next-mdx-remote/hydrate';
+import { serialize } from 'next-mdx-remote/serialize';
+import { MDXRemote } from 'next-mdx-remote';
The hydrate function is no longer necessary, and you can use MDXRemote directly to hydrate the markdown content:
<MDXRemote {...content} />
The renderToString
function has been replaced with serialize
:
- const mdxSource = await renderToString(content);
+ const mdxSource = await serialize(content);
Using typography plugin and prose
We can also use Tailwind's typography plugin to style the markdown. Follow the instructions in the tailwindcss/typography
documentation. We can use the prose
class to add styles and also use HTML tags in the markdown.
<article className="prose dark:prose-invert text-slate-600 dark:text-slate-300 font-light font-sans">
<MDXRemote {...content} />
</article>
Here's an example of how we use h2 tags
on the markdown:
<h2>The setup</h2>
Make sure you have the latest [node](https://nodejs.org/en/download/current/) installed:
We're also using dark:prose-inverse
to enable the use of our dark theme on the markdown.
You can see all changes on our dynamic pages here.
Build and Start NextJs
While working on development you can start the app by only running npm run dev
. However at this point you can generate an optimize Next.js version by running npm run build
and then run npm run dev
or npm run start
$ npm run build
> dev-blogs@0.1.0 build
> next build
info - Linting and checking validity of types
info - Creating an optimized production build
info - Compiled successfully
info - Collecting page data
info - Generating static pages (7/7)
info - Finalizing page optimization
Route (pages) Size First Load JS
┌ ● / 672 B 83.4 kB
├ /_app 0 B 75.6 kB
├ ○ /404 182 B 75.8 kB
├ ○ /about 400 B 76 kB
├ λ /api/hello 0 B 75.6 kB
└ ● /blog/[slug] 1.31 kB 84 kB
├ /blog/1651102477161
├ /blog/1651103589178
└ /blog/1651104045510
+ First Load JS shared by all 79.3 kB
├ chunks/framework-2c79e2a64abdb08b.js 45.2 kB
├ chunks/main-dda1ec63a16662d1.js 26.8 kB
├ chunks/pages/_app-5bca0e82a983c558.js 2.76 kB
├ chunks/webpack-8fa1640cc84ba8fe.js 750 B
└ css/b6c8a4e05cb9e5d4.css 3.7 kB
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
This output indicates that there are no issues currently with compiling and generating the static content. Now, just run yarn dev
to start the server and start coding:
$ npm run dev
> dev-blogs@0.1.0 dev
> next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 1090 ms (188 modules)
wait - compiling...
event - compiled successfully in 104 ms (139 modules)
wait - compiling /_error (client and server)...
event - compiled client and server successfully in 44 ms (189 modules)
Lastly, the end result can be seen on this page style. Compare it to the GitHub link to see the difference.
What is next?
- Host Blog using AWS Amplify
- Manage Blog with Amplify Studio
- Host Next.js images in S3 with AWS Amplify.
- Add Highlights and code language recognization to code blocks
- Improve NexJs performance and SEO best practices.
Conclusion
In this tutorial, we learned how to create a Markdown blog using Next.js and Tailwind Typography. We covered the concepts of rendering Markdown content, styling it using Tailwind CSS, and leveraging packages like nextjs-mdx-remote, gray-matter, and date-fns. By following the steps outlined in this tutorial, you now have the foundation to create your own dynamic and stylish Markdown blog using Next.js and Tailwind CSS.
References
- Code for this arcticle: https://github.com/albac/dev-blogs
- Author original blog: Building a Markdown blog using Next.js and Tailwind Typography
- Next MDX Remote GitHub: https://github.com/hashicorp/next-mdx-remote
- Tailwind Typography plugin: https://tailwindcss.com/docs/typography-plugin
- Usefull tool to checkout markdown syntax issues: https://mdxjs.com/playground/
Posted on May 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.