Next.js로 블로그 프로젝트를 시작했다면, 이제 진짜 “블로그 같은 기능”을 만들어볼 차례죠.
바로 Markdown(.md) 파일을 블로그 글처럼 렌더링하는 기능입니다.
Markdown은 개발자들이 가장 사랑하는 문서 형식 중 하나입니다. 이 글에서는 Next.js 프로젝트에서 Markdown 파일을 읽고, 글 목록과 상세 페이지를 구성하는 방법을 차근차근 설명드릴게요.
📁 1. posts 폴더 만들고 .md 파일 생성하기
먼저 프로젝트 루트에 posts
폴더를 만들고, Markdown 포스트 파일을 작성합니다.
my-blog/
├── posts/
│ ├── hello-world.md
│ └── nextjs-guide.md
hello-world.md
예시:
---
title: "Hello, World!"
date: "2024-06-10"
tags: ["Next.js", "블로그"]
---
이것은 내 첫 번째 블로그 포스트입니다.
Next.js와 Markdown으로 만든 정적 블로그의 시작이에요!
---
부분은 YAML 형태의 메타데이터로, 제목이나 날짜, 태그 등을 정의할 수 있어요.
📦 2. 필요한 패키지 설치
Markdown 파일을 읽고 메타데이터를 파싱하기 위해 다음 패키지를 설치합니다.
npm install gray-matter remark remark-html
gray-matter
: 메타데이터(frontmatter)를 추출remark
+remark-html
: Markdown을 HTML로 변환
🔧 3. Markdown 파일 읽어오는 유틸 만들기
lib
폴더를 만들고, posts.ts
파일에 아래 함수를 추가합니다.
// lib/posts.ts
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const postsDirectory = path.join(process.cwd(), 'posts');
export function getSortedPostsData() {
const fileNames = fs.readdirSync(postsDirectory);
const allPostsData = fileNames.map((fileName) => {
const id = fileName.replace(/\.md$/, '');
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const matterResult = matter(fileContents);
return {
id,
...(matterResult.data as { date: string; title: string }),
};
});
return allPostsData.sort((a, b) => (a.date < b.date ? 1 : -1));
}
이제 글 목록 데이터를 불러올 수 있습니다!
📜 4. 블로그 글 목록 페이지 만들기
이제 pages/index.tsx
에서 이 데이터를 불러와 렌더링해봅시다.
// pages/index.tsx
import { getSortedPostsData } from '../lib/posts';
export async function getStaticProps() {
const allPostsData = getSortedPostsData();
return {
props: {
allPostsData,
},
};
}
export default function Home({ allPostsData }: any) {
return (
<div className="max-w-2xl mx-auto mt-10">
<h1 className="text-3xl font-bold mb-6">블로그 글 목록</h1>
<ul>
{allPostsData.map(({ id, date, title }) => (
<li key={id}>
<a href={`/posts/${id}`} className="text-blue-600">{title}</a> - {date}
</li>
))}
</ul>
</div>
);
}
이제 정적인 글 목록 페이지가 생성됩니다!
🧩 5. 동적 라우팅으로 글 상세 페이지 구성
pages/posts/[id].tsx
를 생성하여 글 내용을 표시합니다.
// pages/posts/[id].tsx
import { getAllPostIds, getPostData } from '../../lib/posts';
export async function getStaticPaths() {
const paths = getAllPostIds();
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }: any) {
const postData = await getPostData(params.id);
return {
props: {
postData,
},
};
}
export default function Post({ postData }: any) {
return (
<article className="max-w-2xl mx-auto mt-10">
<h1 className="text-3xl font-bold">{postData.title}</h1>
<div className="text-gray-500 mb-4">{postData.date}</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</article>
);
}
이때 lib/posts.ts
에 getAllPostIds
와 getPostData
함수도 추가해야 해요.
export function getAllPostIds() {
const fileNames = fs.readdirSync(postsDirectory);
return fileNames.map((fileName) => ({
params: { id: fileName.replace(/\.md$/, '') },
}));
}
export async function getPostData(id: string) {
const fullPath = path.join(postsDirectory, `${id}.md`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
const matterResult = matter(fileContents);
const remark = await import('remark');
const html = await import('remark-html');
const processedContent = await remark.default().use(html.default).process(matterResult.content);
const contentHtml = processedContent.toString();
return {
id,
contentHtml,
...(matterResult.data as { date: string; title: string }),
};
}
✅ 마무리하며
이제 우리는 Markdown 파일을 읽어서 정적 HTML 블로그 포스트로 렌더링하는 데 성공했습니다. 앞으로는 이 위에 카테고리 필터, 태그, 검색 기능을 올려 나만의 완성형 블로그를 만들어갈 수 있어요.
다음 글에서는 MDX를 사용해 Markdown 안에 React 컴포넌트를 넣는 방법도 소개할게요!
“내 글은 코드처럼 작동한다.”
'Next.js' 카테고리의 다른 글
블로그에 다크모드 적용하기 — next-themes로 UX 높이기 (2) | 2025.06.23 |
---|---|
스타일링 선택하기 — Tailwind CSS vs Styled Components (2) | 2025.06.22 |
MDX로 Markdown에 컴포넌트 삽입하기 — 코드블럭, 이미지도 자유롭게! (1) | 2025.06.21 |
동적 라우팅으로 글 URL 구조 잡기 — [slug].tsx 완전 이해하기 (2) | 2025.06.20 |
📘 Next.js로 블로그 프로젝트 시작하기 — 설치부터 폴더 구조까지 (1) | 2025.06.18 |