Creating a blog using Contentlayer
Table of Contents 📚
- Introduction
- Create Next App
- Install Tailwind
- Install Contentlayer
- Update NextJS Configs
- Blogs Folder
- Create Contentlayer Configs
- Listing Blogs
- Displaying Markdown Blog
- 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
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.
@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.
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
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.
---
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
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.
{
"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
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
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