Skip to content

mdx 글쓰기 시스템 구현#9

Merged
Dobbymin merged 27 commits into
mainfrom
feat#08-mdx-system
Oct 25, 2025
Merged

mdx 글쓰기 시스템 구현#9
Dobbymin merged 27 commits into
mainfrom
feat#08-mdx-system

Conversation

@Dobbymin

Copy link
Copy Markdown
Owner

📝 상세 내용

MDX 기반 블로그 콘텐츠 관리 시스템 구축

Next.js 15와 MDX를 활용하여 파일 기반 콘텐츠 관리 시스템을 구현했습니다.
데이터베이스 없이 Markdown 파일로 블로그 게시글을 관리할 수 있으며, 서버 사이드
렌더링으로 SEO가 최적화되었습니다.

주요 구현 사항

1. MDX 패키지 설치 및 설정

  • 설치된 패키지:
    • @next/mdx, @mdx-js/loader, @mdx-js/react: Next.js MDX 통합
    • next-mdx-remote: 서버/클라이언트 MDX 렌더링
    • gray-matter: Frontmatter 파싱
    • remark-gfm: GitHub Flavored Markdown 지원
    • rehype-prism-plus: 코드 하이라이팅
  • Next.js 설정: pageExtensions, serverExternalPackages 추가

2. FSD 아키텍처 준수 타입 시스템

  • entities/post/post.type.ts: 기본 Post 타입 정의
    • PostBase, PostContent, PostHeader, PostMeta
  • shared/types/mdx.type.ts: MDX 전용 타입
    • MDXPost, MDXPostMeta (slug 포함)

3. 서버 사이드 MDX 유틸리티 (shared/lib/mdx.ts)

  • getAllPosts(): 모든 게시글 메타데이터 로드 (날짜순 정렬)
  • getPost(slug): 특정 게시글 전체 데이터 로드
  • getAllPostSlugs(): 정적 생성을 위한 slug 목록
  • Node.js fs 모듈로 파일 시스템 접근
  • gray-matter로 frontmatter와 content 분리

4. MDX 커스텀 컴포넌트 (shared/components/features/mdx/MDXComponents.tsx)

  • 디자인 시스템(globals.css) 준수
  • 가독성 최우선 스타일링
  • 구현된 요소:
    • 제목(h1, h2, h3): 반응형 크기, 구분선
    • 본문(p): 적절한 행간, 반응형 폰트
    • 리스트(ul, ol, li): 들여쓰기 및 간격
    • 강조(strong, em): 컬러 시스템 활용
    • 인용구(blockquote): 좌측 보더, 배경색
    • 코드(code, pre): 모노스페이스 폰트, 배경 처리
    • 링크(a): hover 효과, 외부 링크 새 탭
    • 테이블(table, thead, tbody, tr, th, td)

5. 샘플 콘텐츠 작성 (features/post/contents/)

  • getting-started-with-mdx.md: MDX 소개 (Develop)
  • typescript-type-system.md: TypeScript 가이드 (Develop)
  • winter-walk.md: 일상 일기 (Daily)
  • Frontmatter 구조: title, category, date, description

6. 포스트 상세 페이지 구현 (app/post/[id]/page.tsx)

  • PostHeaderSection: 카테고리, 제목, 설명, 날짜 표시
    • 카테고리별 색상 매핑 (Develop: blue, Daily: pink, Review: purple)
  • PostContentsSection: MDXRemote로 동적 렌더링
    • remark-gfm 플러그인 적용
    • 커스텀 컴포넌트 매핑
  • generateStaticParams()로 정적 사이트 생성 (SSG)
  • 404 처리 (notFound())

7. 홈 페이지 PostCard 연동

  • PostCard 컴포넌트 props 기반으로 변경
    • MDXPostMeta 타입 적용
    • 카테고리별 색상, 제목, 설명, 날짜 표시
    • Link 컴포넌트로 상세 페이지 연결
    • hover 효과 (scale 애니메이션)
    • line-clamp로 텍스트 줄임
  • page.tsx를 서버 컴포넌트로 변경
    • getAllPosts()로 실제 데이터 로드
    • 임시 데이터 제거

8. UI/UX 개선 및 스타일링

  • 반응형 레이아웃: PC 크기에서 좌우 여백 복원
  • 인용구 스타일링: 내부 마지막 요소의 하단 여백 제거 ([&>:last-child]:mb-0)
  • 코드 하이라이팅: Prism.js Tomorrow Night 테마 적용
    • rehype-prism-plus 플러그인 사용
    • globals.css에 Prism CSS 인라인 추가
    • 다크 테마 기반 가독성 향상

9. 썸네일 시스템 구현

  • Frontmatter 썸네일 지원:
    • thumbnail 필드 추가 (post.type.ts)
    • public/images/ 경로 이미지 지원
  • 폴백 썸네일:
    • 이미지 없을 시 컬러 배경 + 제목 표시
    • slug 기반 해시 함수로 일관된 색상 선택
    • 5가지 브랜드 컬러 활용 (blue, pink, purple, green, yellow)
  • PostCard 개선:
    • aspect-video 비율 유지
    • hover 시 scale 애니메이션
    • line-clamp로 텍스트 줄임

10. Markdown 포맷팅 설정

  • Prettier 설정:
    • *.md, *.mdx 파일에 proseWrap: "preserve" 적용
    • 자동 줄바꿈 방지로 MDX 문법 보호
    • bold 텍스트 등 인라인 문법 보존

11. PostCard 높이 조정

  • min-h-[300px] 클래스로 최소 높이 설정
  • 콘텐츠 영역 패딩 증가 (p-3p-4)
  • h-full 클래스로 그리드 내 균일한 높이 유지
  • description 3줄 초과 시 말줄임표(...) 표시

12. 에러 해결

  • 클라이언트 컴포넌트에서 서버 전용 코드(fs) 사용 방지
  • page.tsx에서 'use client' 제거
  • next.config.ts 간소화 (turbopack 설정 제거)
  • MDX 파일 내 bold 텍스트 줄바꿈 오류 수정

기술 스택 및 아키텍처

기술 스택

  • Next.js 15: App Router, SSG, 서버 컴포넌트
  • TypeScript: 엄격한 타입 체크
  • MDX: Markdown + JSX
  • Tailwind CSS: 유틸리티 기반 스타일링
  • gray-matter 4.0.3: YAML frontmatter 파싱
  • next-mdx-remote 5.0.0: 동적 MDX 렌더링
  • remark-gfm 4.0.1: GitHub Flavored Markdown
  • rehype-prism-plus 2.0.1: 코드 신택스 하이라이팅
  • Prism.js: Tomorrow Night 테마

FSD 아키텍처 적용

src/
├── entities/post/          # 도메인 타입
├── features/post/          # 게시글 기능
│   ├── contents/          # MDX 파일 저장소
│   └── ui/                # 게시글 UI 컴포넌트
├── shared/
│   ├── lib/               # MDX 유틸리티
│   ├── types/             # 공통 타입
│   └── components/
│       └── features/mdx/  # MDX 커스텀 컴포넌트
└── app/                   # 라우팅

디자인 시스템 준수

  • globals.css에 정의된 색상 변수 활용
    • blog-blue, blog-pink, blog-purple, blog-green
    • blog-black, blog-gray-*
  • 반응형 디자인 (모바일 우선)
  • 가독성을 위한 적절한 폰트 크기, 행간, 여백

주요 특징

✅ 장점

  1. 파일 기반 CMS: 데이터베이스 불필요, Git 버전 관리
  2. 서버 사이드 렌더링: SEO 최적화, 초기 로딩 속도 향상
  3. 타입 안정성: TypeScript로 컴파일 타임 오류 감지
  4. 완전한 커스터마이징: 모든 Markdown 요소를 React 컴포넌트로 대체
  5. 정적 사이트 생성: generateStaticParams()로 빌드 타임 생성
  6. 확장성: remark/rehype 플러그인 체인 구성 가능
  7. 코드 가독성: Prism.js로 Notion 스타일 신택스 하이라이팅
  8. 썸네일 시스템: 이미지 + 폴백 컬러 배경으로 일관된 UI
  9. 반응형 디자인: 모바일/태블릿/데스크톱 최적화
  10. MDX 문법 보호: Prettier 설정으로 자동 줄바꿈 방지

⚠️ 주의사항

  1. 서버 전용 코드(fs, gray-matter)는 서버 컴포넌트에서만 사용
  2. serverExternalPackages 설정 필수
  3. 클라이언트 컴포넌트에서 import 금지
  4. Prism CSS는 패키지 import 대신 globals.css에 인라인 추가
  5. MDX 파일에서 bold 등 인라인 문법은 한 줄로 작성
  6. Prettier 설정으로 *.md, *.mdx 파일의 줄바꿈 보존

📊 빌드 결과

Route (app)                                 Size  First Load JS
┌ ○ /                                      218 B         156 kB
├ ○ /_not-found                            979 B         103 kB
├ ○ /about                                 136 B         102 kB
└ ● /post/[id]                             218 B         156 kB
    ├ /post/tech-spec
    ├ /post/typescript-type-system
    └ /post/winter-walk
  • 3개의 게시글이 정적 HTML로 생성 (SSG)
  • SEO 친화적인 구조
  • 코드 하이라이팅 적용된 상태로 빌드

#️⃣ 이슈 번호

⏰ 현재 버그

없음

📷 스크린샷(선택)

구현된 기능

1. 홈 페이지

  • MDX 파일에서 로드한 실제 데이터로 PostCard 렌더링
  • 카테고리별 색상 구분
  • 날짜순 정렬
  • 썸네일 이미지 또는 컬러 폴백 배경
  • PostCard 높이 조정 (min-h-[300px])
  • hover 시 scale 애니메이션 효과

2. 포스트 상세 페이지

  • Frontmatter 정보 표시 (헤더)
  • MDX 콘텐츠 렌더링 (본문)
  • 커스텀 스타일 적용
  • 반응형 디자인
  • Prism.js Tomorrow Night 테마 적용
  • 코드 블록 신택스 하이라이팅

3. MDX 콘텐츠

  • 제목, 본문, 리스트, 인용구, 코드 블록
  • GitHub Flavored Markdown 지원
  • 외부 링크 자동 새 탭 열기
  • 인용구 내부 여백 최적화
  • bold, italic 등 인라인 문법 정상 작동

4. 코드 하이라이팅

  • Prism.js Tomorrow Night 다크 테마
  • JavaScript, TypeScript, Python 등 다양한 언어 지원
  • 가독성 높은 토큰 색상 구분
  • Notion 스타일의 코드 블록

5. 썸네일 시스템

  • Frontmatter에 thumbnail 필드로 이미지 지정
  • 이미지 없을 시 slug 기반 일관된 컬러 배경
  • 5가지 브랜드 컬러 자동 선택
  • 제목 텍스트 오버레이

6. Prettier 설정

  • Markdown/MDX 파일의 줄바꿈 보존
  • MDX 문법 오류 방지
  • 다른 포맷팅 규칙은 유지

@Dobbymin Dobbymin self-assigned this Oct 25, 2025
@Dobbymin Dobbymin added ✨ Feature 새로운 기능 추가 및 구현하는 경우 📦 Environment 개발 환경세팅을 진행하는 경우 labels Oct 25, 2025
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @Dobbymin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 풀 리퀘스트는 Next.js 프로젝트에 MDX 기반의 블로그 콘텐츠 관리 시스템을 구축하는 것을 목표로 합니다. 데이터베이스 없이 Markdown 및 MDX 파일을 사용하여 게시글을 관리하고, Next.js의 서버 컴포넌트 및 정적 사이트 생성(SSG) 기능을 활용하여 SEO 최적화 및 성능 향상을 도모합니다. MDX 콘텐츠의 파싱, 렌더링, 스타일링을 위한 다양한 패키지 통합과 함께, 게시글 목록 및 상세 페이지 UI/UX 개선, 타입 시스템 도입, 그리고 효율적인 콘텐츠 관리를 위한 유틸리티 함수들이 포함되어 있습니다.

Highlights

  • MDX 기반 블로그 시스템 구축: Next.js 15와 MDX를 활용하여 파일 기반 콘텐츠 관리 시스템을 구현했습니다. 데이터베이스 없이 Markdown 파일로 블로그 게시글을 관리하며, 서버 사이드 렌더링으로 SEO를 최적화했습니다.
  • MDX 패키지 통합 및 설정: @next/mdx, next-mdx-remote, gray-matter, remark-gfm, rehype-prism-plus 등 MDX 렌더링 및 파싱을 위한 핵심 패키지들을 설치하고 Next.js 설정을 업데이트했습니다.
  • FSD 아키텍처 기반 타입 시스템 도입: PostBase, PostMeta, MDXPost 등 게시글 관련 타입을 정의하여 타입 안정성을 확보했습니다.
  • 서버 사이드 MDX 유틸리티 구현: getAllPosts(), getPost(slug), getAllPostSlugs() 함수를 통해 파일 시스템에서 MDX 게시글을 효율적으로 로드하고 관리할 수 있도록 했습니다.
  • MDX 커스텀 컴포넌트 및 스타일링: 디자인 시스템에 맞춰 MDX 요소(제목, 본문, 리스트, 코드 블록 등)를 커스터마이징하고, Prism.js Tomorrow Night 테마를 적용하여 코드 하이라이팅을 구현했습니다.
  • 게시글 상세 페이지 및 홈 페이지 연동: 동적 라우팅을 통해 개별 게시글 상세 페이지를 구현하고, 홈 페이지의 PostCard 컴포넌트를 실제 MDX 데이터와 연동하여 게시글 목록을 표시하도록 개선했습니다.
  • 썸네일 시스템 및 반응형 디자인: Frontmatter를 통한 썸네일 지원 및 이미지 없을 시 슬러그 기반의 컬러 폴백 썸네일 기능을 추가하고, 전반적인 반응형 레이아웃과 UI/UX를 개선했습니다.
  • Prettier 설정 업데이트: .md, .mdx 파일에 proseWrap: "preserve" 옵션을 적용하여 Markdown 포맷팅을 최적화하고 MDX 문법 보호를 강화했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Dobbymin Dobbymin merged commit ef053a7 into main Oct 25, 2025
1 check passed
@Dobbymin Dobbymin deleted the feat#08-mdx-system branch October 25, 2025 12:56

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 Next.js와 MDX를 사용하여 파일 기반 블로그 콘텐츠 관리 시스템을 성공적으로 구현했습니다. 전반적으로 FSD 아키텍처를 잘 따르고 있으며, 코드 하이라이팅, 썸네일 폴백 시스템 등 상세한 기능 구현이 인상적입니다. 몇 가지 개선점을 제안드립니다. 가장 중요한 점은 app/post/[id]/page.tsx에서 params prop을 잘못 사용하고 있는 부분으로, 이는 런타임 에러를 유발할 수 있어 수정이 필요합니다. 또한, 코드 중복을 줄이고 가독성을 높일 수 있는 몇 가지 리팩토링 제안을 포함했습니다. 훌륭한 작업입니다!

Comment on lines +7 to +9
type PostDetailPageProps = {
params: Promise<{ id: string }>;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Next.js의 App Router에서 페이지 컴포넌트의 params prop은 Promise가 아닌 일반 객체로 전달됩니다. 따라서 Promise<{ id: string }> 타입은 잘못되었습니다. { id: string }으로 수정해야 합니다. 이 수정은 17번째 줄의 await params 코드와도 관련이 있습니다.

Suggested change
type PostDetailPageProps = {
params: Promise<{ id: string }>;
};
type PostDetailPageProps = {
params: { id: string };
};

}

export default async function PostDetailPage({ params }: PostDetailPageProps) {
const { id } = await params;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

위에서 언급했듯이 params prop은 Promise가 아니므로 await 키워드를 제거해야 합니다.

Suggested change
const { id } = await params;
const { id } = params;

Comment on lines +11 to +22
const getCategoryColor = (category: string) => {
switch (category.toLowerCase()) {
case 'develop':
return 'text-blog-blue';
case 'daily':
return 'text-blog-pink';
case 'review':
return 'text-blog-purple';
default:
return 'text-blog-green';
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

getCategoryColor 함수는 src/shared/components/features/card/PostCard.tsx 파일에도 동일하게 존재합니다. 또한, 컴포넌트 내부에 정의되어 있어 렌더링 시마다 함수가 재생성됩니다. 코드 중복을 피하고 성능을 최적화하기 위해 이 함수를 src/shared/utils와 같은 공통 유틸리티 파일로 추출하여 두 컴포넌트에서 함께 사용하는 것을 권장합니다.

Comment on lines +13 to +24
const getCategoryColor = (category: string) => {
switch (category.toLowerCase()) {
case 'develop':
return 'text-blog-blue';
case 'daily':
return 'text-blog-pink';
case 'review':
return 'text-blog-purple';
default:
return 'text-blog-green';
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

getCategoryColor 함수는 src/features/post/ui/PostHeaderSection.tsx 파일에도 동일하게 존재합니다. 또한, 컴포넌트 내부에 정의되어 있어 렌더링 시마다 함수가 재생성됩니다. 코드 중복을 피하고 성능을 최적화하기 위해 이 함수를 src/shared/utils와 같은 공통 유틸리티 파일로 추출하여 두 컴포넌트에서 함께 사용하는 것을 권장합니다.

Comment on lines +27 to +43
const getRandomBgColor = (slug: string) => {
const colors = [
'bg-blog-blue',
'bg-blog-pink',
'bg-blog-purple',
'bg-blog-green',
'bg-blog-yellow',
];

// slug를 숫자로 변환하여 일관된 색상 선택
let hash = 0;
for (let i = 0; i < slug.length; i++) {
hash = slug.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % colors.length;
return colors[index];
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

getRandomBgColor 함수가 PostCard 컴포넌트 내부에 정의되어 있어 렌더링 시마다 재생성됩니다. 이 함수는 slug에만 의존하므로 컴포넌트 외부로 이동시켜 불필요한 재생성을 방지하는 것이 성능에 더 좋습니다. 함수 내의 colors 배열도 마찬가지로 외부로 옮기는 것이 좋습니다.

Comment thread src/shared/lib/mdx.ts
Comment on lines +45 to +58
const fullPath = path.join(postsDirectory, `${slug}.md`);
let fileContents: string;

// .md 파일 먼저 확인, 없으면 .mdx 확인
if (fs.existsSync(fullPath)) {
fileContents = fs.readFileSync(fullPath, 'utf8');
} else {
const mdxPath = path.join(postsDirectory, `${slug}.mdx`);
if (fs.existsSync(mdxPath)) {
fileContents = fs.readFileSync(mdxPath, 'utf8');
} else {
return null;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

.md.mdx 파일 확장자를 순차적으로 확인하는 로직이 다소 반복적입니다. 향후 다른 확장자를 추가할 경우를 대비하여, 확장자 배열을 순회하는 방식으로 리팩토링하면 코드가 더 간결해지고 확장성도 좋아집니다.

Suggested change
const fullPath = path.join(postsDirectory, `${slug}.md`);
let fileContents: string;
// .md 파일 먼저 확인, 없으면 .mdx 확인
if (fs.existsSync(fullPath)) {
fileContents = fs.readFileSync(fullPath, 'utf8');
} else {
const mdxPath = path.join(postsDirectory, `${slug}.mdx`);
if (fs.existsSync(mdxPath)) {
fileContents = fs.readFileSync(mdxPath, 'utf8');
} else {
return null;
}
}
const extensions = ['.md', '.mdx'];
let fileContents: string | null = null;
for (const ext of extensions) {
const fullPath = path.join(postsDirectory, `${slug}${ext}`);
if (fs.existsSync(fullPath)) {
fileContents = fs.readFileSync(fullPath, 'utf8');
break;
}
}
if (!fileContents) {
return null;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📦 Environment 개발 환경세팅을 진행하는 경우 ✨ Feature 새로운 기능 추가 및 구현하는 경우

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MDX 라이브러리를 활용하여 마크다운 기반 글쓰기 시스템 구현

1 participant