개발자로서 기술 블로그를 운영하고 싶었지만, 기존 플랫폼들은 다음과 같은 한계가 있었습니다:
무엇보다 내가 원하는 대로 글을 쓰고, 내가 원하는 대로 표현하고 싶었습니다. 개발자라면 직접 만들 수 있지 않을까? 라는 생각에 블로그를 만들게 되었습니다.
블로그를 만들기 전에 먼저 아키텍처를 고민했습니다. 콘텐츠와 코드를 분리하여 관리하기로 결정하고 두 개의 저장소를 생성했습니다:
blog-contents (콘텐츠 저장소)
blog-contents/
└── posts/
├── react-hooks.md
├── nextjs-tutorial.md
└── ...
blog (애플리케이션 저장소)
blog/
└── src/
├── app/
├── components/
└── lib/cms/ # GitHub API로 blog-contents 조회
npx create-next-app@latest blog --typescript --tailwind --app
cd blog
콘텐츠 저장소에 Obsidian을 연결하고 작성 환경을 구축했습니다:
blog-contents 디렉토리를 Obsidian vault로 설정content/posts 디렉토리 생성src/
├── app/
│ ├── page.tsx # 메인 페이지
│ ├── posts/ # 블로그 포스트 목록
│ │ ├── page.tsx # 포스트 목록
│ │ ├── layout.tsx # 포스트 레이아웃
│ │ └── [slug]/ # 개별 포스트 상세
│ │ └── page.tsx
│ └── resume/ # 이력서 페이지
│ └── page.tsx
├── components/ # 재사용 가능한 컴포넌트
│ ├── post/
│ │ └── post-item.tsx
│ └── ui/ # shadcn/ui 컴포넌트
└── lib/
├── cms/ # CMS 레이어
│ ├── github.ts # GitHub API 클라이언트
│ ├── parser.ts # Frontmatter 파싱
│ ├── service.ts # getAllPosts, getPostBySlug
│ └── types.ts # Post 타입
└── utils.ts
이 구조는 확장성을 고려하여 설계했으며, CMS 레이어를 독립적으로 분리하여 향후 다른 데이터 소스로 전환하기 쉽도록 했습니다.
개발 블로그를 운영하면서 가장 중요하게 생각한 것은 글쓰기 경험이었습니다. Obsidian은 다음과 같은 장점이 있었습니다:
블로그 콘텐츠와 코드를 분리하여 관리하기로 결정했습니다:
blog-contents (저장소 1)
└── posts/ # Obsidian으로 작성한 마크다운 파일들
blog (저장소 2) # Next.js 블로그 애플리케이션
└── src/
└── lib/cms/ # GitHub API를 통해 blog-contents 조회
워크플로우:
1. Obsidian에서 글 작성
2. blog-contents 저장소에 push
3. blog 저장소에서 GitHub API로 콘텐츠 fetch
4. 블로그 배포
이렇게 분리하면 다음과 같은 이점이 있습니다:
Obsidian에서 글 작성 시 일관된 메타데이터를 사용하기 위해 frontmatter 구조를 정의했습니다:
---
title: useEffect 실행 시점 완벽 정리
description: useEffect가 언제 실행되는지 렌더링 흐름 기준으로 정리합니다.
date: 2025-01-10
category: react
tags:
- react
- hooks
- lifecycle
draft: false
---
주요 필드:
title: 포스트 제목description: 포스트 요약 (SEO 및 목록 페이지에서 사용)date: 작성 날짜category: 카테고리 (단일 선택)tags: 태그 (다중 선택)draft: 초안 여부 (true일 경우 배포에서 제외)매번 frontmatter를 직접 작성하는 것은 번거로우므로 Obsidian의 템플릿 기능을 활용했습니다. 새 파일을 생성할 때 자동으로 frontmatter가 채워지도록 설정하여 글쓰기 경험을 개선했습니다.
lib/cms/
├── github.ts # GitHub API 클라이언트
├── parser.ts # 마크다운 frontmatter 파싱 로직
├── service.ts # 비즈니스 로직 (getAllPosts, getPostBySlug)
└── types.ts # Post 타입 정의
이렇게 계층을 분리하여 각 레이어의 책임을 명확히 했습니다:
getAllPosts), 상세 조회(getPostBySlug) 등의 비즈니스 로직1. Post 타입 정의
TypeScript를 활용하여 포스트 데이터의 타입을 명확히 정의했습니다:
interface Post {
slug: string
title: string
description: string
date: string
category: string
tags: string[]
draft: boolean
content: string
}
2. getAllPosts 함수
GitHub API를 통해 blog-contents 저장소의 모든 포스트를 가져옵니다:
draft: true인 포스트는 제외3. getPostBySlug 함수
특정 포스트의 상세 내용을 가져옵니다:
4. Draft 글 처리
draft: false인 포스트만 목록에 표시하여, 작성 중인 글은 배포되지 않도록 했습니다. 이를 통해 안심하고 작업 중인 글을 저장소에 푸시할 수 있습니다.