Building a Markdown blog using Next.js and Tailwind Typography

albac

Alfredo Baldoceda

Posted on May 24, 2023

Building a Markdown blog using Next.js and Tailwind Typography

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.

IMAGE_ALT

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Change the file styles/globals.css with the following content:



@tailwind base;
@tailwind components;
@tailwind utilities;


Enter fullscreen mode Exit fullscreen mode

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;
    }
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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")],
};



Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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....



Enter fullscreen mode Exit fullscreen mode

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,
    })),



Enter fullscreen mode Exit fullscreen mode

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,
    };
  });
}


Enter fullscreen mode Exit fullscreen mode

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,
                })),
        },
    };
}



Enter fullscreen mode Exit fullscreen mode

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';


Enter fullscreen mode Exit fullscreen mode

The hydrate function is no longer necessary, and you can use MDXRemote directly to hydrate the markdown content:



<MDXRemote {...content} />


Enter fullscreen mode Exit fullscreen mode

The renderToString function has been replaced with serialize:



-  const mdxSource = await renderToString(content);
+  const mdxSource = await serialize(content);


Enter fullscreen mode Exit fullscreen mode

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>



Enter fullscreen mode Exit fullscreen mode

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:



Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

Lastly, the end result can be seen on this page style. Compare it to the GitHub link to see the difference.

IMAGE_ALT


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


💖 💪 🙅 🚩
albac
Alfredo Baldoceda

Posted on May 24, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related