Post

Seamlessly Migrate Medium Content to Self-Hosted Sites|Github Pages with Jekyll/Chirpy

Content creators struggling with Medium limitations can transfer posts to Github Pages using Jekyll and Chirpy, enabling full control and customization while maintaining content integrity and SEO benefits.

Seamlessly Migrate Medium Content to Self-Hosted Sites|Github Pages with Jekyll/Chirpy

点击这里查看本文章简体中文版本。

點擊這裡查看本文章正體中文版本。

This post was translated with AI assistance — let me know if anything sounds off!


Seamless Migration from Medium to a Self-Hosted Website

Moving Medium Content to Github Pages (with Jekyll/Chirpy)

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

zhgchg.li

Background

In my fourth year of running Medium, I have accumulated over 65 articles and nearly 1000+ hours of effort. The reason I chose Medium initially was its simplicity and convenience, allowing me to focus on writing without worrying about other issues. Before this, I tried self-hosting WordPress, but I spent most of my time dealing with the environment, themes, and plugins. No matter how I adjusted, I was never satisfied. After making changes, I found the loading speed too slow, the reading experience poor, and the backend writing interface not user-friendly, so I stopped updating it frequently.

As I wrote more articles on Medium and gained some traffic and followers, I started wanting to control these achievements myself, rather than being controlled by a third-party platform (e.g., losing everything if Medium shuts down). So, since the year before last, I have been looking for a secondary backup website. I will continue to maintain Medium but also publish content simultaneously on a site I control. The solution I found back then was — Google Site but honestly, it can only serve as a personal “portal” site. The article writing interface is limited and cannot fully transfer all my articles.

In the end, I returned to self-hosting, but instead of using a dynamic website (e.g., WordPress), I chose a static website. Although it supports fewer features, what I need is just the article writing function and a simple, smooth, customizable browsing experience—nothing else!

The workflow of a static website is: write articles in Markdown format locally, then convert them into static web pages using a static site generator and upload them to the server; done. Static web pages provide a fast browsing experience!

Writing in Markdown format allows your article to be compatible with more platforms; if you’re not used to it, you can also find online or offline Markdown editors, which offer an experience similar to writing directly on Medium!

In summary, this solution can achieve both the smooth browsing experience and the convenient writing interface I desire.

Results

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

zhgchg.li

  • Support Custom Display Styles

  • Support customized page adjustments (e.g., inserting ads, JS widgets)

  • Support Custom Pages

  • Support Custom Domain Names

  • Static pages load quickly and provide a smooth browsing experience

  • Use Git version control to preserve and restore all historical versions of the article.

  • Fully Automated Scheduled Sync of Medium Articles to Website

2025/01/18 Update 🎉🎉🎉

Environment and Tools

Installing Ruby

Here, I only use my environment as an example. For other operating system versions, please Google how to install Ruby.

  • macOS Monterey 12.1

  • rbenv

  • ruby 2.6.5

Install Brew

1
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Enter the above command in Terminal to install Brew.

Install rbenv

1
brew install rbenv ruby-build

Although MacOS comes with Ruby pre-installed, it is recommended to use rbenv to install a separate Ruby version to keep it isolated from the system default. Enter the above command in Terminal to install rbenv.

1
rbenv init

Enter the above command in Terminal to initialize rbenv

  • Close & Reopen Terminal.

Type rbenv in the Terminal to check if the installation was successful!

Success!

Installing Ruby with rbenv

1
rbenv install 2.6.5

Enter the above command in Terminal to install Ruby version 2.6.5.

1
rbenv global 2.6.5

Enter the above command in Terminal to switch the Ruby version used by Terminal from the system default to the rbenv version.

In the Terminal, type rbenv versions to check the current settings:

In Terminal, enter ruby -v to check the current Ruby version, and gem -v to check the current RubyGems version:

  • After installing Ruby, RubyGems should also be installed.

Success!

Install Jekyll & Bundler & ZMediumToMarkdown

1
gem install jekyll bundler ZMediumToMarkdown

Enter the above commands in Terminal to install Jekyll, Bundler, and ZMediumToMarkdown.

Completed!

Create a Jekyll Blog from a Template

The default Jekyll Blog style is very simple. We can find styles we like from the following websites and apply them:

The installation method generally uses gem-based themes. Some repositories offer installation via Fork; others provide one-click installation. In short, each theme may have a different installation method, so please refer to the theme’s instructions.

Also note that since we are deploying to GitHub Pages, not all templates are supported according to the official documentation.

Chirpy Template

Here, I will use the template Chirpy adopted by my blog as an example. This template offers the simplest one-click installation method and can be used directly.

Other templates rarely offer similar one-click installation. For those unfamiliar with Jekyll or GitHub Pages, using this template first is a better way to get started. Future articles may cover installation methods for other templates.

Alternatively, you can find templates on Github that can be directly forked (e.g. al-folio) and used immediately. If none suit your needs and you have to manually install a template, you will need to figure out how to set up Github Pages deployment yourself. I tried researching this but was unsuccessful; I will update this article with my findings in the future.

Create a Git Repo from a Git Template

https://github.com/cotes2020/chirpy-starter/generate

  • Repository name: GithubUsername/OrganizationName.github.io ( Be sure to use this format )

  • Be sure to select “Public” to make the Repo public.

Click “Create repository from template”

Repository creation completed.

Git Clone Project

1
git clone git@github.com:zhgchgli0718/zhgchgli0718.github.io.git

git clone the newly created Repo.

Run bundle to install dependencies:

Run bundle lock — add-platform x86_64-linux to lock the version

Modify Website Settings

Open the _config.yml configuration file to set up:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# The Site Configuration

# Import the theme
theme: jekyll-theme-chirpy

# Change the following value to '/PROJECT_NAME' ONLY IF your site type is GitHub Pages Project sites
# and doesn't have a custom domain.
# baseurl: ''

# The language of the webpage › http://www.lingoes.net/en/translator/langcode.htm
# If it has the same name as one of the files in folder `_data/locales`, the layout language will also be changed,
# otherwise, the layout language will use the default value of 'en'.
lang: en

# Additional parameters for datetime localization, optional. › https://github.com/iamkun/dayjs/tree/dev/src/locale
prefer_datetime_locale:

# Change to your timezone › http://www.timezoneconverter.com/cgi-bin/findzone/findzone
timezone:

# jekyll-seo-tag settings › https://github.com/jekyll/jekyll-seo-tag/blob/master/docs/usage.md
# ↓ --------------------------

title: ZhgChgLi                          # the main title

tagline: Live a life you will remember.   # it will display as the sub-title

description: >-                        # used by seo meta and the atom feed
    ZhgChgLi iOS Developer eager to learn, teaching and learning, loves movies/TV shows/Western music/sports/life

# fill in the protocol & hostname for your site, e.g., 'https://username.github.io'
url: 'https://zhgchg.li'

github:
  username: ZhgChgLi             # change to your github username

twitter:
  username: zhgchgli            # change to your twitter username

social:
  # Change to your full name.
  # It will be displayed as the default author of the posts and the copyright owner in the Footer
  name: ZhgChgLi
  email: zhgchgli@gmail.com             # change to your email address
  links:
    - https://medium.com/@zhgchgli
    - https://github.com/ZhgChgLi
    - https://www.linkedin.com/in/zhgchgli

google_site_verification:               # fill in your verification string

# ↑ --------------------------
# The end of `jekyll-seo-tag` settings

google_analytics:
  id: G-6WZJENT8WR                 # fill in your Google Analytics ID
  # Google Analytics pageviews report settings
  pv:
    proxy_endpoint:   # fill in the Google Analytics superProxy endpoint of Google App Engine
    cache_path:       # the local PV cache data, friendly to visitors from GFW region

# Prefer color scheme setting.
#
# Note: Keep empty will follow the system prefer color by default,
# and there will be a toggle to switch the theme between dark and light
# on the bottom left of the sidebar.
#
# Available options:
#
#     light  - Use the light color scheme
#     dark   - Use the dark color scheme
#
theme_mode:   # [light\|dark]

# The CDN endpoint for images.
# Notice that once it is assigned, the CDN url
# will be added to all image (site avatar & posts' images) paths starting with '/'
#
# e.g. 'https://cdn.com'
img_cdn:

# the avatar on sidebar, support local or CORS resources
avatar: '/assets/images/zhgchgli.jpg'

# boolean type, the global switch for ToC in posts.
toc: true

comments:
  active: disqus        # The global switch for posts comments, e.g., 'disqus'.  Keep it empty means disable
  # The active options are as follows:
  disqus:
    shortname: zhgchgli    # fill with the Disqus shortname. › https://help.disqus.com/en/articles/1717111-what-s-a-shortname
  # utterances settings › https://utteranc.es/
  utterances:
    repo:         # <gh-username>/<repo>
    issue_term:   # < url \| pathname \| title \| ...>
  # Giscus options › https://giscus.app
  giscus:
    repo:             # <gh-username>/<repo>
    repo_id:
    category:
    category_id:
    mapping:          # optional, default to 'pathname'
    input_position:   # optional, default to 'bottom'
    lang:             # optional, default to the value of `site.lang`

# Self-hosted static assets, optional › https://github.com/cotes2020/chirpy-static-assets
assets:
  self_host:
    enabled:      # boolean, keep empty means false
    # specify the Jekyll environment, empty means both
    # only works if `assets.self_host.enabled` is 'true'
    env:          # [development\|production]

paginate: 10

# ------------ The following options are not recommended to be modified ------------------

kramdown:
  syntax_highlighter: rouge
  syntax_highlighter_opts:   # Rouge Options › https://github.com/jneen/rouge#full-options
    css_class: highlight
    # default_lang: console
    span:
      line_numbers: false
    block:
      line_numbers: true
      start_line: 1

collections:
  tabs:
    output: true
    sort_by: order

defaults:
  - scope:
      path: ''          # An empty string here means all files in the project
      type: posts
    values:
      layout: post
      comments: true    # Enable comments in posts.
      toc: true         # Display TOC column in posts.
      # DO NOT modify the following parameter unless you are confident enough
      # to update the code of all other post links in this project.
      permalink: /posts/:title/
  - scope:
      path: _drafts
    values:
      comments: false
  - scope:
      path: ''
      type: tabs             # see `site.collections`
    values:
      layout: page
      permalink: /:title/
  - scope:
      path: assets/img/favicons
    values:
      swcache: true
  - scope:
      path: assets/js/dist
    values:
      swcache: true

sass:
  style: compressed

compress_html:
  clippings: all
  comments: all
  endings: all
  profile: false
  blanklines: false
  ignore:
    envs: [development]

exclude:
  - '*.gem'
  - '*.gemspec'
  - tools
  - README.md
  - LICENSE
  - gulpfile.js
  - node_modules
  - package*.json

jekyll-archives:
  enabled: [categories, tags]
  layouts:
    category: category
    tag: tag
  permalinks:
    tag: /tags/:name/
    category: /categories/:name/

Please replace the settings with your own content according to the comments.

⚠️ Changes to _config.yml require restarting the local website to take effect!

Preview Website

After dependency installation is complete,

You can start the local site with bundle exec jekyll s:

Copy the URL http://127.0.0.1:4000/ and paste it into your browser to open.

Local Preview Successful!

With this Terminal open, the local website stays running. The Terminal continuously updates the website access logs, making debugging easier.

We can open a new Terminal to perform other subsequent operations.

Jekyll Directory Structure

Depending on the template, there may be different folders and configuration files. The article directory is at:

  • _posts/: Articles will be placed in this directory
    Article file naming convention: YYYYMMDD - article-file-name.md

  • assets/:
    Directory for website resources. Images used on the website or images within articles should be placed here.

Other directories like _includes, _layouts, _sites, _tabs… allow you to make advanced extensions and modifications.

Jekyll uses Liquid as its page template engine. The page templates are composed in a way similar to inheritance:

Users can freely customize pages. The engine first checks if the user has created a custom file for the page -> if not, it checks the template -> if not, it uses the original Jekyll style.

So we can easily customize any page by simply creating a file with the same name in the corresponding directory!

Create/Edit Article

  • We can first delete all example article files under the _posts/ directory.

Use Visual Code (free) or Typora (paid) to create a Markdown file. Here, we use Visual Code as an example:

  • Article file naming convention: YYYYMMDD - ArticleFileName .md

  • It is recommended to use English for file names (SEO optimization), as this name will become the URL path.

Article Content Top Meta:

1
2
3
4
5
6
7
8
9
---
layout: post
title:  "Hello"
description: ZhgChgLi's first article
date:   2022-07-16 10:03:36 +0800
categories: Jeklly Life
author: ZhgChgLi
tags: [ios]
---
  • layout: post

  • title: Article Title (og:title)

  • description: Article description (og:description)

  • date: Article publication date (cannot be in the future)

  • author: Author (meta:author)

  • tags: Tags (multiple allowed)

  • categories: Categories (single, separate subcategories with spaces, e.g., Jeklly Life means the Life folder under the Jeklly directory)

Please provide the Markdown paragraphs you want me to translate into English.

Write using Markdown format:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
layout: post
title:  "Hello Hello"
description: ZhgChgLi's first article
date:   2022-07-16 10:03:36 +0800
categories: Jeklly Life
author: ZhgChgLi
tags: [ios]
---
# HiHi!
Hello there
I am **ZhgChgLi**
Image:
![](/assets/post_images/DSC_2297.jpg){: lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJncmV5Ii8+PC9zdmc+" }
> _If you have any questions or feedback, feel free to [contact me](https://www.zhgchg.li/contact)._

Results:

⚠️ Article adjustments do not require restarting the website. After file changes, the content will render directly. If the changes do not appear after a while, the article format may be incorrect, causing rendering failure. You can check the Terminal for the cause.

Download Articles from Medium and Convert to Markdown for Jekyll

With basic Jekyll knowledge, let’s move forward by using the ZMediumToMarkdown tool to download existing articles from the Medium website and convert them into Markdown format to place in our Blog folder.

After changing directory to the blog folder, run the following command to download all articles from the user on Medium:

1
ZMediumToMarkdown -j Your Medium account

Waiting for all articles to finish downloading…

If you encounter any download issues or unexpected errors, feel free to contact me. This downloader was created by me ( development insights ) and can help you resolve problems quickly and directly.

After the download is complete, return to the local website to preview the results.

Done!! We have seamlessly imported the Medium article into Jekyll!

Please check if the article layout is broken or if any images are missing. If so, feel free to report to me for assistance with fixing it.

Upload Content to Repo

After confirming the local preview is fine, we will push the content to the Github repo.

Use the following Git commands in order:

1
2
3
git add .
git commit -m "update post"
git push

After the push is complete, return to GitHub to see the Actions running the CD again:

Wait about 5 minutes…

Deployment complete!

Initial Deployment and Setup Completed

After the initial deployment, update the following settings:

Otherwise, visiting the website will only show:

1
--- layout: home # Index page ---

After clicking “Save,” the changes won’t take effect immediately. You need to go back to the “Actions” page and wait for the deployment again.

After redeployment, you can successfully access the website:

Demo -> zhgchg.li

You now also have a free personal Jekyll Blog!!

About Deployment

Every time content is pushed to the repo, it triggers a redeployment. You must wait for the deployment to succeed before the changes take effect.

Bind a Custom Domain

If you don’t like the zhgchgli0718.github.io Github URL, you can buy a domain you like from Namecheap or register a free .tk domain using Dot.tk.

After purchasing the domain, enter the domain backend:

Add the following four Type A Record entries

1
2
3
4
A Record @ 185.199.108.153
A Record @ 185.199.109.153
A Record @ 185.199.110.153
A Record @ 185.199.111.153

After adding and configuring the domain backend, go back to Github Repo Settings:

Enter your domain in the Custom domain field, then click “Save.”

After the DNS is resolved, you can use zhgchg.li to replace the original github.io URL.

⚠️ DNS settings take at least 5 minutes and up to 72 hours to take effect. If verification keeps failing, please try again later.

Cloud-Based, Fully Automated Medium Synchronization Mechanism

Every time there is a new article, do you have to manually run ZMediumToMarkdown on your computer and then push it to the project? Finding it troublesome?

ZMediumToMarkdown also offers a convenient Github Action feature, allowing you to free your computer and automatically sync Medium articles to your website.

Go to the Actions settings of the Repo:

Click “New workflow”

Click “set up a workflow yourself”

  • Rename the file to: ZMediumToMarkdown.yml

  • The file content is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: ZMediumToMarkdown
on:
  workflow_dispatch:
  schedule:
    - cron: "10 1 15 * *" # At 01:10 on day-of-month 15.

jobs:
  ZMediumToMarkdown:
    runs-on: ubuntu-latest
    steps:
    - name: ZMediumToMarkdown Automatic Bot
      uses: ZhgChgLi/ZMediumToMarkdown@main
      with:
        command: '-j your Medium account'
  • cron: Set the execution schedule (weekly? monthly? daily?). Here, it is set to run automatically at 1:15 AM on the 15th of every month.

  • command: Enter your Medium account after -j

Click the top right “Start commit” -> “Commit new file”

Complete Github Action setup.

After creation, return to Actions and the ZMediumToMarkdown Action will appear.

Besides automatic execution at scheduled times, you can also manually trigger execution by following these steps:

Actions -> ZMediumToMarkdown -> Run workflow -> Run workflow.

After execution, ZMediumToMarkdown will directly run the script on Github Action’s machine to sync Medium articles to the Repo:

After running, it will also trigger a redeployment. Once redeployment is complete, the latest content will appear on the website. 🚀

No manual operation needed at all! This means you can continue updating your Medium articles in the future, and the script will automatically and thoughtfully sync the content from the cloud to your own website!

My Blog Repo

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


Buy me a beer

This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.