days since this article was written, please be aware of its timeliness
A fellow student previously asked me what I use for my blog. I said it’s just Jekyll on GitHub Pages, with the built HTML hosted, but that explanation seemed a bit rough. Plus, the process involves integrating various services, so here I’ll elaborate in detail and share the code.
Overall Workflow
The overall flowchart for Markdown files:

The overall flowchart for blog images:

A few notes on image handling:
-
During local development, the image repository is a submodule of the blog repository. When adding new images locally, you must first push the images to the image repository, triggering the repository’s action to sync them to Tencent Cloud COS. Only then should you push the blog code; otherwise, the image reference links won’t work.
-
When writing blog posts, images are referenced using relative paths. Jekyll has three configuration files for local development, .com website references, and .cn website references. The only difference between these YAML files is the static resource references:
-
Local config file:
static_url: /static -
.com config file:
static_url: https://static.xheldon.com -
.cn config file:
static_url: https://static.xheldon.cn -
To reference image resources in Markdown, the syntax is: \!\[\]\(https://static.xheldon.cn/img/in-post/qing-zheng-lu-yu/IMG_3789.png) (Jekyll processes the Markdown files first, replacing Liquid variables before building the HTML).
Origin
I initially wanted to use Notion as a data source to update the blog, which required a server. So, I purchased a Tencent Cloud Lighthouse server to serve as the backend for fetching Notion data. The results can be seen here:
Subscription & Paid Software - Xheldon Blog
Secondly, another reason is that blogging inevitably requires images. Initially, I used jsDelivr’s Github repository acceleration service:
jsDelivr - A free, fast, and reliable CDN for Open Source
However, the awkward part is that jsDelivr’s acceleration service imposes a 50MB limit per repository, meaning it’s suitable for public js/css files but clearly not ideal for images.
Given this, once I had a server, the solution became obvious: buy another domain and enable CDN acceleration.
Then, taking it a step further—since I’d already bought a domain, and most visitors to my blog are from mainland IPs—why not create a domestic version of the blog? The domain could simply be called https://xheldon.cn.
So, without delay, here’s a summary of the process.
Server and Domain
Purchase
The server I bought is Tencent Cloud’s lightweight server, with 4 cores, 8GB RAM, and 4M bandwidth. It was purchased at a steep discount (think promotional prices at 0.x% off), so it was very affordable.
After buying the domain, of course, it had to be registered for use in mainland China. Without registration, your domain won’t resolve, and accessing it will display messages like “This domain is unregistered and has been suspended.” Fortunately, Tencent Cloud offers free registration services, and the process has been simplified. You just need to fill out Tencent Cloud’s registration application form, providing details like home address, contact information, website purpose, and reasons for application. If your submission doesn’t meet the Ministry of Industry and Information Technology’s requirements—for example, writing something like “Screw the review, just approve it already” in the application reason field—it will definitely be rejected when submitted to the regulatory bureau (the government agency overseeing domain registration is called the “管局”). If there are issues with your submission, a staff member will contact you to help revise and confirm before resubmitting.
Configuration
The configuration involves the following steps, which I’ll briefly outline:
-
Apply for a free HTTPS certificate and enable HTTPS.
-
Set up the CDN acceleration domain.
-
Purchase COS (Cloud Object Storage) for image storage (new users get it for free).
-
Launching a Node service with Express, exposing port 80 externally via Nginx reverse proxy. The server pulls static HTML resources from Gitee and handles certain API requests. Why not pull directly from GitHub but go through the trouble of these API requests?
-
Responding to webhooks from Gitee to notify the server to pull the latest HTML files from Gitee.
-
Handling Notion query requests from the blog. The server will query Notion’s servers to fetch data.
Some points to clarify:
-
For the lightweight server, I chose a Docker image with Node + Nginx. You could also opt for Java or a custom setup—it’s up to you.
-
By default, Node and Nginx on the lightweight server are installed under the
lighthouseuser, often causing permission issues during package installation. To simplify things, I uninstalled the preinstalled Nginx and Node and reinstalled them under the root user. -
Occasionally, I need to upload files via FTP to the server, such as the SSL certificates mentioned earlier. This requires configuring FTP-related settings. Tencent Cloud has relevant documentation—just search for it, so I’ll skip the details here.
-
Domain registration (ICP filing) takes at least a week; in my case, it took about two weeks.
Repository Configuration
Configuring GitHub Actions to Build Static Files
Since I don’t want to expose my blog’s source code and because GitHub Pages has limited support for Jekyll plugins (e.g., pagination on both the homepage and category pages isn’t possible with GitHub Pages’ supported plugins), I decided to build the source code into HTML myself.
Due to the limitations of the free version of GitHub Pages, private repositories cannot enable GitHub Pages. Therefore, I set up another repository as public while keeping the source code repository private. After committing code to the private repository, it is built via GitHub Actions and then pushed to the public repository.
For details on using GitHub Pages and GitHub Actions, you can refer to this article I wrote earlier:
Free Use of Private Repositories to Publish GitHub Pages - Xheldon Blog
However, since I also acquired a domestic domain, some modifications were made to the configuration files. Below are the new configuration files:
1 | |
Configuring Another Repository to Enable GitHub Pages
This step is self-explanatory—just toggle the GitHub Pages switch for the repository.
Configuring Coding Webhooks
In Gitee’s repository management under Webhooks, configure your WebHook address. Mine is https://www.xheldon.cn/hooks_cn_push。.
Configuring the Server to Respond to Webhooks
The server runs an Express service. Here’s the code directly:
1 | |
Configuring the Action for the Image Repository
After uploading images to the image repository x_blog-static, a GitHub Action is triggered to incrementally update Tencent Cloud COS. Here’s the code:
1 | |
Of course, some environment variables, such as COS secrets, can be configured in the repository by referring to the GitHub Action mentioned above.
The above executes a JS file that retrieves commit details to determine what was added, deleted, modified, or renamed in the commit, then performs a batch upload. The repository is public and can be found here.
Future Plans
As mentioned in ](https://www.xheldon.com/tech/use-craft-extension-to-write-blog.html) this article, this post is also synchronized from Craft to a GitHub repository via a plugin. The numerous advantages have already been clearly explained in that article, so I won’t reiterate them here. The only current issue is with images inserted via Craft. Although the official team has released a CORS-unrestricted fetch API (Mac-only), I haven’t yet figured out an elegant way to synchronize images uploaded to Craft with Tencent Cloud COS.
The current plan is to abandon the step of saving images on GitHub and instead directly send images from Craft to Tencent Cloud COS via an extension.
-
Previous
"Ace, CodeMirror, and Monaco: A Comparison of Web Code Editors" -
Next
This article was sent via Craft Extension
I often wish that when facing some key decisions in life, someone could tell me the best course of action so that I would not waste my precious time. Putting myself in others' shoes, I therefore write blogs often, hoping to record in this tiny corner of the vast Internet the once-in-a-lifetime experiences that matter to me, and to help those who seek help.