From f48f5fd8148dc88cce1a8d3a1d0a02806003c89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B0=EC=83=81=EB=B9=88?= Date: Sun, 16 Aug 2026 20:29:21 +0900 Subject: [PATCH] =?UTF-8?q?refactor(posts):=20PostSummary/Post=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EA=B3=84=EC=B8=B5=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20Zod=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=EC=9B=90=EC=B2=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PostFrontmatter(Zod Schema)에서 파생된 PostSummary 모델 정의로 단일 원천(Single Source of Truth) 확립 - Post 타입을 PostSummary & { readonly content: string } 구조로 계층화 - PostCard, PostMeta 등 뷰 컴포넌트의 Props 타입을 PostSummary로 일반화하여 결합도 완화 --- src/components/post-card.tsx | 4 ++-- src/components/post-meta.tsx | 4 ++-- src/lib/posts.ts | 44 +++++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/components/post-card.tsx b/src/components/post-card.tsx index 05d2480..b8bca03 100644 --- a/src/components/post-card.tsx +++ b/src/components/post-card.tsx @@ -1,10 +1,10 @@ import Link from "next/link" -import type { Post } from "@/lib/posts" +import type { PostSummary } from "@/lib/posts" import { PostMeta } from "./post-meta" import { PostTags } from "./post-tags" type PostCardProps = Readonly<{ - post: Post + post: PostSummary /** 카드가 놓인 자리의 바로 위 제목 다음 단계여야 문서 개요에 구멍이 생기지 않는다. */ headingLevel?: 2 | 3 }> diff --git a/src/components/post-meta.tsx b/src/components/post-meta.tsx index f07a181..6318942 100644 --- a/src/components/post-meta.tsx +++ b/src/components/post-meta.tsx @@ -1,8 +1,8 @@ import { siteConfig } from "@/config/site" -import type { Post } from "@/lib/posts" +import type { PostSummary } from "@/lib/posts" type PostMetaProps = Readonly<{ - post: Pick + post: Pick }> // 날짜는 시각이 아니라 달력상의 하루라, 표준시를 UTC 로 고정해 하루가 밀리지 않게 한다. diff --git a/src/lib/posts.ts b/src/lib/posts.ts index 5acc5bf..2d7aaa3 100644 --- a/src/lib/posts.ts +++ b/src/lib/posts.ts @@ -3,7 +3,12 @@ import { basename, join } from "node:path" import { VFile } from "vfile" import { matter } from "vfile-matter" import { siteConfig } from "@/config/site" -import { extractPostExcerpt, PostContentError, parsePostFrontmatter } from "@/lib/markdown" +import { + extractPostExcerpt, + PostContentError, + type PostFrontmatter, + parsePostFrontmatter, +} from "@/lib/markdown" import type { AdjacentPosts, PaginatedPosts, TaxonomyItem } from "@/lib/post-collections" import { findAdjacentPosts, @@ -42,21 +47,28 @@ export class EmptyPostCollectionError extends Error { } } -export type Post = { - readonly slug: string - readonly title: string - readonly description: string - readonly date: string - readonly tags: readonly string[] - readonly category: string - readonly draft: boolean - readonly ogImage?: string - readonly pinned: boolean - readonly toc: boolean - readonly readingMinutes: number - readonly excerpt: string - readonly content: string -} +/** + * 게시글 메타데이터 요약 모델 (Single Source of Truth: PostFrontmatter 파생) + * 목록, 태그, 카테고리, 사이트맵 렌더링에 필요한 속성들을 본문(content) 없이 포함합니다. + */ +export type PostSummary = Readonly< + Omit & { + readonly slug: string + readonly description: string + readonly tags: readonly string[] + readonly readingMinutes: number + readonly excerpt: string + } +> + +/** + * 게시글 전체 상세 모델 + * PostSummary 모델을 확장하여 마크다운 본문(content)을 포함합니다. + */ +export type Post = PostSummary & + Readonly<{ + readonly content: string + }> export function readPostsFromDirectory(directory: string = POSTS_DIRECTORY): readonly Post[] { mkdirSync(directory, { recursive: true })