정적 블로그 구조 만들기 — Markdown 기반 포스트 렌더링하기

2025. 6. 19. 09:00·Next.js
728x90
반응형

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 컴포넌트를 넣는 방법도 소개할게요!

“내 글은 코드처럼 작동한다.”

728x90
반응형

'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
'Next.js' 카테고리의 다른 글
  • 스타일링 선택하기 — Tailwind CSS vs Styled Components
  • MDX로 Markdown에 컴포넌트 삽입하기 — 코드블럭, 이미지도 자유롭게!
  • 동적 라우팅으로 글 URL 구조 잡기 — [slug].tsx 완전 이해하기
  • 📘 Next.js로 블로그 프로젝트 시작하기 — 설치부터 폴더 구조까지
코드를 걷는 사람
코드를 걷는 사람
devwanderer 님의 블로그 입니다.
  • 코드를 걷는 사람
    터미널 밖으로 나온 개발자
    코드를 걷는 사람
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Flutter
        • Flutter 게시판 앱 만들기
        • Flutter 뉴스 앱 만들기
        • Flutter 메모 앱 만들기
        • Flutter 캘린더 앱 만들기
        • Flutter 날씨 앱 만들기
      • Next.js
      • Ruby On Rails
  • 블로그 메뉴

    • 홈
    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    UI디자인
    메모앱
    flutter상태관리
    북마크기능
    UIUX
    flutter개발
    개발블로그
    openweather
    Flutter
    캘린더앱
    RubyOnRails
    백엔드개발
    flutter기초
    flutter게시판
    모바일앱개발
    flutter디자인
    ActiveRecord
    정적사이트
    flutterui
    table_calendar
    다크모드
    Nextjs
    뉴스앱
    감성앱
    rails보안
    코드를걷는사람
    fluttertips
    flutter앱개발
    Firebase
    날씨앱
  • 최근 댓글

  • 최근 글

  • 반응형
    250x250
  • hELLO· Designed By정상우.v4.10.3
코드를 걷는 사람
정적 블로그 구조 만들기 — Markdown 기반 포스트 렌더링하기
상단으로

티스토리툴바