Welcome to my blog! How did I build it?

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.

Project setup

If you’d like to follow along with this blog, make sure you are using the following dependencies:

Building the blog section

A blog typically follows a straightforward routing structure. At a minimum, the routing system should support two key functionalities:

Individual blog posts

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:

  1. Navigate to the post folder inside the blog directory.
  2. Create a new folder using the desired slug for your post. (This slug will define the post's URL.)
  3. Inside that folder, create a page.mdx file. This file will contain both the post's content and its metadata.
app/blog/post/example/page.mdx
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.

app/blog/post/layout.tsx
export default function Layout(props: { children: ReactNode }) {
  return (
    <div className="container p-4 prose lg:prose-lg dark:prose-invert">
      {props.children}
    </div>
  );
}

List of blog posts

Fetching the posts

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.

app/blog/page.tsx
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
};

Rendering the posts

Now with the data at our fingertips we can start representing it visually. So lets create a card component for a blog post.

components/blog-post-card.tsx
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!

app/blog/page.tsx
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!