Building a blog
How I build my blog
The simple way to build blog in Next Js with local mdx content is to use Contentlayer.
Tech Stack
Next.js
Tailwind CSS
ContentLayer
Why Next Js
Next JS has a great structure for React project.
Makes develpement intuitive and fun.
I'm using create t3 app
Why ContentLayer
No need to manualy read, parse and style mdx files.
Saves you from importing 4-5 libraries for only parsing and reading mdx.
Unlike other popular solutions, Contentlayer manage all mdx parsing under the hood.
Why Tailwind CSS
With Tailwind utility classes styling seamless and fast ⚡
Contentlayer
Contentlayer aims to create a pleasant developer experience.
Instead of using multiple third-party packages like glob
, path
, and gray-matter
.
Also, there is a plugin to use with Next JS!
Things you will need to setup:
withContentlayer
wraper innext.config.js
const { withContentlayer } = require("next-contentlayer")
const nextConfig = {}
module.exports = withContentlayer(nextConfig)
- Schema for document type in
contentlayer.config.js
Here you will define all properties for your document type, required fields and source directory, plugins for styles, etc.
import { defineDocumentType, makeSource } from 'contentlayer/source-files'
const Post = defineDocumentType(() => ({
name: 'Post',
filePathPattern: `**/*.md`, // specify md/mdx and subfolders if any
fields: { // define metadata fields
title: { type: 'string', required: true },
date: { type: 'date', required: true }
},
}))
export default makeSource({
contentDirPath: 'posts', //Directory with the Markdown files
documentTypes: [Post],
mdx:{
remarkPlugins:[], //mdx styles plugin
rehypePlugins:[], //mdx styles plugin
}
})
Once everything is configured and the server is running, Contentlayer will generate json
files
from defined Documents.
You will be able to import and consume them from contentlayer/generated
and pass individual post to useMDXComponent
hook like so:
import Layout from '@/components/layout/Layout';
import { allPosts, type Post } from 'contentlayer/generated';
import MDXComponents from '@/components/MDXComponents'
export const getStaticPaths = () => {
return {
paths: allPosts.map((post) => ({
params: {
slug: post.slug,
},
})),
fallback: false,
};
};
export const getStaticProps: GetStaticProps = ({ params }) => {
const post: Post = allPosts.find((post) => post.slug === params?.slug)!;
return {
props: {
post,
},
};
};
const PostPage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
post,
}) => {
const MDXContent = useMDXComponent(post.body.code);
return (
<Layout>
<h1>{post.title}</h1>
<article
className='pt-10 pb-8 pr-2 prose',
// porse is tailwind typography plugin for
// HTML that rendered from uncontrolled sources like mdx
// https://tailwindcss.com/docs/typography-plugin
>
<MDXContent components={MDXComponents} />
</article>
</Layout>
);
};
You also can get posts metadata using same generated object:
import React from 'react';
import {
type GetStaticProps,
type InferGetStaticPropsType,
NextPage,
} from 'next';
export async function getStaticProps() {
const posts = allPosts.filter((post) =>
new RegExp(/^(blog\/)/).exec(post._id)
);
return {
props: {
posts: posts,
},
};
}
const BlogPage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = ({
posts,
}) => {
reutrn (
<ul>
{posts.map((postSummary) => {
return (
<Link
href={`/blog/${postSummary.slug}`}
passHref
key={postSummary.slug}
>
<a>
<PostCard postSummary={postSummary} />
</a>
</Link>
);
})}
</ul>
)
}
Table of Content
Reading articles is much better if there is navigation!
To set up a Table of Content, you will need to fetch all its headings from the post. I made this by using ContentLayer post's raw data and github-slugger library
const headingLines = post.body.raw
.split('\n')
.filter((line: string) => line.match(/^##*\s/));
const headings = headingLines.map((raw: any) => {
const text = raw.replace(/^#*\s/, '').trim();
const level = raw.slice(0, 3) === '#' ? 3 : 2;
const slugger = new GithubSlugger();
return {
text,
level,
id: slugger.slug(text),
};
});
React in MDX files
To use React components in MDX, we can pass them to MDXContent
as props
import Head from 'next/head';
import Footer from '../layout/Footer';
import Carousel from './Carousel';
const MDXComponents = {
Head,
Footer,
a:(props:any)=> <a href={props.href} target='_blank' rel="noreferrer" >{props.children}</a>,
Carousel
};
export default MDXComponents;
import MDXComponents from './MDXComponents';
import { useMDXComponent } from 'next-contentlayer/hooks';
const Post = ({post}:{post:Post}) => {
const MDXContent = useMDXComponent(post.body.code);
return (
<MDXContent components={MDXComponents} />
)
}