본문으로 바로가기
mark-lab

Notion → GitHub 자동 배포 파이프라인 구축기

1. 배경

블로그에 글을 올리려면 매번 마크다운을 직접 작성하고 커밋해야 했습니다. 노션에서 이미 글을 쓰고 있었기 때문에 이 작업이 중복이었습니다. 노션의 StatusPublished로 바꾸는 것만으로 블로그에 자동 배포되는 파이프라인이 필요했습니다.

2. 전체 흐름

3. Notion 데이터베이스 구조

속성타입설명
titletitle포스트 제목
statusstatusdraft / published / synced
postmulti_select카테고리 (폴더명으로 사용)
날짜date발행일
descriptionrich_text포스트 설명 (SEO, 목록에 표시)

statuspublished로 변경하는 순간 파이프라인이 시작됩니다.

4. 핵심 구현

4.1 sync_notion.py

notion-client 라이브러리 대신 requests로 Notion REST API를 직접 호출합니다. 라이브러리 버전 이슈를 피하기 위한 선택입니다.

def fetch_published_pages() -> list[dict]:
    body = {
        'filter': {
            'property': 'status',
            'status': {'equals': 'published'},
        },
    }
    data = notion_post(f'databases/{DATABASE_ID}/query', body)
    return data.get('results', [])

블록을 마크다운으로 변환할 때 Python 3.10의 match 문으로 블록 타입별로 처리합니다.

match bt:
    case 'heading_1': return f'\n# {rich_to_md(rich)}'
    case 'heading_2': return f'\n## {rich_to_md(rich)}'
    case 'code':
        lang = data.get('language', '')
        return f'\n```{lang}\n{get_plain_text(rich)}\n```'
    case 'image':
        local_path = download_image(url, image_dir)
        rel = os.path.relpath(local_path, PUBLIC_DIR)
        return f'\n![{caption}](/{rel})'

4.2 이미지 로컬화

노션 이미지 URL은 만료 기간이 있습니다. 동기화 시점에 이미지를 직접 다운로드하여 public/{category}/assets/{page_id}/ 에 저장합니다.

public/
  book/
    assets/
      b648951665fa4c22b6890a96c771390d/   ← page_id
        a48e201ce382.png

마크다운에서는 /book/assets/.../image.png 경로로 참조합니다. Next.js는 public/ 폴더를 정적 파일로 서빙하므로 별도 설정 없이 동작합니다.

4.3 Notion 상태 업데이트 시점

중요한 설계 결정: git push 성공 후에만 노션 상태를 synced로 업데이트합니다.

- name: Commit and push changes
  id: push
  run: |
    git add posts/ public/
    git commit -m "chore: sync notion posts"
    git push
    echo "pushed=true" >> $GITHUB_OUTPUT
 
- name: Update Notion status to synced
  if: steps.push.outputs.pushed == 'true'
  run: python scripts/sync_notion.py

push가 실패하면 노션 상태가 변경되지 않아 다음 실행에서 재처리됩니다.

4.4 CD 연동

sync-notion.yml이 완료되면 cd.yaml이 자동으로 트리거됩니다.

on:
  workflow_run:
    workflows: ['Sync Notion Posts']
    types: [completed]

sync가 성공했을 때만 CD가 실행되도록 조건을 추가했습니다.

if: ${{ github.event_name == 'workflow_dispatch' ||
        github.event.workflow_run.conclusion == 'success' }}

5. 생성되는 마크다운 형식

---
title: '[HTTP] 1. 인터넷과 네트워크'
published: true
date: 2026.03.06
description: 'IP 프로토콜의 한계와 TCP, UDP의 차이...'
---
 
## 1. 인터넷 통신
...

title은 YAML 특수문자([, : 등)를 포함할 수 있으므로 항상 따옴표로 감쌉니다.

6. Dry Run 모드

배포 없이 변환 결과만 확인하고 싶을 때는 GitHub Actions에서 수동 실행 시 Dry run 옵션을 활성화합니다.

  • 마크다운 변환 및 이미지 다운로드는 실행
  • git push 없음
  • 노션 상태 변경 없음

7. 마치며

이제 노션에서 글을 완성하고 statuspublished로 바꾸면 30분 이내에 블로그에 반영됩니다. 이미지 만료 문제도 해결됐고, 마크다운을 직접 관리할 필요도 없어졌습니다.