diff --git a/.context/current-status.md b/.context/current-status.md
deleted file mode 100644
index 9d11ae2..0000000
--- a/.context/current-status.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# current-status.md
-
-최근 완료 작업과 다음 작업 요소를 정리한 문서입니다.
-
-## 최근 완료 작업
-- ✅ **CLAUDE.md 문서 체계화**: @import 구문으로 모듈화된 문서 구조 완성
-- ✅ **development-guidelines.md 최적화**: 실제 프로젝트의 도메인 중심 설계, 모듈 레벨 캐싱, 태그 그래프 시스템 패턴 중심으로 재작성
-- ✅ **styling-guide.md 최적화**: 실제 컴포넌트 패턴(CategoryBadge, TagBadge, CVA 시스템) 반영한 실용적 가이드
-- ✅ **git-workflow.md 최적화**: 실제 프로젝트의 브랜치 패턴과 커밋 컨벤션 반영
-- ✅ **commands.md 최적화**: 실제 package.json 스크립트 기반, 개발서버/빌드 금지 지침 포함한 실용적 명령어 가이드
-
-## 다음 작업 요소
-- 추후 정의
diff --git a/.context/development-guidelines.md b/.context/development-guidelines.md
index c99f9f9..8e72097 100644
--- a/.context/development-guidelines.md
+++ b/.context/development-guidelines.md
@@ -1,157 +1,51 @@
# 개발 가이드라인
-이 블로그 프로젝트의 핵심 패턴과 구현 원칙을 정의합니다.
+## 필수 원칙
-## 아키텍처 패턴
+### 도메인 API 사용
+- ✅ `import { getCategoryById } from '@/domain/blog'`
+- ❌ 직접 파일 시스템 접근 금지
-### 도메인 중심 설계
-- **도메인 로직**: `src/domain/blog/` 에 통합
-- **UI 컴포넌트**: `src/app/` 하위에 구현
-- **타입 정의**: `src/domain/blog/types.ts` 중앙 집중
+### 타입 안전성
+- ✅ 모든 Props 인터페이스 명시
+- ❌ `any` 타입 사용 금지
+- ✅ 도메인 타입 재사용 (`CategoryId`, `Post`, `Tag`)
-```typescript
-// 도메인 API 사용 예시
-import { getCategoryById, getPostsByCategory, getRelatedPostsByTags } from '@/domain/blog'
-
-const category = getCategoryById('dev')
-const posts = getPostsByCategory('dev')
-const related = getRelatedPostsByTags(currentSlug, 3)
-```
-
-### 모듈 레벨 캐싱
-빌드 타임에 모든 데이터를 사전 로드하여 SSG 최적화
-
-```typescript
-// src/domain/blog/index.ts
-export const allPosts = await getAllPosts()
-export const allCategories = await getAllCategories()
-export const allTags = extractTagsFromPosts(allPosts)
-export const tagGraph = createTagGraph(allPosts, allTags)
-export const tagClusters = createTagClusters(tagGraph)
-```
-
-### 태그 그래프 시스템
-graphology 라이브러리로 태그 간 관계 분석
-
-```typescript
-// 태그 관계 분석
-const relationships = getTagRelationships()
-const clusters = getTagClusters()
-const relatedPosts = getRelatedPostsByTags(currentSlug, 3)
-```
+### 아키텍처 구조
+- **도메인 로직**: `src/domain/blog/` 통합
+- **UI 컴포넌트**: `src/app/` 분리
+- **모듈 레벨 캐싱**: 빌드 타임 데이터 사전 로드
## 콘텐츠 구조
-### MDX 파일 구조
+### MDX 파일
```
src/contents/
-├── dev/ # 개발 카테고리
-│ ├── category.json
-│ └── *.mdx
-└── life/ # 일상 카테고리
- ├── category.json
- └── *.mdx
+├── dev/category.json + *.mdx
+└── life/category.json + *.mdx
```
-### 포스트 frontmatter
+### frontmatter 필수 필드
```yaml
----
title: '포스트 제목'
date: 2025-01-17
tags: ['tag1', 'tag2']
-description: '포스트 설명'
-category: 'dev' # 또는 'life'
----
-```
-
-### 카테고리 메타데이터
-```json
-{
- "name": "개발",
- "description": "개발 관련 포스트",
- "color": "blue",
- "icon": "💻"
-}
-```
-
-## 컴포넌트 패턴
-
-### 타입 정의
-```typescript
-interface PostHeaderProps {
- title: string
- date: string
- tags: string[]
- category?: CategoryId
- readingTime?: number
- author?: string
- className?: string
-}
-```
-
-### 컴포넌트 구현
-```typescript
-// 서버 컴포넌트 기본, 상호작용 필요시만 클라이언트
-export function PostHeader({ title, date, tags, category }: PostHeaderProps) {
- return (
-
-
- {category && }
-
{title}
-
-
-
- )
-}
+category: 'dev' # 또는 'life'
```
-## 품질 기준
+## 품질 체크리스트
-### TypeScript 엄격 모드
-- `any` 타입 사용 금지
-- 모든 Props 인터페이스 명시적 정의
-- 도메인 타입 재사용 (`CategoryId`, `Post`, `Tag`)
+### 컴포넌트 작성시
+- [ ] Props 인터페이스 정의
+- [ ] 서버 컴포넌트 우선 (상호작용 필요시만 클라이언트)
+- [ ] `cn()` 유틸리티로 클래스 병합
-### 테스트 패턴
-```typescript
-// 비즈니스 로직 테스트
-describe('태그 그래프 시스템', () => {
- it('관련 포스트를 태그 유사도로 찾는다', () => {
- const related = getRelatedPostsByTags('test-slug', 3)
- expect(related).toHaveLength(3)
- })
-})
-```
+### 데이터 처리시
+- [ ] 도메인 API 함수 사용
+- [ ] 타입 안전성 보장
+- [ ] 빌드 타임 사전 계산 활용
### 성능 최적화
-- 모든 데이터 빌드 타임 사전 계산
-- `generateStaticParams()` 사용한 정적 경로 생성
-- 컴포넌트 간 props 최소화
-
-## 디렉토리 구조
-
-```
-src/
-├── app/ # Next.js App Router
-│ ├── [category]/ # 카테고리 페이지
-│ │ ├── [slug]/ # 포스트 상세
-│ │ └── _components/ # 페이지별 컴포넌트
-│ └── _components/ # 전역 컴포넌트
-├── domain/blog/ # 도메인 로직
-│ ├── index.ts # 공개 API + 캐싱
-│ ├── types.ts # 타입 정의
-│ └── logic/ # 비즈니스 로직
-│ ├── posts.ts
-│ ├── categories.ts
-│ └── tags.ts
-└── contents/ # MDX 콘텐츠
- ├── dev/
- └── life/
-```
-
-## 구현 원칙
-
-1. **도메인 API 사용**: 직접 파일 시스템 접근 금지
-2. **타입 안전성**: 모든 데이터 흐름 타입 보장
-3. **모듈 레벨 캐싱**: 빌드 타임 데이터 사전 로드
-4. **컴포넌트 단순화**: 단일 책임 원칙
\ No newline at end of file
+- [ ] `generateStaticParams()` 정적 경로 생성
+- [ ] 컴포넌트 간 props 최소화
+- [ ] 모듈 레벨 캐싱 활용
\ No newline at end of file
diff --git a/.context/git-workflow.md b/.context/git-workflow.md
index c4aaf94..53e5bb7 100644
--- a/.context/git-workflow.md
+++ b/.context/git-workflow.md
@@ -11,21 +11,9 @@
- **docs/**: 문서 작업용 브랜치
### 브랜치 네이밍
-```bash
-# 기능 개발 (실제 프로젝트 패턴)
-feat/entities
-feat/post-navigation
-feat/entity-optimization-and-category-system
-
-# 버그 수정
-fix/resolve-navigation-issue
-fix/correct-typo-in-header
-
-# 문서/설정 작업
-docs/claude-pr-workflow
-config/github-templates
-config/tailwind-setup
-```
+- `feat/feature-name` - 기능 개발
+- `fix/issue-name` - 버그 수정
+- `docs/document-name` - 문서 작업
## 커밋 컨벤션
@@ -49,7 +37,20 @@ footer (optional)
## 논리적 단위 커밋
-- 커밋은 변경사항 그룹끼리 묶여서 작성
+### 기본 원칙
+- **하나의 커밋 = 하나의 논리적 변경사항**
+- 서로 다른 기능/목적의 변경사항은 별도 커밋으로 분리
+- 각 커밋은 독립적으로 동작할 수 있어야 함
+
+### 커밋 워크플로우 (TodoWrite 필수)
+1. **변경사항 분석**: `git status`, `git diff` 확인
+2. **TodoWrite로 논리적 단위 계획**: 각 기능별 커밋 목록 작성
+3. **단계별 커밋**: 할일 완료하며 순차 실행
+4. **진행 추적**: TodoWrite에서 완료 체크
+
+### 올바른 커밋 분리
+- ✅ 기능별 분리: 메타데이터 / RSS / 레이아웃
+- ❌ 모든 변경사항을 한 번에 커밋
## PR 프로세스
diff --git a/.context/quality-checklist.md b/.context/quality-checklist.md
new file mode 100644
index 0000000..9726ceb
--- /dev/null
+++ b/.context/quality-checklist.md
@@ -0,0 +1,41 @@
+# 품질 체크리스트
+
+## 커밋 전 필수 체크
+
+### TodoWrite 사용
+- [ ] 여러 변경사항이 있을 때 TodoWrite로 논리적 단위 계획
+- [ ] 각 커밋 단위별로 할일 목록 작성
+- [ ] 완료된 항목 실시간 체크
+
+### 코드 품질
+- [ ] `pnpm biome:check` 통과
+- [ ] `pnpm type` 통과
+- [ ] `pnpm test` 통과
+
+### 아키텍처 준수
+- [ ] 도메인 API 사용 (`@/domain/blog` import)
+- [ ] `any` 타입 사용 없음
+- [ ] Props 인터페이스 정의
+
+## PR 생성 전 체크
+
+### 문서 업데이트
+- [ ] 변경된 영역의 CLAUDE.md 업데이트
+- [ ] 새로운 기능/패턴이 있으면 가이드라인 반영
+
+### 최종 검증
+- [ ] 모든 커밋이 논리적 단위로 분리됨
+- [ ] 커밋 메시지가 명확함
+- [ ] 테스트 통과 확인
+
+## 일상 개발 체크
+
+### 컴포넌트 작성
+- [ ] 서버 컴포넌트 우선 (상호작용 필요시만 클라이언트)
+- [ ] `cn()` 유틸리티로 클래스 병합
+- [ ] TypeScript 엄격 모드 준수
+
+### 성능 최적화
+- [ ] `generateStaticParams()` 정적 경로 생성
+- [ ] 모듈 레벨 캐싱 활용
+- [ ] 불필요한 props 전달 최소화
\ No newline at end of file
diff --git a/.kiro/steering/ui-design-system.md b/.kiro/steering/ui-design-system.md
index c991333..73ef4e7 100644
--- a/.kiro/steering/ui-design-system.md
+++ b/.kiro/steering/ui-design-system.md
@@ -144,7 +144,7 @@ text-4xl: 36px / 40px
// 최대 너비 제한
-
+
{/* 콘텐츠 */}
```
diff --git a/CLAUDE.md b/CLAUDE.md
index eb0b71e..399216e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -69,18 +69,18 @@ Next.js 15 기반 정적 블로그의 아키텍처 지침과 작업 가이드입
- @.context/git-workflow.md - 브랜치 전략, 커밋 컨벤션
### 참조 정보
-- @.context/current-status.md - 최신 작업 상태
- @.context/project-overview.md - 기술 스택, 상세 구조
- @.context/commands.md - 개발 명령어, 빌드 설정
+- @.context/quality-checklist.md - 품질 체크리스트
-## 🚀 작업 흐름
+## 🚀 작업 원칙
-1. **작업 영역 파악**: 수정할 파일의 디렉토리 확인
-2. **컨텍스트 확보**: 해당 영역의 CLAUDE.md 및 상위 문서 읽기
-3. **아키텍처 준수**: 도메인 분리, 타입 안전성, 성능 원칙 적용
-4. **문서 업데이트**: 작업 완료 후 해당 CLAUDE.md 즉시 업데이트
-5. **상위 영향 확인**: 변경사항이 상위 아키텍처에 미치는 영향 검토
+### 필수 체크
+- **TodoWrite 사용**: 복잡한 작업시 할일 목록으로 계획
+- **스코프별 CLAUDE.md 업데이트**: 작업 완료 후 즉시 반영
+- **품질 검증**: @.context/quality-checklist.md 준수
----
-
-💡 **Tip**: 새로운 기능 개발 시 먼저 해당 영역의 CLAUDE.md를 확인하고, 기존 패턴을 따라 일관성을 유지하세요.
\ No newline at end of file
+### 아키텍처 준수
+- **도메인 API 사용**: 직접 파일 시스템 접근 금지
+- **타입 안전성**: `any` 금지, 인터페이스 명시
+- **논리적 단위 커밋**: TodoWrite로 계획 후 분리 커밋
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 22daa92..f2314eb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -119,7 +119,7 @@ importers:
version: 5.8.3
vitest:
specifier: ^3.2.4
- version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(yaml@2.8.0)
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
packages:
@@ -1333,6 +1333,9 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
+ get-tsconfig@4.10.1:
+ resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
+
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
@@ -1960,6 +1963,9 @@ packages:
remark-rehype@11.1.2:
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
rollup@4.45.0:
resolution: {integrity: sha512-WLjEcJRIo7i3WDDgOIJqVI2d+lAC3EwvOGy+Xfq6hs+GQuAA4Di/H72xmXkOhrIWFg2PFYSKZYfH0f4vfKXN4A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -2167,6 +2173,11 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tsx@4.20.3:
+ resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
tw-animate-css@1.3.5:
resolution: {integrity: sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA==}
@@ -3049,7 +3060,7 @@ snapshots:
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
- vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(yaml@2.8.0)
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies:
- supports-color
@@ -3061,13 +3072,13 @@ snapshots:
chai: 5.2.1
tinyrainbow: 2.0.0
- '@vitest/mocker@3.2.4(vite@7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))':
+ '@vitest/mocker@3.2.4(vite@7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
+ vite: 7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -3098,7 +3109,7 @@ snapshots:
sirv: 3.0.1
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
- vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(yaml@2.8.0)
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
'@vitest/utils@3.2.4':
dependencies:
@@ -3405,6 +3416,11 @@ snapshots:
fsevents@2.3.3:
optional: true
+ get-tsconfig@4.10.1:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+ optional: true
+
glob@10.4.5:
dependencies:
foreground-child: 3.3.1
@@ -4339,6 +4355,9 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
+ resolve-pkg-maps@1.0.0:
+ optional: true
+
rollup@4.45.0:
dependencies:
'@types/estree': 1.0.8
@@ -4572,6 +4591,14 @@ snapshots:
tslib@2.8.1: {}
+ tsx@4.20.3:
+ dependencies:
+ esbuild: 0.25.6
+ get-tsconfig: 4.10.1
+ optionalDependencies:
+ fsevents: 2.3.3
+ optional: true
+
tw-animate-css@1.3.5: {}
typescript@5.8.3: {}
@@ -4657,13 +4684,13 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
- vite-node@3.2.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0):
+ vite-node@3.2.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
+ vite: 7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -4678,7 +4705,7 @@ snapshots:
- tsx
- yaml
- vite@7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0):
+ vite@7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
esbuild: 0.25.6
fdir: 6.4.6(picomatch@4.0.2)
@@ -4691,13 +4718,14 @@ snapshots:
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.30.1
+ tsx: 4.20.3
yaml: 2.8.0
- vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(yaml@2.8.0):
+ vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.7)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(vite@7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
+ '@vitest/mocker': 3.2.4(vite@7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -4715,8 +4743,8 @@ snapshots:
tinyglobby: 0.2.14
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
- vite-node: 3.2.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
+ vite: 7.0.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
+ vite-node: 3.2.4(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
diff --git a/src/app/[category]/[slug]/page.tsx b/src/app/[category]/[slug]/page.tsx
index bdfc478..28c1078 100644
--- a/src/app/[category]/[slug]/page.tsx
+++ b/src/app/[category]/[slug]/page.tsx
@@ -1,14 +1,19 @@
+import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
+import { createPostMetadata } from '@/app/_lib/metadata'
import {
PostContent,
PostFooter,
PostHeader,
PostNavigation,
RelatedPosts,
+ TableOfContents,
} from '@/app/[category]/_components'
import {
type CategoryId,
+ extractHeadingsFromMDX,
+ getCategoryById,
getPostNavigation,
getRelatedPostsByTags,
isValidCategoryId,
@@ -26,6 +31,48 @@ export function generateStaticParams() {
return params
}
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{
+ category: string
+ slug: string
+ }>
+}): Promise {
+ const { category, slug } = await params
+ const decodedSlug = decodeURIComponent(slug)
+
+ // 카테고리 유효성 검증
+ if (!isValidCategoryId(category)) {
+ return {}
+ }
+
+ try {
+ // 카테고리 기반 경로로 MDX 파일 import
+ const { frontmatter } = await import(
+ `@/contents/${category}/${decodedSlug}.mdx`
+ )
+
+ // 포스트의 실제 카테고리와 URL 카테고리가 일치하는지 확인
+ if (frontmatter.category && frontmatter.category !== category) {
+ return {}
+ }
+
+ // 포스트 데이터 찾기
+ const postData = posts.find((post) => post.slug === decodedSlug)
+ if (!postData) {
+ return {}
+ }
+
+ // 카테고리 정보 가져오기
+ const categoryInfo = getCategoryById(category)
+
+ return createPostMetadata(postData, categoryInfo?.name)
+ } catch (_error) {
+ return {}
+ }
+}
+
export default async function PostPage({
params,
}: {
@@ -55,22 +102,31 @@ export default async function PostPage({
notFound()
}
+ // 포스트 콘텐츠에서 헤딩 추출 (실제 포스트 내용 필요)
+ const postData = posts.find((post) => post.slug === decodedSlug)
+ const headings = postData ? extractHeadingsFromMDX(postData.content) : []
+
const { previousPost, nextPost } = getPostNavigation(decodedSlug)
const relatedPosts = getRelatedPostsByTags(decodedSlug)
return (
-