BUILDING A MODERN BLOG WITH NEXT.JS, CONTENTLAYER, AND MAGIC UI
Building a Modern Blog with Next.js, Contentlayer, and Magic UI
Creating a personal blog doesn't have to be complicated. In this post, I'll walk through how I built this blog using a simple yet powerful stack: Next.js for the framework, Contentlayer for content management, and Magic UI for beautiful visual effects.
The Foundation: Next.js App Router
This blog is built on Next.js, which provides an excellent foundation for React applications with built-in routing, server-side rendering, and static site generation capabilities. The App Router in Next.js 13+ makes creating routes as simple as adding directories to your project structure.
// src/app/page.tsx - The main page component
export default function Home() {
// Blog post listing logic
return (
<div>
<Header />
<main>
{/* Blog posts organized by year */}
</main>
<Footer />
</div>
);
}
The simplicity of Next.js allows me to focus on content rather than complex configuration.
Content Management with Contentlayer
For content management, I chose Contentlayer - a content SDK that transforms your content into type-safe JSON data. This means I can write my blog posts in Markdown/MDX and have them automatically converted to structured data that I can use in my React components.
Here's how the Contentlayer configuration looks:
// contentlayer.config.ts
import { defineDocumentType, makeSource } from 'contentlayer2/source-files';
export const Post = defineDocumentType(() => ({
name: 'Post',
filePathPattern: `**/*.mdx`,
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
date: { type: 'date', required: true },
summary: { type: 'string', required: true },
},
computedFields: {
url: {
type: 'string',
resolve: (post) => `/posts/${post._raw.flattenedPath}`,
},
},
}));
export default makeSource({ contentDirPath: 'posts', documentTypes: [Post] });
With this setup, I can create blog posts as simple MDX files in the posts
directory:
---
title: My Blog Post Title
date: '2024-04-26'
summary: A brief summary of the post
---
# My Blog Post
Content goes here...
Contentlayer automatically validates my content, generates TypeScript types, and makes the data available to my components. This means I get type safety and auto-completion when working with my content.
Styling with Magic UI
The visual appeal of this blog comes from Magic UI, a collection of beautiful UI components and effects for React applications. Magic UI provides components like:
- HyperText: For the scramble text effect on post titles
- AuroraText: For the gradient text effect on the blog title
- GridPattern: For the subtle grid background
- Dock: For the macOS-style dock at the bottom of the page
These components add visual interest without overwhelming the content. Here's an example of how I use the AuroraText component for the blog title:
<AuroraText
className="text-4xl font-bold md:text-5xl"
colors={["#FF0080", "#7928CA", "#0070F3", "#38bdf8"]}
speed={1.5}
>
Pranay.wtf
</AuroraText>
And the HyperText component for post titles:
<HyperText
className="text-lg font-medium py-0"
duration={800}
animateOnHover={false}
startOnView={false}
delay={300 + index * 100}
>
{post.title.toUpperCase()}
</HyperText>
Dark Mode Support
The blog includes dark mode support using the next-themes
package, which makes it easy to add theme switching functionality:
const { setTheme, theme } = useTheme();
// Toggle theme button
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
{theme === "light" ? <MoonIcon /> : <SunIcon />}
</button>
Organizing Posts by Year
One of the key design elements is organizing posts by year, similar to many minimalist blogs:
// Group posts by year
const postsByYear = allPosts.reduce((acc, post) => {
const year = getYear(parseISO(post.date));
if (!acc[year]) {
acc[year] = [];
}
acc[year].push(post);
return acc;
}, {});
// Sort years in descending order
const years = Object.keys(postsByYear)
.map(Number)
.sort((a, b) => b - a);
// Render posts grouped by year
{years.map((year) => (
<div key={year}>
<h2>{year}</h2>
{postsByYear[year].map((post) => (
<div key={post._id}>
<Link href={post.url}>{post.title}</Link>
<time>{format(parseISO(post.date), "MMM dd")}</time>
</div>
))}
</div>
))}
Social Links with a Dock
The footer features a macOS-style dock with social links, created using the Dock component from Magic UI:
<Dock className="bg-background/50 border-border shadow-lg">
<DockIcon>
<Link href="/">
<HomeIcon />
</Link>
</DockIcon>
{/* Social links */}
<DockIcon>
<Link href="https://github.com/pranaysinghdev">
<GithubIcon />
</Link>
</DockIcon>
{/* More social links */}
</Dock>
Conclusion
Building this blog was an exercise in simplicity. By leveraging Next.js, Contentlayer, and Magic UI, I was able to create a visually appealing blog without unnecessary complexity. The focus remains on the content, with just enough visual flair to make the experience enjoyable.
The entire setup is:
- Framework: Next.js with App Router
- Content Management: Contentlayer with MDX
- Styling: Tailwind CSS with Magic UI components
- Theming: Dark/light mode with next-themes
This approach allows me to write blog posts in Markdown, automatically generate routes, and focus on creating content rather than managing infrastructure.
If you're looking to create your own blog, I highly recommend this stack for its simplicity, flexibility, and developer experience.