블로그 만들기(Next + Obsidian)(1)

지리·2026년 2월 1일

들어가며

개발자로서 기술 블로그를 운영하고 싶었지만, 기존 플랫폼들은 다음과 같은 한계가 있었습니다:

  • Tistory, Velog: 디자인과 기능 커스터마이징의 제약
  • Medium: 유료화 정책과 플랫폼 종속성
  • Notion: 퍼블리싱 속도와 SEO 최적화의 한계

무엇보다 내가 원하는 대로 글을 쓰고, 내가 원하는 대로 표현하고 싶었습니다. 개발자라면 직접 만들 수 있지 않을까? 라는 생각에 블로그를 만들게 되었습니다.

기술 스택

  • 프레임워크: Next.js 14 (App Router, Server Components)
  • 언어: TypeScript
  • 스타일링: Tailwind CSS
  • UI 컴포넌트: shadcn/ui

프로젝트 시작

1. 저장소 구조 설계

블로그를 만들기 전에 먼저 아키텍처를 고민했습니다. 콘텐츠와 코드를 분리하여 관리하기로 결정하고 두 개의 저장소를 생성했습니다:

blog-contents (콘텐츠 저장소)

blog-contents/
└── posts/
    ├── react-hooks.md
    ├── nextjs-tutorial.md
    └── ...

blog (애플리케이션 저장소)

blog/
└── src/
    ├── app/
    ├── components/
    └── lib/cms/        # GitHub API로 blog-contents 조회

2. Next.js 프로젝트 생성

npx create-next-app@latest blog --typescript --tailwind --app
cd blog

3. Obsidian 설정 (blog-contents)

콘텐츠 저장소에 Obsidian을 연결하고 작성 환경을 구축했습니다:

  1. Vault 생성: blog-contents 디렉토리를 Obsidian vault로 설정
  2. 디렉토리 구조: content/posts 디렉토리 생성
  3. Template 설정: 새 포스트 작성 시 자동으로 frontmatter가 채워지도록 템플릿 구성
  4. Git 플러그인: Obsidian Git 플러그인으로 자동 커밋/푸시 설정

4. 블로그 애플리케이션 폴더 구조

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 레이어를 독립적으로 분리하여 향후 다른 데이터 소스로 전환하기 쉽도록 했습니다.

CMS 시스템 구축

Obsidian을 CMS로 선택한 이유

개발 블로그를 운영하면서 가장 중요하게 생각한 것은 글쓰기 경험이었습니다. Obsidian은 다음과 같은 장점이 있었습니다:

  • ✅ 마크다운 기반의 편리한 에디터
  • ✅ 플러그인 시스템으로 확장 가능
  • ✅ Git 연동으로 버전 관리 가능
  • ✅ 로컬 파일 시스템 기반이라 빠른 성능

저장소 분리 전략

블로그 콘텐츠와 코드를 분리하여 관리하기로 결정했습니다:

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. 블로그 배포

이렇게 분리하면 다음과 같은 이점이 있습니다:

  • 콘텐츠와 코드의 관심사 분리
  • 콘텐츠만 독립적으로 버전 관리 가능
  • 다른 프로젝트에서도 같은 콘텐츠 재사용 가능

Frontmatter 구조 설계

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일 경우 배포에서 제외)

Obsidian Template 설정

매번 frontmatter를 직접 작성하는 것은 번거로우므로 Obsidian의 템플릿 기능을 활용했습니다. 새 파일을 생성할 때 자동으로 frontmatter가 채워지도록 설정하여 글쓰기 경험을 개선했습니다.

CMS 아키텍처

lib/cms/
├── github.ts    # GitHub API 클라이언트
├── parser.ts    # 마크다운 frontmatter 파싱 로직
├── service.ts   # 비즈니스 로직 (getAllPosts, getPostBySlug)
└── types.ts     # Post 타입 정의

이렇게 계층을 분리하여 각 레이어의 책임을 명확히 했습니다:

  • github.ts: GitHub API와의 직접적인 통신 담당
  • parser.ts: 마크다운 frontmatter 파싱 및 메타데이터 추출
  • service.ts: 포스트 목록 조회(getAllPosts), 상세 조회(getPostBySlug) 등의 비즈니스 로직
  • types.ts: Post 인터페이스 등 타입 안정성을 위한 정의

주요 기능 구현

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인 포스트는 제외
  • 날짜 기준 내림차순 정렬
  • frontmatter 파싱 및 메타데이터 추출

3. getPostBySlug 함수

특정 포스트의 상세 내용을 가져옵니다:

  • slug를 기반으로 파일 조회
  • 마크다운 본문 파싱
  • 존재하지 않는 경우 404 처리

4. Draft 글 처리

draft: false인 포스트만 목록에 표시하여, 작성 중인 글은 배포되지 않도록 했습니다. 이를 통해 안심하고 작업 중인 글을 저장소에 푸시할 수 있습니다.

profile
공부한것들, 경험한 것들을 기록하려 노력합니다✨

0개의 댓글