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 ( -
+ <> - - - - - - -
+
+
+ + + + + + +
+
+ + ) } catch (_error) { console.error(_error) diff --git a/src/app/[category]/_components/PostContent.tsx b/src/app/[category]/_components/PostContent.tsx index 10db526..da57b08 100644 --- a/src/app/[category]/_components/PostContent.tsx +++ b/src/app/[category]/_components/PostContent.tsx @@ -1,38 +1,20 @@ import { cn } from '@/app/_lib/cn' -import type { Heading } from '@/domain/blog' interface PostContentProps { children: React.ReactNode - showTOC?: boolean - headings?: Heading[] - className?: string } -export function PostContent({ - children, - showTOC = false, - headings, - className, -}: PostContentProps) { +export function PostContent({ children }: PostContentProps) { return ( -
- {showTOC && headings && ( - +
- {children} -
-
+ > + {children} + ) } diff --git a/src/app/[category]/_components/PostHeader.tsx b/src/app/[category]/_components/PostHeader.tsx index f97b353..ad6d6be 100644 --- a/src/app/[category]/_components/PostHeader.tsx +++ b/src/app/[category]/_components/PostHeader.tsx @@ -1,4 +1,3 @@ -import { cn } from '@/app/_lib/cn' import { formatDate } from '@/app/_lib/formatDate' import type { CategoryId } from '@/domain/blog' @@ -12,7 +11,6 @@ interface PostHeaderProps { category?: CategoryId readingTime?: number author?: string - className?: string } export function PostHeader({ @@ -22,15 +20,12 @@ export function PostHeader({ category, readingTime, author, - className, }: PostHeaderProps) { return ( -
+
{category && } -

- {title} -

+

{title}