If you already use Mix Space to manage posts and notes and also want to use Hexo to provide a lightweight, fast, easy-to-customize public site, you can have a Hexo repository periodically pull content from the Mix Space API, then automatically build and deploy it to GitHub Pages.
The point of this setup is not to turn the two blogs into a two-way editing system, but to treat Mix Space as the single source of content and Hexo as the presentation layer. Posts are maintained only in Mix Space; the post files in Hexo are generated by an automated program.
Overall Flow
The workflow runs every 8 hours and can also be triggered by push or manually. Each run re-reads the public online content, generates the current complete post directory, and then runs the Hexo build.
Why Sync Everything from the API
Appending only new posts seems simpler, but it cannot correctly handle deletions, hidden content, and modifications. The current implementation reads the complete list of posts and notes every time:
GET /api/v3/posts?page=1&size=50&truncate=1
GET /api/v3/notes?page=1&size=50&withSummary=1
After the list endpoint returns pagination information, the program keeps requesting until page === total_pages. The list is only used to obtain post identities and summaries; it then requests details for each item:
GET /api/v3/posts/{id}
GET /api/v3/notes/nid/{nid}?single=1
This way it obtains fields such as title, body, tags, category, creation time, modification time, and original route. Unauthenticated requests can only obtain content that Mix Space allows to be public. Unpublished, scheduled but not yet public, and password-protected content will not be synced.
How Posts and Notes Coexist
Posts and notes use different API identities:
| Type | Stable identity | Original site path |
|---|---|---|
| Post | post:{id} | /posts/{categorySlug}/{slug} |
| Note | note:{id} | /notes/{nid} |
The sync program does not use titles as identities, because titles can change. It uses the immutable id returned by Mix Space and marks posts and notes as post:id or note:id respectively.
Numeric Numbering Only Controls Hexo Internal Paths
Post paths on the Hexo site use numeric numbering, for example:
/archives/1/
/archives/2/
/archives/3/
The number is not directly related to the Mix Space post ID; it is stored in the .content-sync-map.json file in the repository root. On the first sync, the program assigns the next number according to creation time; subsequent syncs recover the original number through post:id or note:id.
This brings two benefits:
- Changing the title, category, or Mix Space slug does not change the Hexo permalink.
- A number that has already been used will not be reassigned to another piece of content.
So do not manually rename files such as source/_posts/1.md, and do not delete the mapping file. They are the basis on which the sync program preserves permanent paths.
How Original Links Are Generated Automatically
Each generated Hexo file writes original_url:
original_url: https://moitr.ren/posts/categories/example
The original path for a post is composed of the category slug and post slug, while a note uses its nid:
文章:/posts/{category.slug}/{slug}
笔记:/notes/{nid}
The domain is fixed as https://moitr.ren. This way, even though Hexo uses numeric archive paths, readers can still go back to the original Mix Space post from the article page. This requires your theme to support it
Body Format and Core Containers
When content_format is markdown, the program uses the text returned by the API. When the format is lexical, the current implementation also prefers using the public text provided by Mix Space, avoiding reimplementing all rendering rules for the editor JSON on the sync side.
The sync stage also handles some of Mix Space's custom Markdown containers. For example:
masonrycontainers are converted into the image gallery structure already provided by the Hexo theme.successcontainers are converted into Markdown blockquotes.- Unknown containers are kept as-is, to avoid the automatic sync accidentally deleting the original content.
Line endings are normalized to LF, only a single trailing newline remains at the end of the body, and times are written in UTC ISO format. This ensures stable file content whether it runs locally on Windows or in Linux Actions.
What Happens When Content Is Modified, Deleted, or the API Fails
Syncing does not append files to the directory indefinitely. Instead, it generates all results in a temporary directory, verifies that the count and post payloads are valid, and then replaces source/_posts all at once.
- When Mix Space modifies body or publish time: the corresponding numbered file is updated.
- When Mix Space deletes or hides content: the corresponding file is removed from the current site and marked inactive in the mapping.
- Previously used numbers: even if a post is deleted, the number will not be reassigned.
- API request failure: the program fails before writing, preserves the existing local posts and mapping, and does not publish an incomplete site.
This is also the key reason full sync matters more than a simple download script: deletions take effect, and network failures do not replace the live site with a partial set of content.
GitHub Actions Build Order
The core workflow steps are as follows:
The current schedule expression is:
- cron: "20 */8 * * *"
GitHub Actions uses UTC, so this corresponds to Beijing time at 00:20, 08:20, and 16:20 every day. If you need an immediate update, you can push to main, or run the workflow manually from the Actions page.
Files That Need to Be Preserved
The sync feature mainly consists of the following files:
tools/sync-content.js # 拉取 API、规范化数据、生成文章
.content-sync-map.json # Mix Space 身份到数字编号的永久映射
.github/workflows/deploy-pages.yml
source/_posts/ # 自动生成的 Hexo 文章目录
The numbered Markdown files in source/_posts are auto-generated content and are not recommended to edit directly. On the next sync, the online API content will overwrite any local manual changes.
Local Verification
Before committing the workflow, you can first run:
pnpm install --frozen-lockfile
pnpm content:sync
pnpm test
pnpm clean
pnpm build
If the API is temporarily unavailable, pnpm content:sync should fail without changing existing posts. After the build finishes, you can check whether public/archives/ contains generated archive pages and numbered post paths.
Common Considerations
1. Do Not Delete the Mapping File
Deleting .content-sync-map.json makes the program lose the correspondence between old posts and numeric numbers. When syncing again, the content may be treated as brand-new posts and assigned new paths.
2. Do Not Request Only the First Page
Once there are more than 50 posts, the first page does not represent the complete data. You must request all pages according to meta.pagination.total_pages; otherwise the site will silently miss posts.
3. Do Not Put API Keys in the Frontend
The current public content API does not require login, so Actions can request it directly. If it later changes to an authenticated API, credentials should be placed in GitHub Actions Secrets and used only in the workflow. They must not be written into theme JavaScript or generated HTML.
Summary
Mix Space handles content management, GitHub Actions handles periodic fetching and verification, Hexo generates static pages, and GitHub Pages handles distribution. Through stable remote identities, numeric identifiers that are never reused, and failure protection, you can keep the original blog and the personal homepage reliably in sync without manually maintaining Hexo post files.