Next.js로 블로그 만들기 #1 시작과 설계

19 분 소요

모던 리액트 + Next.js 시리즈에서 배운 Server Components / Server Actions를 실전 프로젝트로 손에 익혀봅니다. 이번 시리즈는 개인 블로그를 처음부터 만듭니다 — 흥미로운 점 하나는, 지금 이 글을 읽고 계신 사이트(schoolofweb.net) 자체가 거의 같은 구조라는 거예요. 도그푸딩 학습이 가능한 좋은 주제입니다.

5편으로 나눠 점진적으로 쌓아갑니다.

  • #1 시작과 설계 ← 이번 글
  • #2 글 목록과 상세 페이지
  • #3 태그와 검색
  • #4 댓글 (Server Actions)
  • #5 SEO와 배포 (마무리)

요구사항 정의

먼저 우리가 만들 블로그가 뭘 할 수 있어야 하는지 적어둡니다.

핵심 기능

  • 글 목록 페이지 (최신순)
  • 글 상세 페이지 (마크다운 본문 렌더)
  • 태그별 글 모음 페이지
  • 검색 기능 (제목/본문)
  • 글마다 댓글 달기

기술 결정 (앞에서 정해두면 흔들림 없음)

  • 글 본문은 MDX 파일로 작성 (DB 없음, 파일 시스템에 저장)
  • 댓글은 메모리 저장소 사용 (실제 서비스라면 DB가 필요하지만 학습 단순화 위해)
  • 데이터 페칭은 Server Components가 직접 fs로 읽기
  • mutation은 Server Actions 사용
  • 배포는 Vercel

이 결정들이 시리즈 내내 중심을 잡아줍니다. "이건 어디서 다루나?", "이건 어떻게 풀어야 하나?"에 헤매지 않게 해주죠.

왜 MDX 파일 기반인가?

블로그를 만들 때 글 데이터를 어디에 둘지 큰 선택지 셋:

  1. DB (PostgreSQL, SQLite, Supabase 등) — 다인 운영 / 어드민 페이지 / 동적 글 추가에 강점
  2. MDX 파일 — Git 워크플로우, 단순함, JSX 컴포넌트 임베딩
  3. 외부 CMS (Contentful, Sanity 등) — 비기술자 편집자 협업에 강점

이 시리즈는 MDX 파일 방식을 택합니다. 이유:

  • Git이 곧 백업 — 글 히스토리가 그대로 보존됨
  • 로컬에서 편집 — 좋아하는 에디터로 글 쓰기
  • DB 셋업 안 함 — 학습 부담 ↓
  • Server Components의 강점이 자연스럽게 살아남fs.readFileSync로 파일을 직접 읽는 코드가 그대로 동작
  • React 컴포넌트 임베딩 — 글 안에 <YouTube />, <Tip> 같은 커스텀 컴포넌트 사용 가능

이 사이트도 같은 방식이고, 많은 개인 기술 블로그가 이렇게 운영됩니다.

폴더 구조 설계

코딩 시작 전 폴더 구조를 그려보면 막히는 일이 줄어듭니다.

프로젝트 구조
my-blog/
├── posts/                       ← MDX 글들이 사는 곳
│   ├── hello-world.mdx
│   ├── about-rsc.mdx
│   └── learning-react.mdx
├── src/
│   └── app/
│       ├── layout.js            ← 사이트 공통 (헤더/푸터)
│       ├── page.js              ← '/' 글 목록 (최신순)
│       ├── posts/
│       │   └── [slug]/
│       │       └── page.js      ← '/posts/[slug]' 글 상세
│       ├── tags/
│       │   ├── page.js          ← '/tags' 태그 목록
│       │   └── [tag]/
│       │       └── page.js      ← '/tags/[tag]' 태그별 글
│       ├── search/
│       │   └── page.js          ← '/search?q=...' 검색
│       └── lib/
│           └── posts.js         ← MDX 읽기/파싱 유틸
├── public/
└── package.json

각 라우트의 역할:

라우트화면
/최신 글 목록
/posts/[slug]글 상세 (본문 + 댓글)
/tags모든 태그 목록
/tags/[tag]특정 태그가 붙은 글들
/search?q=...검색 결과

src/app/lib/posts.js 같은 유틸 파일에 "MDX 파일 읽기" 같은 공통 로직을 모아둘 거예요. 페이지 파일이 너무 비대해지지 않도록 분리하는 흔한 패턴입니다.

글 데이터 모양 정하기

각 MDX 파일이 어떤 정보를 가질지 정해두면 코드가 깔끔해집니다.

posts/hello-world.mdx 예시
---
title: "안녕, 블로그"
date: 2026-05-01
description: "첫 글입니다."
tags: ["일상", "공지"]
draft: false
---
 
## 첫 단락
 
이것은 **마크다운**으로 쓴 본문입니다.
 
리스트도 가능:
- 항목 1
- 항목 2
- 항목 3

위쪽의 ---로 둘러싸인 부분이 frontmatter(메타데이터)이고, 그 아래가 본문입니다. frontmatter는 YAML 형식이며, 우리 코드에서 자바스크립트 객체로 파싱해 사용할 거예요.

각 필드의 의미:

필드타입설명
titlestring글 제목
datestring (YYYY-MM-DD)발행일
descriptionstring한 줄 요약 (목록과 메타에 사용)
tagsstring[]태그 배열
draftbooleantrue면 목록에 안 보임 (작성 중)

이 외에도 필요하면 image, keywords 등을 추가할 수 있는데 일단 최소한으로 시작합시다.

Slug

/posts/[slug]slug는 글의 URL 식별자입니다. 우리는 파일명을 슬러그로 사용할 거예요.

  • posts/hello-world.mdx/posts/hello-world
  • posts/about-rsc.mdx/posts/about-rsc

규칙이 단순해서 좋습니다. 글마다 별도 ID 부여나 URL 매핑 작업이 필요 없어요.

프로젝트 시작

이제 진짜 코드로 들어갑니다. 새 Next.js 프로젝트를 만듭니다.

프로젝트 생성
npx create-next-app@latest my-blog
cd my-blog

지난 시리즈에서 했던 것과 같은 옵션 선택 (App Router, JavaScript, src/ directory 권장).

필요한 의존성

MDX 파일을 파싱하고 컴파일할 라이브러리를 설치합니다.

MDX 의존성
npm install gray-matter next-mdx-remote

각 패키지의 역할:

  • gray-matter.mdx 파일에서 frontmatter와 본문을 분리
  • next-mdx-remote — 본문 마크다운을 React 컴포넌트로 컴파일

선택적으로 추가할 만한 패키지:

  • remark-gfm — GitHub Flavored Markdown(테이블, 체크박스 등) 지원
  • rehype-pretty-code — 코드 블록 신택스 하이라이팅

이번 글에서는 일단 핵심 두 개만 설치하고, #2에서 본문 컴파일하면서 추가 플러그인들을 도입하겠습니다.

첫 번째 글 만들기

my-blog/posts/ 폴더를 만들고 (Next.js 외부에 둡니다 — 라우트가 아닌 데이터니까요) 첫 글을 추가합니다.

posts/hello-world.mdx:

posts/hello-world.mdx
---
title: "안녕, 블로그"
date: 2026-05-01
description: "Next.js로 만든 첫 블로그 글입니다."
tags: ["공지", "리액트"]
draft: false
---
 
# 안녕하세요!
 
이 글은 **MDX**로 작성되었습니다. Next.js의 Server Component가 이 파일을 직접 읽어서 화면에 그립니다.
 
## 굵은 글씨, 기울임, 코드
 
마크다운 기본 문법 대부분이 동작합니다.
 
- 리스트 항목 1
- 리스트 항목 2
- 리스트 항목 3
 
`인라인 코드`도 가능합니다.
 
```js
// 코드 블록도
function hello() {
  console.log("hello");
}
\```

(주의: 위 본문의 마지막 코드 블록 끝부분 \``` 는 실제 파일에서는 `````` 로 적습니다 — 마크다운 인라인 표시상 이스케이프된 것)

posts/learning-react.mdx:

posts/learning-react.mdx
---
title: "리액트 학습 노트"
date: 2026-05-10
description: "리액트를 배우면서 정리한 핵심 포인트들."
tags: ["리액트", "학습"]
draft: false
---
 
리액트 공부 시작!
 
## Server Components
 
기본은 Server Component, 필요한 것만 Client Component.

posts/draft-not-shown.mdx:

posts/draft-not-shown.mdx
---
title: "아직 작성 중"
date: 2026-05-15
description: "초안입니다."
tags: ["메모"]
draft: true
---
 
이 글은 draft가 true라 목록에 안 나타나야 합니다.

세 글 모두 비슷한 구조죠. draft: true인 글은 #2에서 만들 목록에서 자동 제외됩니다.

MDX 파싱 유틸 — 첫 단계

src/app/lib/posts.js에 MDX 파일을 읽고 파싱하는 함수들의 첫 윤곽을 만들어둡니다 (#2에서 본격 사용).

src/app/lib/posts.js
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
 
const POSTS_DIR = path.join(process.cwd(), 'posts');
 
export function getAllSlugs() {
  return fs.readdirSync(POSTS_DIR)
    .filter(file => file.endsWith('.mdx'))
    .map(file => file.replace(/\.mdx$/, ''));
}
 
export function getPostBySlug(slug) {
  const fullPath = path.join(POSTS_DIR, `${slug}.mdx`);
  if (!fs.existsSync(fullPath)) return null;
 
  const fileContent = fs.readFileSync(fullPath, 'utf-8');
  const { data, content } = matter(fileContent);
 
  return {
    slug,
    frontmatter: data,
    content,
  };
}
 
export function getAllPosts() {
  const slugs = getAllSlugs();
  const posts = slugs
    .map(slug => getPostBySlug(slug))
    .filter(post => post && !post.frontmatter.draft);
 
  return posts.sort((a, b) => a.frontmatter.date < b.frontmatter.date ? 1 : -1);
}

각 함수의 역할:

  • getAllSlugs()posts/ 폴더의 .mdx 파일 이름들을 슬러그 배열로 반환
  • getPostBySlug(slug) — 슬러그에 해당하는 파일을 읽고 frontmatter와 본문 분리
  • getAllPosts() — 모든 글을 가져오되 draft는 제외, 발행일 내림차순 정렬

이 함수들은 Server Component에서만 호출 가능합니다 (fs를 쓰니까요). Client Component에서 이걸 쓰려고 하면 빌드 에러가 납니다 — 의도한 대로의 안전장치예요.

이 사이트의 app/lib/posts-util.ts도 거의 같은 구조로 동작합니다. 다만 우리는 학습 목적이라 단순화해서 시작하고, 필요할 때 점진적으로 확장합니다.

동작 확인

지금 상태에서는 페이지가 아직 없으니 dev 서버를 띄워도 의미 있는 화면이 나오지 않습니다. 그래도 한 번 잘 돌아가는지는 확인해두면 좋아요.

dev 서버
npm run dev

http://localhost:3000에서 Next.js 기본 화면이 보이면 OK. 다음 글부터 진짜 페이지를 만들어갑니다.

마무리

이번 글에서는 블로그 빌드 시리즈의 토대를 다졌습니다.

  • 요구사항을 명확히 적었다 (목록 / 상세 / 태그 / 검색 / 댓글)
  • 데이터 저장 방식을 결정했다 (MDX 파일)
  • 폴더 구조와 라우팅 그림을 그렸다
  • 글 frontmatter 모양을 정했다
  • 첫 MDX 글들을 만들었다
  • posts.js 유틸 함수의 첫 윤곽을 잡았다

본격적인 화면 작업은 다음 글부터입니다. "Next.js로 블로그 만들기 #2 글 목록과 상세 페이지"에서는 위에서 만든 getAllPosts를 활용해 홈 화면에 글 목록을 그리고, /posts/[slug] 동적 라우트에서 MDX 본문을 컴파일해 표시하는 곳까지 갑니다.