A responsive and static blog website with category functionality, portfolio section and more.
Explore the code »
View Demo
·
Report Bug
Table of Contents
MDX Blog Website is a statically generated blog. Blog posts can be grouped into categories accessible from the sidebar. It is also possible to add individual pages based on mdx and group them into categories. Each individual page shows linked tags of the other pages within its category for easier access. In addition to Markdown pages, there are three other sections: Who Am I, Portfolio and Contact, which offer the possibility to display additional author information.
type script tailwind react next mdx bundler mdx gray matter
In this section, I highlight a few code snippets that I find valuable. Please refer to the section below for more concepts and features I implemented.
This function is the core logic of the blog. First, the frontmatter metadata interface is defined
according to the metadata used in the actual .mdx
file. In the function, I set the path to the
folder that contains the blog entries, where the last folder
name can be adjusted as needed via
the function argument. Then I initialize the numberOfPosts
as an object containing all categories
and the value 0 as a field value. Since the map function is asynchronous, I use Promise.all()
to
await all promises. Inside the map function, I return the output code and metadata output using
mdx-bundler and simulatively increment the number of blog entries within the specified category.
Note: The date is reformatted to an ISO string because of a type error.
The final output is an object with numberOfPosts
containing the number of posts per category and
the object posts
containing the code and metadata of each post.
import fs from 'fs';
import path from 'path';
import { bundleMDX } from 'mdx-bundler';
import { CategoriesUnion } from '#/src/types/types';
import { initialState } from '#/src/store/NumberOfPostsContext';
export interface FrontmatterBlog {
title: string;
description: string;
category: CategoriesUnion;
date: string;
slug: string;
}
export async function getBlogData(folder: string) {
const blogDir = path.join(process.cwd(), 'src', 'markdown', folder);
const files = fs.readdirSync(blogDir);
const numberOfPosts = { ...initialState };
const posts = await Promise.all(
files.map(async (filename) => {
const markdownWithMeta = fs.readFileSync(path.join(blogDir, filename), 'utf-8');
const result = await bundleMDX<FrontmatterBlog>({ source: markdownWithMeta });
result.frontmatter.date = new Date(result.frontmatter.date).toISOString();
const category = result.frontmatter.category;
numberOfPosts[category] = numberOfPosts[category] ? (numberOfPosts[category] += 1) : 1;
numberOfPosts['DevBlog'] = numberOfPosts['DevBlog']
? (numberOfPosts['DevBlog'] += 1)
: 1;
return result;
}),
);
return { numberOfPosts, posts };
}
This is an example of the NumberOfPosts
type used for the numberOfPosts
object for reference:
type NumberOfPosts = {
Algorithms: number;
'React.js': number;
'Next.js': number;
};
With getStaticProps
data can be passed to statically generated pages. Here I get the blog data
from the function getBlogData
, which I showed in more detail above. I then use type guards to make
sure I have a category slug with a string type, otherwise a 404 page is returned. Next, the category
slug is converted to the into the actual category name, and again type-checking is required to
ensure that this category exists. Finally, I either return the metadata for all posts if the
category devblog
was chosen, or I only return the metadata for the appropriate category. All posts
are sorted by date, with new posts first. To understand this, notice the -(...)
around
new Date(a.date).getTime() - new Date(b.date).getTime()
.
export async function getStaticProps(context: GetStaticPropsContext) {
const { numberOfPosts, posts } = await getBlogData('blog');
if (!context.params || !context.params.category || Array.isArray(context.params.category)) {
return {
notFound: true,
};
}
const contextCategory = context.params.category;
const category = categorySlugToCategory(contextCategory);
if (!category) {
return {
notFound: true,
};
}
if (contextCategory === 'devblog') {
const frontmatterSorted = posts
.map((post) => post.frontmatter)
.sort((a, b) => -(new Date(a.date).getTime() - new Date(b.date).getTime()));
return { props: { numberOfPosts, frontmatterSorted, category: 'DevBlog' } };
} else {
const frontmatterSorted = posts
.map((post) => post.frontmatter)
.filter((post) => categoryToSlug(post.category) === contextCategory)
.sort((a, b) => -(new Date(a.date).getTime() - new Date(b.date).getTime()));
return { props: { numberOfPosts, frontmatterSorted, category } };
}
}
- TypeScript
- Reusable components
- Dynamic routes
- Transitions
- Responsive sidebar
- Number of blog posts by category
- Blog post pages via mdx
- Single pages via mdx
- Show links to other single pages of the same category
To get a local copy up and running follow these steps.
-
Clone the repo
```sh git clone https://github.com/vincentole/mdx_blog_website.git ```
-
Install packages
npm
npm install
yarn
yarn
You can run the project in a local environment as follows:
npm
npm run dev
yarn
yarn dev
Distributed under the MIT License. See github/LICENSE.md
for more information.
Ole Urfels (vincentole):
Project Link: https://github.com/vincentole/mdx_blog_website