12min readGuide

마크다운 완전 가이드 — 블로그 스타일링의 모든 것


마크다운(Markdown)은 읽기 쉽고 쓰기 쉬운 경량 마크업 언어다. 2004년 John Gruber와 Aaron Swartz가 설계했으며, 오늘날 GitHub, Notion, 대부분의 블로그 플랫폼에서 표준처럼 쓰인다. 이 글은 마크다운 문법을 처음 접하는 사람부터 레퍼런스가 필요한 숙련자까지 모두를 위해 작성했다. 한 페이지에서 거의 모든 문법을 직접 확인하고 복사해 쓸 수 있도록 구성했다.


1. 제목 계층 (Headings)

제목은 # 기호의 개수로 레벨을 결정한다. HTML의 <h1> ~ <h6>에 대응한다. 블로그 글에서는 보통 ##(h2)부터 시작하고 ####(h4)까지 사용하는 것을 권장한다. #(h1)은 페이지 제목과 충돌할 수 있으므로 본문에서는 피하는 편이 좋다.

1-1. h3 소제목

h3는 h2 아래에서 세부 항목을 나눌 때 사용한다.

1-1-1. h4 세부 항목

h4는 더 세밀한 분류가 필요할 때 사용한다. 너무 깊은 계층은 가독성을 해치므로 h4 이하는 꼭 필요한 경우에만 쓰는 것이 좋다.


2. 텍스트 서식 (Text Formatting)

일반 문단 텍스트에 강조와 의미를 더하는 인라인 서식들이다.

  • 굵게(Bold): 양쪽에 ** 또는 __를 붙인다. 핵심 키워드나 경고 문구에 사용한다.
  • 기울임(Italic): 양쪽에 * 또는 _를 붙인다. 책 제목, 외래어, 강조에 사용한다.
  • 취소선(Strikethrough): 양쪽에 ~~를 붙인다. 오류 수정이나 더 이상 유효하지 않은 정보를 나타낼 때 쓴다.
  • 인라인 코드(Inline Code): 백틱(`)으로 감싼다. 변수명, 함수명, 명령어를 본문 안에서 표기할 때 쓴다.
  • 굵게+기울임: **_텍스트_**처럼 조합할 수도 있다.

실제 문장에서 혼합해 보면 이런 식이다. useState는 React의 핵심 훅 중 하나로, *상태(state)*를 함수형 컴포넌트에서 관리할 수 있게 해준다. 클래스 컴포넌트의 this.state는 이제 레거시다.


3. 리스트 (Lists)

3-1. 순서 없는 리스트

-, *, + 중 하나로 시작하면 된다. 보통 -를 통일해서 쓰는 것이 일관성 있다.

  • 프론트엔드 프레임워크
    • React
      • Next.js
      • Remix
    • Vue
      • Nuxt.js
    • Svelte
  • 백엔드 언어
    • TypeScript (Node.js / Bun)
    • Go
    • Rust
  • 데이터베이스
    • PostgreSQL
    • SQLite
    • Redis

3-2. 순서 있는 리스트

숫자와 점(.)으로 시작한다. 실제 숫자가 달라도 렌더링 시 자동으로 순서가 맞춰진다.

  1. 요구사항 분석
  2. 와이어프레임 설계
  3. 컴포넌트 구조 정의
  4. 개발 환경 설정
  5. 구현
    1. UI 레이아웃
    2. API 연동
    3. 상태 관리
  6. 테스트
  7. 배포

3-3. 체크리스트

  • 마크다운 문법 정리
  • 코드블록 예제 추가
  • 테이블 작성
  • 이미지 최적화
  • 다크모드 대응 확인
  • SEO 메타데이터 점검

4. 코드블록 (Code Blocks)

코드블록은 세 개의 백틱(```)으로 시작하고 끝낸다. 언어 이름을 명시하면 문법 강조(syntax highlighting)가 적용된다.

4-1. JavaScript

// 비동기 데이터 페칭 예시
async function fetchPosts(page = 1, limit = 10) {
  const url = new URL('/api/posts', window.location.origin);
  url.searchParams.set('page', page);
  url.searchParams.set('limit', limit);
 
  const response = await fetch(url.toString());
 
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
 
  const data = await response.json();
  return data;
}
 
// 호출 예시
fetchPosts(1, 5)
  .then(posts => console.log(posts))
  .catch(err => console.error('Failed to fetch:', err));
javascript

4-2. TypeScript

interface Post {
  id: string;
  title: string;
  slug: string;
  date: string;
  tags: string[];
  draft: boolean;
}
 
type PostSummary = Pick<Post, 'id' | 'title' | 'slug' | 'date'>;
 
function sortPostsByDate(posts: Post[]): Post[] {
  return [...posts].sort(
    (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
  );
}
 
function filterPublished(posts: Post[]): PostSummary[] {
  return sortPostsByDate(posts)
    .filter(post => !post.draft)
    .map(({ id, title, slug, date }) => ({ id, title, slug, date }));
}
typescript

4-3. CSS

/* 마크다운 렌더링 스타일 */
.prose {
  max-width: 65ch;
  margin: 0 auto;
  line-height: 1.75;
  color: var(--color-text);
}
 
.prose h2 {
  font-size: 1.5rem;
  font-weight: 700;
  margin-top: 2rem;
  margin-bottom: 1rem;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: 0.5rem;
}
 
.prose code {
  background-color: var(--color-code-bg);
  padding: 0.2em 0.4em;
  border-radius: 4px;
  font-size: 0.875em;
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
}
 
.prose pre code {
  background-color: transparent;
  padding: 0;
}
css

4-4. Bash / Shell

# 프로젝트 초기화
mkdir my-blog && cd my-blog
bun create next-app . --typescript --tailwind --app
 
# 의존성 설치
bun add @mdx-js/react gray-matter reading-time
 
# 개발 서버 실행
bun dev
 
# 빌드 및 배포
bun run build
bun run start
bash

4-5. JSON

{
  "name": "hubblelog",
  "version": "0.1.0",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "^15.0.0",
    "react": "^19.0.0",
    "gray-matter": "^4.0.3"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "@types/node": "^20.0.0",
    "@types/react": "^19.0.0"
  }
}
json

5. 인용구 (Blockquotes)

> 기호로 인용구를 만든다. 여러 줄에 걸쳐 사용할 수 있고, 중첩도 가능하다.

5-1. 일반 인용구

마크다운의 목표는 최대한 읽기 쉽고 쓰기 쉽게 만드는 것이다. 마크다운으로 서식이 지정된 문서는 태그나 서식 지정 명령으로 표시되지 않고 일반 텍스트 그대로 출판될 수 있어야 한다.

— John Gruber, Markdown 소개 문서 (2004)

5-2. 중첩 인용구

좋은 코드에 대한 논쟁에서 자주 인용되는 말이 있다.

코드는 작성하는 것보다 읽히는 횟수가 훨씬 많다. — Guido van Rossum

이 원칙은 마크다운 문서를 작성할 때도 동일하게 적용된다. 렌더링된 결과뿐 아니라 소스 텍스트 자체가 읽기 쉬어야 좋은 문서다.

5-3. 주의/팁 스타일 인용구

주의: draft: true로 설정된 포스트는 빌드 시 제외된다. 공개하려면 반드시 draft: false로 변경하거나 필드를 삭제해야 한다.

: VSCode에서 마크다운을 작성할 때 Ctrl+Shift+V(Mac: Cmd+Shift+V)로 미리보기를 열 수 있다.


6-1. 인라인 링크

기본 링크 문법은 [텍스트](URL) 형식이다.

6-2. 본문 내 인라인 링크

마크다운은 John Gruber가 만들었고, GitHub에서 대중화됐다. 오늘날 MDX를 사용하면 마크다운 안에 JSX 컴포넌트를 삽입할 수도 있다.

6-3. 링크에 title 속성 추가

마우스를 올리면 툴팁이 나타나는 링크를 만들 수 있다.

Next.js 공식 문서


7. 이미지 (Images)

이미지 문법은 링크와 비슷하지만 앞에 !가 붙는다: ![대체텍스트](URL).

7-1. 기본 이미지

마크다운 로고 플레이스홀더

7-2. 이미지에 title 속성 추가

코드 에디터 스크린샷 플레이스홀더

7-3. 이미지에 링크 걸기

클릭 가능한 배너


8. 테이블 (Tables)

파이프(|)와 하이픈(-)으로 테이블을 만든다. 헤더 행 아래에 구분선을 넣고, :의 위치로 정렬 방향을 지정한다.

8-1. 마크다운 지원 현황 비교

문법CommonMarkGitHub GFMMDX
제목(Headings)
굵게/기울임
취소선
테이블
체크리스트
자동 링크
JSX 컴포넌트
수식(LaTeX)플러그인 필요

8-2. 인기 블로그 프레임워크 비교

프레임워크언어마크다운 지원빌드 속도러닝커브
Next.jsReact / TSMDX빠름중간
AstroAny / TSMDX, MD매우 빠름낮음
HugoGo 템플릿MD매우 빠름높음
JekyllRuby / LiquidMD보통낮음
GatsbyReact / TSMDX느림높음

9. 구분선 (Horizontal Rule)

세 개 이상의 -, *, _으로 구분선을 만든다.


위는 ---으로 만든 구분선이다.


위는 ***으로 만든 구분선이다. 시각적으로 동일하지만 소스 가독성을 위해 ---를 권장한다.


10. 마무리 — 마크다운을 잘 쓰는 법

마크다운은 문법보다 사용 철학이 더 중요하다. 가장 중요한 원칙 몇 가지를 정리하면 이렇다.

  1. 구조가 먼저다: 헤딩 계층을 논리적으로 잡아야 TOC와 스크린 리더 모두 제대로 동작한다.
  2. 덜 쓸수록 좋다: 서식 과잉은 오히려 핵심을 흐린다. Bold는 진짜 중요한 것에만 쓰자.
  3. 소스도 읽힌다: 렌더링된 화면뿐 아니라 .md 파일 자체도 사람이 읽는다. 빈 줄과 들여쓰기를 일관되게 유지하자.
  4. 링크는 의미 있게: 여기를 클릭 대신 Next.js 공식 문서처럼 목적지를 알 수 있는 텍스트를 써야 접근성이 높아진다.
  5. 코드는 항상 언어를 명시: ```js 처럼 언어를 명시해야 신택스 하이라이팅이 적용되고 가독성이 좋아진다.

좋은 글은 좋은 구조에서 시작한다. 마크다운은 그 구조를 강요하지 않고, 자연스럽게 유도한다.

이 가이드가 마크다운을 처음 배우는 사람에게는 입문서로, 이미 쓰고 있는 사람에게는 레퍼런스로 도움이 되길 바란다.