Create Blog app using NextJS + MDX
Here we go ๐ . So hello everyone ๐๐ป this is my first time writing a blog.
When I started redesigning this portfolio in NextJS
from my old portfolio which was created using ReactJS
, I wanted to make something cool like a blog or something, here it is! I created this using MDX
. Believe me, I faced many problems while creating this blog page. There was that one problem in which I was stuck for 2 days.
The problem was that in the App router of NextJS the Routing is done in the ./src/app
directory and I couldn't use getStaticProps()
function in the ./src/app
directory, so I had to change the whole file structure of this project and switched routing to Pages router.
So in short creating this page was a pain in a** ๐
Below are some cool things I can do with MDX.
// the hello world program in js
alert('Hello, World!')
//MDX doesn't provide any styling by default so I had to style every different classes of MDX
This is a quoted text. And there are many more things I can do!!
So this is how it works. I changed the project file structure so routing is done in the ./src/pages
directory.
./src/pages/posts/index.jsx
//./src/pages/posts/index.jsx
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import Link from 'next/link'
export default function Blog({ posts }) {
return (
<div className="w-11/12 md:w-4/5 lg:w-3/4 xl:w-2/3 2xl:w-1/2 mx-auto mt-32">
<div className="pb-8">
<h1 className="text-3xl font-bold">Blogs</h1>
<p>
Welcome to my blog page! I write some cool stuff here. Feel
free to read ๐
</p>
</div>
<ul className="w-full p-2 break-words whitespace-normal">
{posts.map((post) => (
<li className="my-2" key={post.slug}>
<Link
href={`/posts/${post.slug}`}
className="w-full flex justify-between items-baseline"
passHref
>
<div className="font-lg link">
{post.frontmatter.title}
</div>
<div className="text-zinc-400 text-sm">
{post.frontmatter.date}
</div>
</Link>
</li>
))}
</ul>
</div>
)
}
export async function getStaticProps() {
const postsDirectory = path.join(process.cwd(), 'src', 'posts')
const fileNames = fs.readdirSync(postsDirectory)
const posts = fileNames.map((fileName) => {
const filePath = path.join(postsDirectory, fileName)
const fileContent = fs.readFileSync(filePath, 'utf-8')
const { data } = matter(fileContent)
return {
slug: fileName.replace(/\.mdx$/, ''),
frontmatter: data,
}
})
return {
props: {
posts,
},
}
}
Blog component
The
Blog
component is a functional React component that renders a list of blog posts.It receives a prop called
posts
, which is an array of objects representing the blog posts. Each object has aslug
andfrontmatter
property.The component maps over the
posts
array and renders a list of blog post titles along with their publication dates.It uses the Next.js
Link
component to create links to individual blog posts. Each link points to the respective post page/posts/[slug]
.
getStaticProps Function:
This function is an asynchronous function that Next.js uses to fetch data at build time. It's part of the static site generation process.
The function reads the content of MDX files from the
.src/posts
directory.For each MDX file, it extracts the front matter using
gray-matter
(which is used to parse YAML front matter from markdown files).It creates an array of post objects, where each object contains a
slug
(derived from the MDX file name) andfrontmatter
properties.The function returns an object with a
props
key, which contains the posts array. This data will be passed asprops
to theBlog
component.
Usage:
When a user visits the
/posts
route, thegetStaticProps
function is executed at build time to generate the list of blog posts.The
Blog
component then receives the generatedposts
as props and renders the list of blog posts on the page.
./src/pages/posts/[slug].jsx
//./src/pages/posts/[slug].jsx
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
const components = {}
const BlogPost = ({ frontMatter: { title, date, readTime }, mdxSource }) => {
return (
<div className="w-11/12 md:w-4/5 lg:w-3/4 xl:w-2/3 2xl:w-1/2 mx-auto mt-32">
<div className="pb-8">
<h1 className="text-3xl font-bold">{title}</h1>
<div className="text-zinc-400 flex items-baseline text-base">
๐
{date} โข โฐ {readTime} minutes
</div>
</div>
<div className="post break-words w-full p-0 m-0">
<MDXRemote {...mdxSource} components={components} />
</div>
</div>
)
}
const getStaticPaths = async () => {
const postsDirectory = path.join(process.cwd(), 'src', 'posts')
const fileNames = fs.readdirSync(postsDirectory)
const paths = fileNames.map((fileName) => ({
params: {
slug: fileName.replace(/\.mdx$/, ''),
},
}))
return {
paths,
fallback: false,
}
}
const getStaticProps = async ({ params: { slug } }) => {
const postsDirectory = path.join(process.cwd(), 'src', 'posts')
const filePath = path.join(postsDirectory, `${slug}.mdx`)
const fileContent = fs.readFileSync(filePath, 'utf-8')
const { data: frontMatter, content } = matter(fileContent)
const mdxSource = await serialize(content)
return {
props: {
frontMatter,
slug,
mdxSource,
},
}
}
export { getStaticProps, getStaticPaths }
export default BlogPost
BlogPost Component:
It receives two props:
frontMatter
andmdxSource
.frontMatter: Metadata of the blog post extracted from the front matter of the MDX file (e.g., title, date, readTime).
mdxSource: The serialized MDX content obtained from the MDX file, which is transformed into a format that
MDXRemote
can render.
The component renders the blog post's title, date, and estimated reading time, and the MDX content is rendered using the
MDXRemote
component.
getStaticPaths Function:
The
getStaticPaths
function is used to generate static paths for blog posts at build time.It reads the list of MDX files from the
.src/posts
directory and creates an array ofparams
objects for each file. Each object contains aslug
parameter derived from the MDX file name (excluding the.mdx
extension).The function returns an object with
paths
(an array of paths) andfallback
(set tofalse
to return a 404 page for unknown paths).
getStaticProps Function:
The
getStaticProps
function is used to generate static props for a specific blog post at build time.It receives a
slug
parameter representing the filename (without the extension) of the MDX file.The function reads the content of the corresponding MDX file, extracts front matter and content using
gray-matter
, and then serializes the MDX content usingnext-mdx-remote
.The function returns an object with
props
containingfrontMatter
,slug
, andmdxSource
.
Usage:
- When a user visits a blog post page (e.g.,
/posts/some-post
), Next.js will use the static paths generated bygetStaticPaths
and fetch the static props for that specific post usinggetStaticProps
.
- When a user visits a blog post page (e.g.,
Bazinga! And it's done ๐. Thanks for reading my first blog <3.
Syntax Highlighting
I've used rehype-prism
and prism-themes
to highlight code-blocks. Here's the updated code.
./src/pages/posts/[slug].jsx
//./src/pages/posts/[slug].jsx
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'
import rehypePrism from 'rehype-prism'
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import 'prism-themes/themes/prism-holi-theme.css'
const components = {}
const BlogPost = ({ frontMatter: { title, date, readTime }, mdxSource }) => {
return (
<div className="w-11/12 md:w-4/5 lg:w-3/4 xl:w-2/3 2xl:w-1/2 mx-auto mt-32">
<div className="pb-8">
<h1 className="text-3xl font-bold">{title}</h1>
<div className="text-zinc-400 flex items-baseline text-base">
๐
{date} โข โฐ {readTime} minutes
</div>
</div>
<div className="post break-words w-full p-0 m-0">
<MDXRemote {...mdxSource} components={components} />
</div>
</div>
)
}
const getStaticPaths = async () => {
const postsDirectory = path.join(process.cwd(), 'src', 'posts')
const fileNames = fs.readdirSync(postsDirectory)
const paths = fileNames.map((fileName) => ({
params: {
slug: fileName.replace(/\.mdx$/, ''),
},
}))
return {
paths,
fallback: false,
}
}
const getStaticProps = async ({ params: { slug } }) => {
const postsDirectory = path.join(process.cwd(), 'src', 'posts')
const filePath = path.join(postsDirectory, `${slug}.mdx`)
const fileContent = fs.readFileSync(filePath, 'utf-8')
const { data: frontMatter, content } = matter(fileContent)
const mdxSource = await serialize(content, {
mdxOptions: {
rehypePlugins: [
[
rehypePrism,
{
ignoreMissing: true,
aliases: {},
},
],
],
},
})
return {
props: {
frontMatter,
slug,
mdxSource,
},
}
}
export { getStaticProps, getStaticPaths }
export default BlogPost
I personally liked Holi-Theme
so much which I'm using. Below are some code snippets in different languages.
Python
# This program adds two numbers
num1 = 1.5
num2 = 6.3
# Add two numbers
sum = num1 + num2
# Display the sum
print('The sum of {0} and {1} is {2}'.format(num1, num2, sum))
Java
class Main {
public static void main(String[] args) {
int first = 10;
int second = 20;
// add two numbers
int sum = first + second;
System.out.println(first + " + " + second + " = " + sum);
}
}
CSS
div {
padding-top: 50px;
padding-right: 30px;
padding-bottom: 50px;
padding-left: 80px;
}
So finally thank you so much for scrolling till the end. Cya in the next blog!