GitHub Actions로 Hashnode 블로그 자동 포스팅 구축하기 (feat. 첫 자동 발행 성공!)
안녕하세요, Hashnode! 👋
이 글은 저의 GitHub TIL(Today I Learned) 레포지토리에 마크다운(.md) 파일을 푸시하면, GitHub Actions가 이를 감지하여 자동으로 Hashnode 블로그에 글을 발행해 주는 시스템을 통해 작성되었습니다.
원래는 티스토리(Tistory) API를 활용해 자동화를 하려고 했으나, 최근 티스토리 오픈 API가 완전히 종료됨에 따라 훌륭한 대안인 Hashnode를 선택하게 되었습니다. Hashnode는 개발자 친화적이며 강력한 GraphQL API를 제공하여 자동화 구축이 매우 수월합니다.
저처럼 GitHub 레포지토리를 블로그와 연동하여 자동으로 글을 업로드하고 싶으신 분들을 위해, 제가 방금 성공한 따끈따끈한 구축 방법을 단계별로 공유합니다!
🛠️ 1단계: Hashnode API 키 및 블로그 ID 발급
외부(GitHub)에서 내 Hashnode 블로그에 글을 쓰려면 권한이 필요합니다.
Personal Access Token (PAT) 발급
- Hashnode 우측 상단 프로필 >
Account Settings>Developer메뉴로 이동합니다. Generate New Token을 클릭하고 이름을 지정한 뒤 발급합니다.- (중요) 발급된 토큰은 다시 볼 수 없으니 반드시 복사해 둡니다.
- Hashnode 우측 상단 프로필 >
내 블로그의 Publication ID 확인
- Hashnode GraphQL Playground에 접속합니다.
- 좌측 쿼리창에 아래 코드를 입력하고
your-blog.hashnode.dev부분을 본인의 블로그 주소로 변경합니다.query { publication(host: "your-blog.hashnode.dev") { id } } - 상단의 재생(▶) 버튼을 누르면 우측에
id값(예:625d...)이 나옵니다. 이 값을 복사합니다.
🔒 2단계: GitHub 레포지토리 Secrets 설정
발급받은 토큰과 ID를 코드에 직접 노출하면 안 되므로 GitHub Secrets에 안전하게 보관합니다.
- 포스트를 관리할 GitHub 레포지토리의
Settings>Secrets and variables>Actions로 이동합니다. New repository secret을 클릭하여 다음 두 가지를 추가합니다.HASHNODE_PAT: 1단계에서 발급받은 PAT 토큰 값HASHNODE_PUBLICATION_ID: 1단계에서 확인한 Publication ID 값
📁 3단계: 폴더 구조 설정
로컬 컴퓨터에 레포지토리를 클론하고 다음과 같은 폴더 구조를 만듭니다.
my-til-blog/
├── .github/
│ ├── workflows/ # GitHub Actions 설정 파일이 들어갈 곳
│ └── scripts/ # 파이썬 자동화 스크립트가 들어갈 곳
└── _posts/ # 마크다운 포스트(.md)를 작성할 곳
🐍 4단계: 포스팅 파이썬 스크립트 작성
.github/scripts/ 폴더 안에 publish_to_hashnode.py 파일을 생성하고 아래 코드를 작성합니다. 이 스크립트는 마크다운 파일의 메타데이터(Frontmatter)와 본문을 읽어 Hashnode API로 전송하는 역할을 합니다.
import os
import sys
import requests
import frontmatter
HASHNODE_API_URL = "https://gql.hashnode.com"
HASHNODE_PAT = os.getenv('HASHNODE_PAT')
PUBLICATION_ID = os.getenv('HASHNODE_PUBLICATION_ID')
CREATE_POST_MUTATION = """
mutation publishPost($input: PublishPostInput!) {
publishPost(input: $input) {
post {
id
title
url
}
}
}
"""
def main():
filepath = sys.argv[1]
with open(filepath, 'r', encoding='utf-8') as f:
post = frontmatter.load(f)
title = post.get('title')
content = post.content
tags = post.get('tags', [])
if not title:
print(f"Error: 'title' not found in frontmatter of {filepath}")
sys.exit(1)
tag_objects = [{"slug": tag.lower().replace(" ", "-"), "name": tag} for tag in tags]
variables = {
"input": {
"title": title,
"contentMarkdown": content,
"publicationId": PUBLICATION_ID,
"tags": tag_objects
}
}
headers = {
"Authorization": HASHNODE_PAT
}
response = requests.post(
HASHNODE_API_URL,
json={'query': CREATE_POST_MUTATION, 'variables': variables},
headers=headers
)
response.raise_for_status()
result = response.json()
if 'errors' in result:
print(f"GraphQL Error: {result['errors']}")
sys.exit(1)
print("✅ Successfully published post to Hashnode!")
if __name__ == "__main__":
main()
⚙️ 5단계: GitHub Actions 워크플로우 작성
언제 위의 파이썬 스크립트를 실행할지 정의하는 파일입니다. .github/workflows/ 폴더 안에 publish_article.yml 파일을 만들고 아래 코드를 넣습니다.
name: Publish to Hashnode
on:
push:
branches:
- main
paths:
- "_posts/**.md"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Find changed markdown files
id: changed_files
uses: tj-actions/changed-files@v35
with:
files: _posts/**.md
- name: Set up Python
if: steps.changed_files.outputs.any_changed == 'true'
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install Python Dependencies
if: steps.changed_files.outputs.any_changed == 'true'
run: |
python -m pip install --upgrade pip
pip install python-frontmatter requests
- name: Run publisher script for each changed file
if: steps.changed_files.outputs.any_changed == 'true'
env:
HASHNODE_PAT: ${{ secrets.HASHNODE_PAT }}
HASHNODE_PUBLICATION_ID: ${{ secrets.HASHNODE_PUBLICATION_ID }}
run: |
for file in ${{ steps.changed_files.outputs.all_changed_files }}; do
python .github/scripts/publish_to_hashnode.py "$file"
done
📝 6단계: 글 작성 및 푸시
이제 \_posts/ 폴더 안에 마크다운 파일을 만들고 글을 작성합니다. 파일의 최상단에는 반드시 아래와 같이 --- 로 감싼 Frontmatter를 넣어주어야 스크립트가 제목과 태그를 인식합니다.
---
title: "포스트 제목입니다"
tags:
- "태그1"
- "태그2"
---
본문 내용...
모두 작성했다면 GitHub에 커밋하고 푸시합니다!
💡 주의사항 (제가 겪은 시행착오!)
모든 설정을 마치고 "첫 번째 커밋"으로 모든 파일(스크립트 포함)을 올리면 워크플로우가 정상 완료되었다고 뜨는데 글은 안 올라가는 현상이 발생할 수 있습니다. 이는 우리가 사용한 tj-actions/changed-files 액션이 '이전 커밋'과 '현재 커밋'을 비교해서 변경된 파일을 찾기 때문입니다. 첫 커밋은 비교할 대상이 없어서 변경된 파일이 '0개'라고 판단하고 스크립트 실행을 건너뜁니다. 해결책: 마크다운 파일에 띄어쓰기 하나라도 수정한 뒤 두 번째 커밋을 푸시해 보세요. 그때부터는 정상적으로 변경 파일을 감지하고 블로그에 글이 촥촥 올라갑니다! 이제 잔디(Commit)도 심고 블로그 관리도 하는 일석이조의 효과를 누려보세요! 🚀
---
이제 터미널에서 아래 명령어를 순서대로 실행하시면 업데이트가 완료됩니다.
```bash
git add .
git commit -m "docs: Update first post with full tutorial"
git push origin main