Home Painless Migration from Medium to Self-Hosted Website
Post
Cancel

Painless Migration from Medium to Self-Hosted Website

Painless Migration from Medium to Self-Hosted Website

Migrating Medium content to Github Pages (with Jekyll/Chirpy)

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

zhgchg.li

Background

In the fourth year of running Medium, I have accumulated over 65 articles, 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 things. Before that, I had tried self-hosting Wordpress, but I spent all my time on setting up the environment, styles, and plugins, never feeling satisfied with the adjustments. After setting it up, I found it loaded too slowly, the reading experience was poor, and the backend writing interface was not user-friendly, so I stopped updating it.

As I wrote more articles on Medium and accumulated some traffic and followers, I started wanting to control these achievements myself, rather than being controlled by a third-party platform (e.g Medium shutting down and losing all my work). So, I began looking for a second backup website two years ago. I continued to run Medium but also synchronized the content to a website I could control. The solution I found at the time was — Google Site, but honestly, it could only be used as a personal “portal site.” The article writing interface was limited in functionality, and I couldn’t really transfer all my work there.

In the end, I returned to self-hosting, but this time using a static website instead of a dynamic one (e.g. Wordpress). Static websites support fewer features, but all I needed was a writing function and a clean, smooth, customizable browsing experience, nothing else!

The workflow for a static website is: write the article locally in Markdown format, then convert it to a static webpage using a static site engine and upload it to the server, and it’s done. Static webpages provide a fast browsing experience!

Writing in Markdown format allows the article to be compatible with more platforms. If you’re not used to it, you can find online or offline Markdown writing tools, and the experience is just like writing directly on Medium!

In summary, this solution meets my needs for a smooth browsing experience and a convenient writing interface.

Results

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

zhgchg.li

  • Supports customizable display styles
  • Supports customizable page adjustments (e.g. inserting ads, js widgets)
  • Supports custom pages
  • Supports custom domains
  • Static pages load quickly, providing a good browsing experience
  • Uses Git version control, preserving all historical versions of articles
  • Fully automated scheduled synchronization of Medium articles to the website

Environment and Tools

Install Ruby

Here, I will 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, it is recommended to use rbenv to install another Ruby to separate it from the system’s built-in version. Enter the above command in Terminal to install rbenv.

1
rbenv init

Enter the above command in Terminal to initialize rbenv.

  • Close & reopen Terminal.

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

Success!

Use rbenv to install Ruby

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’s built-in version to the rbenv version.

Enter rbenv versions in Terminal to check the current settings:

Enter ruby -v in Terminal to check the current Ruby version, and gem -v to check the current RubyGems status:

*After installing Ruby, RubyGems should also be installed.

Success!

Install Jekyll & Bundler & ZMediumToMarkdown

1
gem install jekyll bundler ZMediumToMarkdown

Enter the above command in Terminal to install Jekyll & Bundler & ZMediumToMarkdown.

Done!

Create Jekyll Blog from Template

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

The installation method generally uses gem-based themes, some repos provide a Fork method for installation, and some even offer a one-click installation method. In short, the installation method may vary for each template, so please refer to the template’s tutorial for usage.

Additionally, note that since we are deploying to Github Pages, according to the official documentation, not all templates are applicable.

Chirpy Template

Here, I will use the template Chirpy as an example, which I adopted for my Blog. This template provides the simplest one-click installation method and can be used directly.

Other templates rarely offer similar one-click installation. If you are not familiar with Jekyll or GitHub Pages, using this template is a better way to get started. I will update the article with other template installation methods in the future.

Additionally, you can find templates on GitHub that can be directly forked (e.g., al-folio) and used directly. If not, you will need to manually install the template and research how to set up GitHub Pages deployment. I tried this briefly but was not successful. I will update the article with my findings in the future.

Create Git Repo from Git Template

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

  • Repository name: GithubUsername/OrganizationName.github.io (Make sure to use this format)
  • Make sure to select “Public” for the Repo

Click “Create repository from template”

Complete the Repo creation.

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 from each other, loves movies/TV shows/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 to 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 according to the comments.

⚠️ _config.yml needs to be restarted after any adjustments to apply the changes.

Preview the Website

After the dependencies are installed,

you can start the local website with bundle exec jekyll s:

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

Local preview successful!

As long as this Terminal is open, the local website will be running. The Terminal will continuously update the website access logs, which is convenient for debugging.

We can open a new Terminal for other subsequent operations.

Jekyll Directory Structure

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

  • _posts/: Articles will be placed in this directory Article file naming convention: YYYYMMDD - article-file-name .md
  • assets/: Website resource directory, 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 customizations.

Jekyll uses Liquid as the page template engine. The page template is composed in a manner similar to inheritance:

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

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

Create/Edit Articles

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

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

  • Article file naming convention: YYYYMMDD - article-file-name .md
  • It is recommended to use English for the file name (SEO optimization), as this name will be 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: Jekyll Life
author: ZhgChgLi
tags: [ios]
---
  • layout: post
  • title: Article title (og:title)
  • description: Article description (og:description)
  • date: Article publication time (cannot be in the future)
  • author: Author (meta:author)
  • tags: Tags (can be multiple)
  • categories: Categories (single, use space to separate subcategories Jekyll Life -> Life directory under Jekyll)

Article Content:

Write using Markdown format:

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

Results:

⚠️ Adjusting the article does not require restarting the website. The file changes will be rendered and displayed directly. If the modified content does not appear after a while, it may be due to an error in the article format causing rendering failure. You can check the Terminal for the reason.

Download articles from Medium and convert them to Markdown for Jekyll

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

cd to the blog directory and run the following command to download all articles from the specified Medium user:

1
ZMediumToMarkdown -j your Medium account

Wait for all articles to download…

If you encounter any download issues or unexpected errors, feel free to contact me. This downloader was written by me (development insights), and I can help you solve the problem quickly and directly.

After the download is complete, you can preview the results on the local website.

Done!! We have seamlessly imported Medium articles into Jekyll!

You can check if the articles are formatted correctly and if there are any missing images. If there are any issues, feel free to report them to me for assistance in fixing them.

Upload content to Repo

After confirming that the local preview content is correct, we need to push the content to the Github Repo.

Use the following Git commands in sequence:

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

After pushing, go back to Github, and you will see that Actions are running CD:

Wait about 5 minutes…

Deployment completed!

Initial deployment settings

After the initial deployment, you need to change the following settings:

Otherwise, when you visit the website, you will only see:

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

After clicking “Save,” it will not take effect immediately. You need to go back to the “Actions” page and wait for the deployment again.

After redeployment is complete, you can successfully access the website:

Demo -> zhgchg.li

Now you also have a free Jekyll personal blog!!

About deployment

Every time you push content to the Repo, it will trigger a redeployment. You need to wait for the deployment to succeed for the changes to take effect.

Bind a custom domain

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

After purchasing the domain, go to the domain backend:

Add the following four Type A Record records

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 the settings in the domain backend, go back to Github Repo Settings:

In the Custom domain section, enter your domain, and then click “Save”.

After the DNS is connected, you can replace the original github.io address with zhgchg.li.

⚠️ DNS settings take at least 5 minutes ~ 72 hours to take effect. If it cannot be verified, please try again later.

Cloud, Fully Automated Medium Synchronization Mechanism

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

ZMediumToMarkdown actually also provides a convenient Github Action feature that allows you to free up your computer and automatically synchronize Medium articles to your website.

Go to the Actions settings of the Repo:

Click “New workflow”

Click “set up a workflow yourself”

  • Change the file name 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 cycle (weekly? monthly? daily?). Here it is set to automatically execute at 1:15 AM on the 15th of each month.
  • command: Enter your Medium account after -j

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

Complete the creation of Github Action.

After creation, go back to Actions and you will see the ZMediumToMarkdown Action.

In addition to automatic execution at the scheduled time, you can also manually trigger execution by following these steps:

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

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

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

No manual operation required! This means you can continue to update Medium articles in the future, and the script will automatically help you 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 article was first published in Traditional Chinese on Medium ➡️ View Here


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

iOS: Insuring Your Multilingual Strings!

App Store Connect API Now Supports Reading and Managing Customer Reviews