Creating a blog using Contentlayer

–––

Table of Contents 📚

  1. Introduction
  2. Create Next App
  3. Install Tailwind
    1. Configure Tailwind
    2. Tailwind Typography
  4. Install Contentlayer
  5. Update NextJS Configs
  6. Blogs Folder
  7. Create Contentlayer Configs
    1. Define Document Type
    2. JS Config JSON
  8. Listing Blogs
  9. Displaying Markdown Blog
  10. Conclusion

Introduction

In this article we will take a look at one of the simplest ways to create your personal blog using NextJS, Markdown (Contentlayer), and TailwindCSS.

So let's get started

Create Next App

You can use the following commands to create a NextJS application

npx create-next-app my-blog
# or
create-next-app my-blog

Install Tailwind

In this blog we won't go through the process of styling your blog but we will be using @tailwind/typography out of the box to make the blog content look good to the eyes.

You can directly follow the Install TailwindCSS with Next.js guide or follow the steps below. The choice is yours

Go inside the my-blog folder and execute the following commands

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Configure Tailwind

A file named tailwind.config.js will be created inside the my-blog folder. Copy the following code below and paste it inside the file

my-blog/tailwind.config.js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Now let's add the @tailwind directive to the top of the styles/global.css file.

my-blog/styles/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;

/* keep whatever is there */

Tailwind Typography

Let's add Tailwind Typography to the blog to make the blog content look clean

Install the @tailwindcss/typography as a _dev dependency_

npm install -D @tailwindcss/typography

We need to add a require statement inside the plugins array in tailwind.config.js.

my-blog/tailwind.config.js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/typography")],
}

Install Contentlayer

Install the following dependencies to add Contentlayer to the project

npm install contentlayer next-contentlayer

Update NextJS Configs

To allow Contentlayer to parse the .mdx we need to add a few configs to the the next.config.js file.

Update the next.config.js file with the code below

my-blog/next.config.js
const { withContentlayer } = require("next-contentlayer");

module.exports = withContentlayer({
  reactStrictMode: true,
});

Blogs Folder

Let's create some folders to store the .mdx files. Create a content folder at the base of the application i.e. my-blog/content.

  • Add a blogs folder inside the content folder.
  • Add a change-me.mdx file inside the blogs folder.
  • Add the following content to the change-me.mdx file.
my-blog/content/blogs/change-me.mdx
---
title: Lorem Ipsum
date: 2021-12-24
desc: Lorem Ipsum excepteur consequat nostrud esse esse fugiat dolore. Reprehenderit occaecat exercitation non cupidatat in eiusmod laborum ex eufugiat aute culpa pariatur. Irure elit proident consequat veniam minim ipsum ex pariatu
thumbnail: https://images.pexels.com/photos/8347501/pexels-photo-8347501.jpeg
---

## Table of Contents 📑

1. Content 1
   1. One
   2. Two
2. Content 2

Ullamco et nostrud magna commodo nostrud occaecat quis pariatur id ipsum. Ipsum
consequat enim id excepteur consequat nostrud esse esse fugiat dolore.
Reprehenderit occaecat exercitation non cupidatat in eiusmod laborum ex eu
fugiat aute culpa pariatur. Irure elit proident consequat veniam minim ipsum ex
pariatur.

Mollit nisi cillum exercitation minim officia velit laborum non Lorem
adipisicing dolore. Labore commodo consectetur commodo velit adipisicing irure
dolore dolor reprehenderit aliquip. Reprehenderit cillum mollit eiusmod
excepteur elit ipsum aute pariatur in. Cupidatat ex culpa velit culpa ad non
labore exercitation irure laborum.

Create Contentlayer Configs

We need to create document types for every categroy of post we want to publish. Let's assume we have two categories blogs and projects, the layout for showing the content is different for both so we create two different document types for such cases.

In this article we are only showing blogs we will create a single document type called Blog

Define Document Type

Create contentlayer.config.js at the base of the project and add the following code

my-blog/contentlayer.config.js
import { defineDocumentType, makeSource } from "contentlayer/source-files";

export const Blog = defineDocumentType(() => ({
  name: "Blog",
  filePathPattern: `blogs/*.mdx`,
  contentType: "mdx",
  fields: {
    title: {
      type: "string",
      description: "The title of the blog",
      required: true,
    },
    date: {
      type: "date",
      description: "The date of the blog",
      required: true,
    },
    desc: {
      type: "string",
      description: "The description of the blog",
      required: true,
    },
    thumbnail: {
      type: "string",
      description: "Link to Thumbnail Image",
      required: false,
    },
  },
  computedFields: {
    readingTime: { type: "json", resolve: (doc) => readingTime(doc.body.raw) },
    wordCount: {
      type: "number",
      resolve: (doc) => doc.body.raw.split(/\s+/gu).length,
    },
    url: {
      type: "string",
      resolve: (blog) => `/${blog._raw.flattenedPath}`,
    },
    slug: {
      type: "string",
      resolve: (doc) => doc._raw.sourceFileName.replace(/\.mdx$/, ""),
    },
  },
}));


export default makeSource({
  contentDirPath: "content",
  documentTypes: [Blog],
  mdx: {},
});

JS Config JSON

Contentlayer generates the markdown code inside the .contentlayer folder and we will be getting the metadata and the content of the blogs from the .contentlayer/generated

Now to directly import blogs content and metadata directly from the generated we add a path alias for it to the jsconfig.json to make imports easier

Let's create a jsconfig.json file at the base of the project and add the following code.

my-blog/jsconfig.json
{
    "compilerOptions": {
      "baseUrl": ".",
      "paths": {
        "contentlayer/generated": ["./.contentlayer/generated"]
      }
    },
    "include": ["next-env.d.ts", "**/*.tsx", "**/*.ts", ".contentlayer/generated"]
}

Listing Blogs

Now that we have change-me.mdx inside the my-blog/content/blogs folder we can have list of all the blogs inside the folder.

Go to the index.js inside the pages/ folder and change the content of the file as follows

my-blog/pages/index.js
import Head from "next/head";
import Link from "next/link";

import { allBlogs } from "contentlayer/generated";

export async function getStaticProps() {
    const blogs = allBlogs;

    return { props: { blogs } }
}

export default function Home({ blogs }) {
    return (
        <div>
            <div className="min-h-screen min-w-full flex flex-col justify-center items-center" >
                <h2>My Blog</h2>
                <div className="space-y-4 flex flex-col justify-center items-center" >
                    {
                        blogs.map((blog, index) => (
                            <Link key={index} href={blog.url}>
                                <a>{blog.title}</a>
                            </Link>
                            ))
                    }
                </div>
            </div>
        </div>
    )
}

Now go to the terminal inside the my-blog project and run the server in the dev mode.

npm run dev

Now you can go to http://localhost:3000 and see the list of Blogs available

Let's try and show the content inside the blog in a new page

Displaying Markdown Blog

Now that we have a page to list all the blogs present inside the my-blog/content/blogs folder we can create a dynamic route which shows the content inside a particular blog selected

Create a blogs folder inside the pages folder. Then create the [slug].js inside the blogs folder and add the following code

my-blog/pages/blogs/[slug].js
import Head from "next/head";
import { useMDXComponent } from "next-contentlayer/hooks";
import Image from "next/image";

import { allBlogs } from "contentlayer/generated";

export async function getStaticPaths() {
    const paths = allBlogs.map((post) => post.url);

    return {
        paths,
        fallback: false
    }
}

export async function getStaticProps({ params }) {
    const blog = allBlogs.find((blog) => blog._raw.flattenedPath === `blogs/${params.slug}`);

    return {
        props: {
            blog,
        }
    }
}

export default function BlogPost({ blog }) {
    const MDXContent = useMDXComponent(blog.body.code);
    return (
        <div className="max-w-2xl mx-auto py-16 scroll-smooth">
            <div className="text-2xl font-bold" >{blog.title}</div>
            {blog?.thumbnail ? (
                <div>
                <Image
                    src={post.thumbnail}
                    width={1501 / 2}
                    height={712 / 2}
                    priority
                    className="rounded-lg shadow-2xl"
                />
                </div>
            ) : null}
            <div className="prose my-2">
                <MDXContent />
            </div>
        </div>
    )
}

Hopefully, this should show the markdown content 🤞

Conclusion

In this article we saw how we can publish a blog using mark and how Contentlayer makes it very easy. Plus the customisation using TailwindCSS are really good