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.
点击这里查看本文章简体中文版本。
點擊這裡查看本文章正體中文版本。
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)
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
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
Environment Language: Ruby
Dependency Management Tools: RubyGems.org, Bundler
Static Site Generator: Jekyll (Based on Ruby)
Article Format: Markdown
Server: Github Page (Free, unlimited traffic/capacity static website server)
CI/CD: Github Action (Free 2,000 mins+/month)
Medium Article to Markdown Tool: ZMediumToMarkdown (Based on Ruby)
Version Control: Git
(Optional) Git GUI: Git Fork
(Optional) Domain Service: Namecheap
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:YYYY
–MM
–DD
-article-file-name
.mdassets/:
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:
YYYY
–MM
–DD
-ArticleFileName
.mdIt 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:
{: 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.
This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.