mdx 글쓰기 시스템 구현#9
Conversation
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
이 PR은 Next.js와 MDX를 사용하여 파일 기반 블로그 콘텐츠 관리 시스템을 성공적으로 구현했습니다. 전반적으로 FSD 아키텍처를 잘 따르고 있으며, 코드 하이라이팅, 썸네일 폴백 시스템 등 상세한 기능 구현이 인상적입니다. 몇 가지 개선점을 제안드립니다. 가장 중요한 점은 app/post/[id]/page.tsx에서 params prop을 잘못 사용하고 있는 부분으로, 이는 런타임 에러를 유발할 수 있어 수정이 필요합니다. 또한, 코드 중복을 줄이고 가독성을 높일 수 있는 몇 가지 리팩토링 제안을 포함했습니다. 훌륭한 작업입니다!
| type PostDetailPageProps = { | ||
| params: Promise<{ id: string }>; | ||
| }; |
There was a problem hiding this comment.
Next.js의 App Router에서 페이지 컴포넌트의 params prop은 Promise가 아닌 일반 객체로 전달됩니다. 따라서 Promise<{ id: string }> 타입은 잘못되었습니다. { id: string }으로 수정해야 합니다. 이 수정은 17번째 줄의 await params 코드와도 관련이 있습니다.
| type PostDetailPageProps = { | |
| params: Promise<{ id: string }>; | |
| }; | |
| type PostDetailPageProps = { | |
| params: { id: string }; | |
| }; |
| } | ||
|
|
||
| export default async function PostDetailPage({ params }: PostDetailPageProps) { | ||
| const { id } = await params; |
| 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'; | ||
| } | ||
| }; |
| 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'; | ||
| } | ||
| }; |
| 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]; | ||
| }; |
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
.md와 .mdx 파일 확장자를 순차적으로 확인하는 로직이 다소 반복적입니다. 향후 다른 확장자를 추가할 경우를 대비하여, 확장자 배열을 순회하는 방식으로 리팩토링하면 코드가 더 간결해지고 확장성도 좋아집니다.
| 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; | |
| } |
📝 상세 내용
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: 코드 하이라이팅pageExtensions,serverExternalPackages추가2. FSD 아키텍처 준수 타입 시스템
entities/post/post.type.ts: 기본 Post 타입 정의PostBase,PostContent,PostHeader,PostMetashared/types/mdx.type.ts: MDX 전용 타입MDXPost,MDXPostMeta(slug 포함)3. 서버 사이드 MDX 유틸리티 (
shared/lib/mdx.ts)getAllPosts(): 모든 게시글 메타데이터 로드 (날짜순 정렬)getPost(slug): 특정 게시글 전체 데이터 로드getAllPostSlugs(): 정적 생성을 위한 slug 목록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)title,category,date,description6. 포스트 상세 페이지 구현 (
app/post/[id]/page.tsx)PostHeaderSection: 카테고리, 제목, 설명, 날짜 표시PostContentsSection: MDXRemote로 동적 렌더링remark-gfm플러그인 적용generateStaticParams()로 정적 사이트 생성 (SSG)notFound())7. 홈 페이지 PostCard 연동
PostCard컴포넌트 props 기반으로 변경page.tsx를 서버 컴포넌트로 변경getAllPosts()로 실제 데이터 로드8. UI/UX 개선 및 스타일링
[&>:last-child]:mb-0)rehype-prism-plus플러그인 사용globals.css에 Prism CSS 인라인 추가9. 썸네일 시스템 구현
thumbnail필드 추가 (post.type.ts)public/images/경로 이미지 지원aspect-video비율 유지10. Markdown 포맷팅 설정
*.md,*.mdx파일에proseWrap: "preserve"적용11. PostCard 높이 조정
min-h-[300px]클래스로 최소 높이 설정p-3→p-4)h-full클래스로 그리드 내 균일한 높이 유지12. 에러 해결
fs) 사용 방지page.tsx에서'use client'제거next.config.ts간소화 (turbopack 설정 제거)기술 스택 및 아키텍처
기술 스택
FSD 아키텍처 적용
디자인 시스템 준수
globals.css에 정의된 색상 변수 활용blog-blue,blog-pink,blog-purple,blog-greenblog-black,blog-gray-*주요 특징
✅ 장점
generateStaticParams()로 빌드 타임 생성fs,gray-matter)는 서버 컴포넌트에서만 사용serverExternalPackages설정 필수globals.css에 인라인 추가📊 빌드 결과
#️⃣ 이슈 번호
⏰ 현재 버그
없음
📷 스크린샷(선택)
구현된 기능
1. 홈 페이지
2. 포스트 상세 페이지
3. MDX 콘텐츠
4. 코드 하이라이팅
5. 썸네일 시스템
thumbnail필드로 이미지 지정6. Prettier 설정