테크·5 min read
Next.js + MDX로 블로그 만들기 실전 가이드
Next.js App Router와 MDX를 활용해 개발자 블로그를 처음부터 만드는 방법을 단계별로 설명합니다. 정적 생성, 메타데이터, 코드 하이라이팅까지 다룹니다.
왜 Next.js + MDX인가
개발자 블로그를 만들 때 Next.js + MDX 조합은 최적의 선택입니다. 마크다운으로 편하게 글을 쓰면서, React 컴포넌트를 자유롭게 삽입할 수 있기 때문이죠.
장점:
- 마크다운 문법으로 빠른 글 작성
- JSX 컴포넌트 삽입 가능 (차트, 인터랙티브 데모 등)
- SSG로 빌드하면 서버 비용 제로
- Vercel 무료 배포로 운영 비용 최소화
프로젝트 구조
my-blog/
├── content/
│ ├── tech/
│ │ └── my-first-post.mdx
│ └── finance/
│ └── saving-tips.mdx
├── src/
│ ├── app/
│ │ ├── blog/
│ │ │ └── [slug]/
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── components/
│ │ └── mdx-components.tsx
│ └── lib/
│ └── posts.ts
└── next.config.ts
1단계: MDX 파일에서 콘텐츠 읽기
content/ 폴더의 MDX 파일을 파싱하는 유틸리티를 만듭니다.
// src/lib/posts.ts
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const contentDir = path.join(process.cwd(), 'content');
export interface PostMeta {
slug: string;
title: string;
description: string;
date: string;
category: string;
tags: string[];
}
export function getAllPosts(): PostMeta[] {
const categories = fs.readdirSync(contentDir);
const posts: PostMeta[] = [];
for (const category of categories) {
const categoryDir = path.join(contentDir, category);
const files = fs.readdirSync(categoryDir);
for (const file of files) {
if (!file.endsWith('.mdx')) continue;
const source = fs.readFileSync(
path.join(categoryDir, file), 'utf-8'
);
const { data } = matter(source);
posts.push({
slug: file.replace('.mdx', ''),
...data as Omit<PostMeta, 'slug'>,
});
}
}
return posts.sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
);
}
2단계: 동적 라우트로 게시글 페이지 만들기
// src/app/blog/[slug]/page.tsx
import { getAllPosts } from '@/lib/posts';
import { notFound } from 'next/navigation';
export function generateStaticParams() {
const posts = getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const posts = getAllPosts();
const post = posts.find((p) => p.slug === slug);
if (!post) notFound();
// MDX 렌더링 로직
return (
<article>
<h1>{post.title}</h1>
<time>{post.date}</time>
{/* MDX 콘텐츠 렌더링 */}
</article>
);
}
3단계: SEO 메타데이터 설정
// src/app/blog/[slug]/page.tsx에 추가
import type { Metadata } from 'next';
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const posts = getAllPosts();
const post = posts.find((p) => p.slug === slug);
if (!post) return {};
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: 'article',
publishedTime: post.date,
},
};
}
4단계: 코드 하이라이팅
개발 블로그에 코드 하이라이팅은 필수입니다. rehype-pretty-code를 사용하면 VS Code 수준의 하이라이팅이 가능합니다.
npm install rehype-pretty-code shiki
// next.config.ts
import rehypePrettyCode from 'rehype-pretty-code';
const nextConfig = {
// MDX 플러그인 설정에서 rehypePrettyCode 추가
};
5단계: RSS 피드 생성
블로그 구독자를 위해 RSS 피드를 제공합니다.
// src/app/feed.xml/route.ts
import { getAllPosts } from '@/lib/posts';
export function GET() {
const posts = getAllPosts();
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>DevFinance Blog</title>
<link>https://devfinance.kr</link>
${posts.map((post) => `
<item>
<title>${post.title}</title>
<link>https://devfinance.kr/blog/${post.slug}</link>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
<description>${post.description}</description>
</item>`).join('')}
</channel>
</rss>`;
return new Response(xml, {
headers: { 'Content-Type': 'application/xml' },
});
}
배포
Vercel에 연결하면 git push만으로 자동 배포됩니다.
npm install -g vercel
vercel
무료 플랜으로 충분하고, 커스텀 도메인도 연결할 수 있습니다.
정리
| 단계 | 핵심 |
|---|---|
| 콘텐츠 관리 | content/ 폴더 + gray-matter 파싱 |
| 페이지 생성 | generateStaticParams로 SSG |
| SEO | generateMetadata로 메타 태그 |
| 코드 하이라이팅 | rehype-pretty-code + shiki |
| 배포 | Vercel 무료 배포 |
마크다운 파일 하나 추가하면 새 글이 발행되는 시스템. 개발자에게 이보다 편한 블로그 플랫폼은 없습니다.