Post

Things to Know About Automatically Backing Up Medium Articles to GitHub Pages (Jekyll)

A record of building, maintaining, upgrading, and customizing a personal Medium article backup mirror site.

Things to Know About Automatically Backing Up Medium Articles to GitHub Pages (Jekyll)

ℹ️ℹ️ℹ️ The following content is translated by OpenAI.

Click here to view the original Chinese version. | 點此查看本文中文版


Things to Know About Automatically Backing Up Medium Articles to GitHub Pages (Jekyll)

A record of building, maintaining, upgrading, and customizing a personal Medium article backup mirror site.

Introduction

I’ve been managing my Medium account for six years, and last year, I surpassed 100 articles. As time goes on and the number of articles increases, I worry more about the possibility of Medium suddenly shutting down or my account encountering issues that could lead to the loss of all my hard work. While some articles may not hold much value, many serve as records of technical frameworks and problem-solving thoughts from that time. I often revisit my older articles to refresh my knowledge. Additionally, in recent years, I’ve started documenting my travel experiences abroad, which are cherished memories and have performed well in terms of traffic. Losing this content would mean I could never rewrite it.

Developing a Backup Tool

I usually write articles directly on the Medium platform without any personal backup. Therefore, during the 2022 New Year period, I took the time to develop a tool to download Medium articles and convert them into Markdown files (including article images, embedded code, etc.) — ZMediumToMarkdown:

I then extended the use of this tool to deploy the downloaded Markdown files as a static backup mirror site on GitHub Pages using Jekyll (Chirpy Theme)https://zhgchg.li/

[https://zhgchg.li/](https://zhgchg.li/){:target="_blank"}

https://zhgchg.li/

At that time, I integrated the entire setup into a GitHub Template Repo for friends with similar needs to quickly deploy — ZMediumToJekyll. Since then (in 2022), I haven’t updated the version or settings of Jekyll (Chirpy Theme); however, ZMediumToMarkdown is still being maintained, and I promptly fix any formatting parsing errors that arise. It is currently stable.

The version of Jekyll (Chirpy Theme) I was using at that time was v5.x, which had no major issues and included all necessary features (e.g., pinning, categories, tags, cover images, comments, etc.). The only problem was that the page would often become unscrollable while scrolling, but after a few attempts, it would return to normal. This was a frustrating user experience. I once tried upgrading to v6.x, but the issue persisted, and I received no response when reporting it to the official team. Additionally, as versions increase, the conflicts encountered during upgrades become more frequent, so I eventually abandoned the idea of upgrading.

Recently, I decided to tackle the issues with Jekyll (Chirpy Theme), upgrade the version, and simultaneously optimize the quick deployment tool ZMediumToJekyll.

New! medium-to-jekyll-starter 🎉🎉

medium-to-jekyll-starter.github.io

I have integrated the latest version v7.x of Jekyll (Chirpy Theme) with my ZMediumToMarkdown Medium article download and conversion tool into a new — medium-to-jekyll-starter.github.io GitHub Template Repo.

You can use this template repo to quickly set up your own Medium mirror content backup site, with a one-time setup for continuous automatic backups, deployed on GitHub Pages completely free.

For a step-by-step setup guide, please refer to this article: https://zhgchg.li/posts/zh-TW-medium-to-jekyll/

Results

[https://zhgchg.li/](https://zhgchg.li/){:target="_blank"}

https://zhgchg.li/

*All the articles above are automatically downloaded from my Medium account, converted to Markdown format, and re-uploaded.

Here’s a comparison example of the conversion results from a random article:

Original content on Medium / Converted result on my personal site

After the upgrade, the scrolling issue has been resolved, and I also added customized dynamic content (displaying the number of Medium followers).

Some Technical Records

The deployment method for Jekyll (Chirpy Theme) on GitHub Pages mainly references the official Start Repo:

Last month, I also referenced this project to create a new open-source project — Linkyee which is an open-source version of a Link Tree personal link page.

[https://link.zhgchg.li/](https://link.zhgchg.li/){:target="_blank"}

https://link.zhgchg.li/

Jekyll Customization Method (1) — Override HTML

Jekyll is a powerful Ruby static site generator. Jekyll (Chirpy Theme) is just a theme based on Jekyll. Compared to other themes, Chirpy Theme has the best quality, user experience, and comprehensive features.

Jekyll pages have inheritance, allowing us to add files with the same page name as Jekyll in ./_layouts. The engine will use your custom page content to replace the original when generating the site content.

For example, if I want to add a line of text at the end of each article page, I first copy the original article page file (post.html) and place it in the ./_layouts directory:

Using an editor, I open post.html, add the text or customization in the appropriate location, and redeploy the site to see the customized result.

You can also create a ./_include directory to store some shared page content files:

Then in post.html, you can directly use {% include buymeacoffee.html %} to include the HTML content from the file for reuse.

The advantage of overriding HTML layout files is 100% customization; you can freely adjust how the page content and layout are presented. The downside is that during the upgrade process, you may encounter conflicts or unexpected results, requiring you to review your customizations again.

Jekyll Customization Method (2) — Plugin

The second method is to use the Plugin with the Hook method, injecting your desired custom content during the static content generation phase in Jekyll.

[Built-in Hook Owners and Events](https://jekyllrb.com/docs/plugins/hooks/#built-in-hook-owners-and-events){:target="_blank"}

There are many Hook events, and here I will only mention the ones I used: site:pre_render and post:pre_render.

Adding a hook is simple; just create a Ruby file in ./_plugins.

posts-lastmod-hook.rb is an existing Plugin

The posts-lastmod-hook.rb is an existing Plugin.

I wanted a few “pseudo” dynamic content features, the first being to display the number of Medium followers under my profile and the last updated time at the bottom of the page.

In the ./_plugins directory, I created a file named zhgchgli-customize.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env ruby
#
require 'net/http'
require 'nokogiri'
require 'uri'
require 'date'

def load_medium_followers(url, limit = 10)
  return 0 if limit.zero?

  uri = URI(url)
  response = Net::HTTP.get_response(uri)
  case response
  when Net::HTTPSuccess then
      document = Nokogiri::HTML(response.body)

      follower_count_element = document.at('span.pw-follower-count > a')
      follower_count = follower_count_element&.text&.split(' ')&.first

      return follower_count || 0
  when Net::HTTPRedirection then
    location = response['location']
    return load_medium_followers(location, limit - 1)
  else
      return 0
  end
end

$medium_url = "https://medium.com/@zhgchgli"
# could also define in _config.yml and retrieve in Jekyll::Hooks.register :site, :pre_render do |site| site.config

$medium_followers = load_medium_followers($medium_url)

$medium_followers = 1000 if $medium_followers == 0
$medium_followers = $medium_followers.to_s.reverse.scan(/\d{1,3}/).join(',').reverse

Jekyll::Hooks.register :site, :pre_render do |site|

  tagline = site.config['tagline']
  
  followMe = <<-HTML
  <a href="#{$medium_url}" target="_blank" style="display: block;text-align: center;font-style: normal;/* text-decoration: underline; */font-size: 1.2em;color: var(--heading-color);">#{$medium_followers}+ Followers on Medium</a>
  HTML

  site.config['tagline'] = "#{followMe}";
  site.config['tagline'] += tagline;

  meta_data = site.data.dig('locales', 'en', 'meta');
  # only implementation in en, could implement to all langs.

  if meta_data
    gmt_plus_8 = Time.now.getlocal("+08:00")
    formatted_time = gmt_plus_8.strftime("%Y-%m-%d %H:%M:%S")
    site.data['locales']['en']['meta'] += "<br/>Last updated: #{formatted_time} +08:00"
  end
end
  • The principle is to register a hook before the site renders, adding the Medium follower count to the tagline section under the profile.
  • The Medium follower count will be fetched each time to get the latest number.
  • The logic for the last updated time at the bottom is similar; it adds the last updated time string to locales->en->meta during site generation.
  • Additionally, if it’s a hook before article generation, you can access the Markdown, and after article generation, you can access the generated HTML.

After saving, you can test the results locally with bundle exec jekyll s:

Open your browser and go to 127.0.0.1:4000 to check the results.

Finally, add a scheduled job in the Actions of your GitHub Pages Repo to automatically regenerate the site:

In the Jekyll (Chirpy Theme) Repo project, find the Actions file named pages-deploy.yml and add the following under on::

1
2
  schedule:
    - cron: "10 1 * * *" # Automatically execute once a day at UTC 01:10, https://crontab.guru

The advantage of using plugins is that it allows for dynamic content updates (scheduled content updates) without affecting the site structure, avoiding conflicts during upgrades. The downside is that the content and display positions you can adjust are limited.

Issues with GitHub Pages Deployment After Jekyll (Chirpy Theme) v7.x

In addition to structural adjustments, the deployment script for v7.x has also changed; the original deploy.sh deployment script has been removed, and the deployment steps now directly use GitHub Actions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# build:
# ...
      - name: Upload site artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: "_site${{ steps.pages.outputs.base_path }}"

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

However, I encountered an issue during deployment:

Uploaded artifact size of 1737778940 bytes exceeds the allowed size of 1 GB because my site content was too large, causing the Upload Artifact to fail. Since the previous deployment script worked, I had to revert to using the original deploy.sh and comment out the above section.

GitHub Pages Deployment Test Site Step Failing

The deployment of Jekyll (Chirpy Theme) includes a step to test the site, checking if the web content is correct, such as whether links are functioning and if HTML tags are missing, etc.:

1
2
3
4
5
6
7
8
9
# build:
# ...
      - name: Test site
        run: |
          bundle exec htmlproofer _site \
            --disable-external \
            --no-enforce-https \
            --ignore-empty-alt \
            --ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/"

I added --no-enforce-https and --ignore-empty-alt to ignore HTTPS and missing alt checks for HTML tags, which allowed the checks to pass (since I couldn’t modify the content temporarily).

The CLI command for htmlproofer is not documented in the official documentation, and I spent a long time searching before finding the rules in a comment on a certain issue here.

Image

Link to the original comment

Additional Articles

If you have any questions or feedback, feel free to contact me.


This article was first published on Medium ➡️ Click Here

Automatically converted and synchronized using ZMediumToMarkdown and Medium-to-jekyll-starter.

Improve this page on Github.

Buy me a beer

317 Total Views
Last Statistics Date: 2025-03-11 | 28 Views on Medium.
This post is licensed under CC BY 4.0 by the author.