이미 Mix Space로 글과 노트를 관리하고 있으면서, 여기에 가볍고 빠른 사이트를 Hexo로 띄우고 싶다면, Hexo 저장소가 주기적으로 Mix Space API에서 콘텐츠를 가져와 자동으로 빌드한 뒤 GitHub Pages에 배포하도록 구성하면 됩니다.
전체 흐름
워크플로는 8시간마다 한 번 실행되며, push나 수동 실행으로도 트리거할 수 있습니다. 실행할 때마다 온라인에 공개된 콘텐츠를 다시 읽어 현재 시점의 전체 글 목록을 만들고, 이어서 Hexo 빌드를 수행합니다.
그림에 나오는 '숫자 번호 할당 또는 재사용'은 아무렇게나 번호를 붙이는 게 아니라 매핑 테이블을 통해 이뤄집니다. 지금은 이런 게 있다는 정도만 기억해 두고, 자세한 내용은 뒤에서 다루겠습니다.
왜 API에서 전부 동기화할까
새 글만 덧붙이는 방식이 더 단순해 보이지만, 삭제와 숨김, 수정을 제대로 처리하지 못합니다. 현재 구현은 매번 글과 노트의 전체 목록을 읽어옵니다.
GET /api/v3/posts?page=1&size=50&truncate=1
GET /api/v3/notes?page=1&size=50&withSummary=1
목록 API가 페이지 정보를 돌려주면, 프로그램은 page === total_pages가 될 때까지 계속 요청합니다. 목록은 글의 정체성과 요약을 얻는 데만 쓰고, 이어서 각 항목의 상세 정보를 요청합니다.
GET /api/v3/posts/{id}
GET /api/v3/notes/nid/{nid}?single=1
이렇게 하면 제목, 본문, 태그, 카테고리, 생성 시각, 수정 시각, 원본 라우트 같은 필드를 얻을 수 있습니다. 로그인하지 않은 요청은 Mix Space가 공개를 허용한 콘텐츠만 가져올 수 있으므로, 아직 발행하지 않았거나 예약 발행을 기다리는 콘텐츠, 비밀번호가 걸린 콘텐츠는 동기화되지 않습니다.
글과 노트는 어떻게 공존할까
글과 노트는 서로 다른 API 정체성을 씁니다.
| 유형 | 안정적인 정체성 | 원본 사이트 경로 |
|---|---|---|
| 글 | post:{id} | /posts/{categorySlug}/{slug} |
| 노트 | note:{id} | /notes/{nid} |
동기화 프로그램은 제목을 식별자로 삼지 않습니다. 제목은 바뀔 수 있으니까요. 대신 Mix Space가 돌려주는 변경 불가능한 id를 사용하고, 글과 노트를 각각 post:id, note:id로 표시합니다.
숫자 번호와 매핑 테이블
여기까지 읽다 보면 이런 의문이 들 수 있습니다. Mix Space의 id만으로도 글과 노트를 구분할 수 있는데, 숫자 번호는 왜 또 필요할까요?
이유는 Hexo 사이트의 글 경로가 숫자 번호를 쓰기 때문입니다. 예를 들면 다음과 같습니다.
/archives/1/
/archives/2/
/archives/3/
이 숫자는 Mix Space의 글 ID와 직접적인 관계가 없습니다. 그저 Hexo 내부에서 쓰는 경로 번호일 뿐입니다.
이렇게 이해하면 됩니다.
- Mix Space의
post:id나note:id는 주민등록번호처럼 '이 콘텐츠가 누구인지'를 나타내고, - Hexo의
/archives/1/,/archives/2/는 집 주소처럼 '이 콘텐츠가 Hexo 안에서 어디에 사는지'를 나타내며, - 루트 디렉터리의
.content-sync-map.json은 이 둘을 연결해 주는 주소록으로, '주민등록번호'와 '집 주소'의 대응 관계를 기억합니다.
이 매핑 테이블에는 대략 다음 내용이 기록됩니다.
| 기록 내용 | 역할 |
|---|---|
post:{id} / note:{id} | 이 콘텐츠가 Mix Space에서 가지는 안정적인 식별자 |
번호, 예: 1, 2, 3 | 이 콘텐츠가 Hexo에서 쓰는 고정 번호 |
active / inactive | 이 콘텐츠가 지금 동기화 대상에 포함되는지 여부 |
동작 방식은 그리 복잡하지 않습니다.
- 처음 동기화할 때는 매핑 테이블에 기록이 없으므로, 프로그램이 생성 시각 순서대로 다음 숫자를 할당합니다.
- 이후 동기화에서는 먼저
post:id나note:id를 얻은 뒤 매핑 테이블에서 기존 번호를 찾습니다. 찾으면 그대로 쓰고, 없을 때만 새 번호를 할당합니다. - Mix Space에서 제목이나 카테고리, slug를 수정해도 정체성은 그대로이므로 Hexo 숫자 번호도 바뀌지 않습니다.
- Mix Space에서 콘텐츠를 삭제하거나 숨기면 해당 파일은 현재 사이트에서 제거되고, 매핑 테이블에는
inactive로 표시됩니다. - 한 번 사용한 숫자는 원래 글이 이미 삭제되었더라도 다른 콘텐츠에 다시 할당되지 않습니다.
이렇게 하는 데에는 두 가지 장점이 있습니다.
- 제목이나 카테고리, Mix Space slug를 수정해도 Hexo 영구 링크가 바뀌지 않습니다.
- 한 번 사용한 번호는 다른 콘텐츠에 다시 할당되지 않습니다.
그러니 source/_posts/1.md 같은 파일의 이름을 직접 바꾸거나 매핑 파일을 지우지 마세요. 이 둘은 동기화 프로그램이 영구 경로를 유지하는 근거입니다.
원문 링크는 어떻게 자동 생성될까
생성되는 모든 Hexo 파일에는 original_url이 기록됩니다.
original_url: https://moitr.ren/posts/categories/example
글의 원문 경로는 카테고리 slug와 글 slug를 조합해 만들고, 노트는 nid를 사용합니다.
글: /posts/{category.slug}/{slug}
노트: /notes/{nid}
도메인은 https://moitr.ren으로 고정입니다. 덕분에 Hexo가 숫자 아카이브 경로를 쓰더라도 독자는 글 페이지에서 Mix Space 원문으로 돌아갈 수 있습니다. 단, 이 기능은 사용하는 테마가 지원해야 합니다.
본문 형식과 Core 컨테이너
content_format이 markdown이면 API가 돌려준 text를 사용합니다. 형식이 lexical일 때도 현재 구현은 Mix Space가 제공하는 공개 text를 우선 사용합니다. 동기화 쪽에서 에디터 JSON의 렌더링 규칙을 전부 다시 구현하지 않기 위해서입니다.
동기화 단계에서는 Mix Space의 사용자 정의 Markdown 컨테이너 일부도 처리합니다. 예를 들어
masonry컨테이너는 Hexo 테마에 이미 있는 이미지 갤러리 구조로 변환됩니다.success컨테이너는 Markdown 인용 블록으로 변환됩니다.- 알 수 없는 컨테이너는 원문 그대로 둡니다. 자동 동기화가 원문을 잘못 지우는 일을 막기 위해서입니다.
줄바꿈은 LF로 통일하고, 본문 끝에는 줄바꿈을 하나만 남기며, 시각은 UTC ISO 형식으로 통일합니다. 이렇게 하면 Windows에서 로컬로 돌리든 Linux Actions에서 돌리든 파일 내용이 똑같이 나옵니다.
GitHub Actions의 빌드 순서
워크플로의 핵심 단계는 다음과 같습니다.
현재 스케줄 표현식은 다음과 같습니다.
- cron: "20 */8 * * *"
GitHub Actions는 UTC를 사용하므로, 이는 베이징 시간으로 매일 00:20, 08:20, 16:20에 해당합니다. 바로 갱신하고 싶다면 main에 푸시하거나, Actions 페이지에서 워크플로를 수동으로 실행하면 됩니다.
유지해야 할 파일
동기화 기능은 주로 다음 파일들로 이뤄져 있습니다.
tools/sync-content.js # API 호출, 데이터 정규화, 글 생성
.content-sync-map.json # Mix Space 정체성과 숫자 번호의 영구 매핑
.github/workflows/deploy-pages.yml
source/_posts/ # 자동 생성되는 Hexo 글 디렉터리
source/_posts에 있는 숫자로 된 Markdown 파일은 자동 생성된 결과물이므로 직접 편집하지 않는 편이 좋습니다. 다음 동기화 때 온라인 API의 내용이 로컬에서 손댄 부분을 덮어씁니다.
로컬 검증
워크플로를 커밋하기 전에 먼저 다음을 실행해 볼 수 있습니다.
pnpm install --frozen-lockfile
pnpm content:sync
pnpm test
pnpm clean
pnpm build
API를 잠시 쓸 수 없는 상황이라면 pnpm content:sync는 실패해야 하고, 기존 글은 그대로여야 합니다. 빌드가 끝나면 public/archives/에 아카이브 페이지와 숫자 글 경로가 생겼는지 확인해 보세요.
자주 겪는 주의 사항
1. 매핑 파일을 삭제하지 마세요
.content-sync-map.json을 지우면 프로그램이 기존 글과 숫자 번호의 대응 관계를 잃어버립니다. 다시 동기화할 때 해당 콘텐츠가 완전히 새로운 글로 취급되어 새 경로를 받을 수 있습니다.
2. 첫 페이지만 요청하지 마세요
글 수가 50을 넘으면 첫 페이지가 전체 데이터를 의미하지 않습니다. meta.pagination.total_pages를 기준으로 모든 페이지를 요청해야 합니다. 그렇지 않으면 사이트에서 글이 조용히 빠집니다.
3. API 키를 프런트엔드에 넣지 마세요
현재 공개 콘텐츠 API는 로그인이 필요 없으므로 Actions에서 바로 요청하면 됩니다. 나중에 인증이 필요한 API로 바뀐다면 자격 증명은 GitHub Actions Secrets에 넣고 워크플로에서만 사용해야 합니다. 테마 JavaScript나 생성된 HTML에는 절대 넣어서는 안 됩니다.
정리
Mix Space는 콘텐츠 관리를, GitHub Actions는 주기적인 수집과 검증을, Hexo는 정적 페이지 생성을, GitHub Pages는 배포를 맡습니다. 안정적인 원격 정체성과 재사용되지 않는 숫자 번호, 실패 방지 장치 덕분에 Hexo 글 파일을 직접 관리하지 않고도 원본 블로그와 개인 홈페이지 사이의 동기화를 안정적으로 유지할 수 있습니다.