Hey there! I'm thrilled to have you here. My name is Nordin van Dijk, and this is my very first blog post. To kick things off, I thought it would be a great idea to share how I built this section of my website. Hopefully, you'll find it insightful and maybe even learn something new along the way.
If you’d like to follow along with this blog, make sure you are using the following dependencies:
A blog typically follows a straightforward routing structure. At a minimum, the routing system should support two key functionalities:
Displaying Individual Blog Posts – Each post should have its own dedicated page, accessible via a unique URL. This allows users to easily share and revisit specific articles.
Listing All Blog Posts – A main index page should provide an overview of all published posts, making it easy for visitors to browse content and discover articles of interest.
Let's set up a route to display individual blog posts. I prefer the URL structure:
/blog/post/BLOG_SLUG
To create a new blog post, follow these steps:
export const metadata = {
title: 'Example',
image: '/image.jpg',
publishDate: '2003-21-05',
readingTime: "3 min",
tags: ["example"]
};
# Example
Start of blog post text...
If you're following along and have navigated to a blog post in the browser, you may have noticed that the markdown content lacks proper styling. Let’s fix that by integrating Tailwind CSS Typography!
Since every blog post should share a consistent style, we don’t want to manually define styling in each .mdx file. To avoid repetition and keep our code clean, we’ll use Next.js layouts to apply global styling to all blog posts automatically.
export default function Layout(props: { children: ReactNode }) {
return (
<div className="container p-4 prose lg:prose-lg dark:prose-invert">
{props.children}
</div>
);
}To display a list of all blog posts, we first need to fetch them! We are using React Server Compnents so we can interact with the filesystem of the server to retrieve the posts. We will read the directories inside of the post directory, then map over those to import the metadata of every page.
export default async function Page() {
const files = await readdir("./app/blog/post", { withFileTypes: true });
const slugs = files.filter((dirent) => dirent.isDirectory());
const posts = await Promise.all(
slugs.map(async ({ name }) => {
const { metadata } = await import(`@/app/blog/post/${name}/page.mdx`);
return { slug: name, ...metadata } satisfies BlogPostMetadata;
}),
);
// 2. Rendering the data
};Now with the data at our fingertips we can start representing it visually. So lets create a card component for a blog post.
export function BlogPostCard(props: { post: BlogPostMetadata }) {
return (
<Link key={props.post.slug} href={`./blog/post/${props.post.slug}`} className="flex flex-col gap-2 rounded-lg border border-foreground/0 bg-background p-4 transition duration-200 hover:-translate-y-1 hover:border-foreground" >
<div className="aspect-[3/2] w-full rounded bg-muted relative">
<Image src={props.post.image} alt={`Cover Image for ${props.post.title} `} fill />
</div>
<div>
<p className="text-xs text-neutral-400">
{new Date(props.post.publishDate).toLocaleDateString("nl-NL")},{" "}
{props.post.readingTime} read
</p>
<p className="text-lg font-bold text-foreground">{props.post.title}</p>
</div>
</Link>
)
}Lets render our new BlogPostCard for every post and put those in a grid layout!
export default async function Page() {
// 1. Retrieving the blog post data
return (
<div className="min-h-screen bg-neutral-200 bg-hexagon-pattern-light dark:bg-neutral-700 dark:bg-hexagon-pattern-dark">
<div className="container mx-auto flex flex-col items-center">
<h1 className="my-8 text-center text-5xl font-bold text-foreground">
Blog
</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{posts.map((post) => <BlogPostCard key={post.slug} post={post} />)}
</div>
</div>
</div>
);
};Thats it! We've build a simple blog. From here, you can enhance the blog further by adding features like pagination, categories or search functionality. I hope you found this guide helpful! If you have any questions or want to share your own improvements, feel free to contact me. Happy coding!