DEV Community 👩‍💻👨‍💻

Cover image for Create a blog using Next.js and Markdown/MDX
usedevjet
usedevjet

Posted on

Create a blog using Next.js and Markdown/MDX

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" />
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 the pages/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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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: [],
};
Enter fullscreen mode Exit fullscreen mode

Add the @tailwind directives for each of Tailwind's layers to your ./styles/globals.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

We'll also we using the Tailwind CSS Typography plugin so let's install it:

npm install @tailwindcss/typography --save-dev
Enter fullscreen mode Exit fullscreen mode

And add it to your tailwdind.config.js:

//…
plugins: [
  require('@tailwindcss/typography'),
],
//…
Enter fullscreen mode Exit fullscreen mode

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"],
});
Enter fullscreen mode Exit fullscreen mode

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$/)
);
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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"
/>
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
funnypan profile image
panfan

my site build by next.js and mdx:
github.com/Manonicu/site
manon.icu

We are hiring! Do you want to be our Senior Platform Engineer? Forem is hiring a Senior Platform Engineer

If you're interested in ops and site reliability and capable of dipping in to our Linux stack, we'd love your help shoring up our systems!