無痛轉移 Medium 到自架網站
將 Medium 內容搬遷至 Github Pages (with Jekyll/Chirpy)
背景
經營 Medium 的第四年,已累積超過 65 篇文章,將近 1000+ 小時的時間心血;當初會選擇 Medium 的原因是簡單方便,可以很好的把心思放在撰寫文章上,不需要去管其他的事;在此之前曾經嘗試過自架 Wordpress,但都把心思放在弄環境、樣式、Plguin 這些事情上,感覺怎麼調整都不滿意,調整好後又發現載入太慢、閱讀體驗不佳、後台撰寫文章介面也不夠人性化,然後就沒怎麼在更新了。
隨著在 Medium 撰寫的文章越來越多、累積了一些流量與追蹤者後,又開始想自己掌握著這些成果,而不是被第三方平台掌控 (e.g Medium 關站心血全沒),所以從前年開始就一直在尋覓第二備份網站,會持續經營 Medium 但也會同步把內容發佈到自己能掌控的網站上;當時找到的解決方案是 — Google Site 但老實說只能當成個人「入口網站」使用,文章撰寫界面功能有限,無法真的把所有文章心血搬過去。
最終還是走回自架的的道路,不同的是採用的並非動態網站(e.g. wordpress),而是靜態網站;相較之下能支援的功能較少,但是我要的就是文章撰寫功能跟簡潔流暢可客製化的瀏覽體驗,其他都不需要!
靜態網站的工作流程是:在本地使用 Markdown 格式撰寫好文章,然後將其透過靜態網站引擎轉換為 靜態網頁 上傳到伺服器,即完成;靜態網頁,瀏覽體驗快速!
使用 Markdown 格式寫作,可以讓文章兼容更多不同平台;如不習慣,也可以找線上或線下的 Markdown 撰寫工具,體驗就跟直接在 Medium 撰寫一樣!。
綜合以上,這個方案可以達成我希望流暢的瀏覽體驗及方便的撰寫界面兩個維度的需求。
成果
- 支援客製化顯示樣式
- 支援客製化頁面調整 (e.g. 插入廣告、js widget)
- 支援自訂頁面
- 支援自訂域名
- 靜態化頁面載入快速、瀏覽體驗佳
- 使用 Git 版本控制,文章所有的歷史版本都能保留恢復
- 全自動定時自動同步 Medium 文章到網站
2025/01/18 Update 🎉🎉🎉
環境及工具
- 環境語言:Ruby
- 依賴管理工具:RubyGems.org、Bundler
- 靜態網站引擎:Jekyll (Based on Ruby)
- 文章格式:Markdown
- 伺服器:Github Page (免費、無限流量/容量 靜態網站伺服器)
- CI/CD:Github Action (免費 2,000 mins+/月)
- Medium 文章轉換 Markdown 工具:ZMediumToMarkdown (Based on Ruby)
- 版本控制:Git
- (可選) Git GUI:Git Fork
- (可選) 網域服務:Namecheap
安裝 Ruby
這邊只以我的環境為例,其他作業系統版本請 Google 如何安裝 Ruby。
- macOS Monterey 12.1
- rbenv
- ruby 2.6.5
安裝 Brew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
在 Terminal 輸入以上指令安裝 Brew。
安裝 rbenv
brew install rbenv ruby-build
MacOS 雖自帶 Ruby 但建議使用 rbenv 安裝另一個 Ruby 與系統自帶的區隔開來,在 Terminal 輸入以上指令安裝 rbenv。
rbenv init
在 Terminal 輸入以上指令初始化 rbenv
- 關閉&重新打開 Terminal。
在 Terminal 輸入rbenv
檢查是否安裝成功!
成功!
使用 rbenv 安裝 Ruby
rbenv install 2.6.5
在 Terminal 輸入以上指令安裝 Ruby 2.6.5 版本。
rbenv global 2.6.5
在 Terminal 輸入以上指令將 Terminal 所使用的 Ruby 版本從系統自帶的切換到 rbenv 的版本。
在 Terminal 輸入 rbenv versions
查看當前設定:
在 Terminal 輸入 ruby -v
查看當前 Ruby、gem -v
查看當前 RubyGems 狀況:
*Ruby 安裝完後理應也安裝好 RubyGems了。
成功!
安裝 Jekyll & Bundler & ZMediumToMarkdown
gem install jekyll bundler ZMediumToMarkdown
在 Terminal 輸入以上指令安裝 Jekyll & Bundler & ZMediumToMarkdown。
完成!
從模版建立 Jekyll Blog
預設的 Jekyll Blog 樣式非常簡潔,我們可以從以下網站找到自己喜歡的樣式並套用:
安裝方式一般使用 gem-based themes,也有的 Repo 提供 Fork 方式安裝;甚至是提供直接一鍵安裝方式;總之每個模板的安裝方式可能有所不同,請參閱模板的教學使用。
另外要注意,因我們要部署到 Github Pages 上,依據官方文件所說並非所有模板都能適用。
Chirpy 模版
這邊就以我 Blog 採用的模版 Chirpy 為示範,此模版提供最傻瓜的一鍵安裝方式,可以直接使用。
其他模版比較少有提供類似的一鍵安裝,在不熟悉 Jeklly、Github Pages 的情況下先使用此模版是比較好入門的方式;日後有機會再更新文章講其他的模版安裝方式。
另外在 Github 上找可以直接 Fork 的模版也可以(e.g. al-folio)直接使用,如果都不是,是需要自己手動安裝的模版就要自行研究如何設定 Github Pages 部署,這邊我稍微研究了一下沒成功,待日後有結果再回來文章補充分享。
從 Git Template 建立 Git Repo
https://github.com/cotes2020/chirpy-starter/generate
- Repository name:
Github帳號/組織名稱.github.io
(務必使用這個格式) - 務必選擇「Public」公開 Repo
點擊「Create repository from template」
完成 Repo 建立。
Git Clone 專案
git clone git@github.com:zhgchgli0718/zhgchgli0718.github.io.git
git clone 剛剛建立的 Repo。
執行 bundle
安裝依賴:
執行 bundle lock — add-platform x86_64-linux
鎖定版本
修改網站設定
打開 _config.yml
設定檔案進行設定:
# 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 求知若渴 教學相長 更愛電影/美劇/西音/運動/生活
# 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/
請依照註解將設定替換成您的內容。
⚠️ _config.yml 有調整都需要重新啟動本地網站!才會套用效果
預覽網站
依賴安裝完成後,
可以下 bundle exec jekyll s
啟動本地網站:
複製其中的網址 http://127.0.0.1:4000/
貼到瀏覽器打開
本地預覽成功!
此 Terminal 開著,本地網站就開著,Terminal 會持續更新網站存取紀錄,方便我們除錯。
我們可以再開一個新的 Termnial 做後續的其他操作。
Jeklly 目錄結構
依照樣板不同可能會有不同的資料夾跟設定檔案,文章目錄在:
- _posts/:文章會放在這個目錄下
文章檔案命名規則:YYYY
–MM
–DD
-文章檔案名稱
.md - assets/:
網站資源目錄,網站用圖片或文章內的圖片都要放置於此
其他目錄 _incloudes、_layouts、_sites、_tabs… 都可讓你做進階的擴充修改。
Jeklly 使用 Liquid 做為頁面模板引擎,頁面模板是類似繼承方式組成:
使用者可自由客製化頁面,引擎會先看使用者有沒有建立對應頁面的客製化檔案 -> 如果沒有則看樣板有沒有 -> 如果沒有就用原始的 Jekyll 樣式呈現。
所以我們可以很輕易地對任何頁面做客製化,只需要在相對應的目錄建立一樣的檔案名稱即可!
建立/編輯文章
- 我們可以先把
_posts/
目錄下的範例文章檔案全數刪除。
使用 Visual Code (免費) 或 Typora (付費) 建立 Markdown 檔案,這邊以 Visual Code 為例:
- 文章檔案命名規則:
YYYY
–MM
–DD
-文章檔案名稱
.md - 建議以英文為檔案名稱 (SEO 最佳化),這個名稱就會是網址的路徑
文章內容頂部 Meta:
---
layout: post
title: "安安"
description: ZhgChgLi 的第一篇文章
date: 2022-07-16 10:03:36 +0800
categories: Jeklly Life
author: ZhgChgLi
tags: [ios]
---
- layout: post
- title: 文章標題 (og:title)
- description: 文章描述 (og:description)
- date: 文章發表時間 (不可以是未來)
- author: 作者 (meta:author)
- tags: 標籤 (可多個)
- categories: 分類 (單個,用空格區分子母分類
Jeklly Life
-> Jeklly 目錄下的 Life 目錄)
文章內容:
使用 Markdown 格式撰寫:
---
layout: post
title: "安安"
description: ZhgChgLi 的第一篇文章
date: 2022-07-16 10:03:36 +0800
categories: Jeklly Life
author: ZhgChgLi
tags: [ios]
---
# HiHi!
你好啊
我是 **ZhgChgLi**
圖片:

> _有任何問題及指教歡迎 [與我聯絡](https://www.zhgchg.li/contact) 。_
成果:
⚠️ 文章調整不需要重新啟動網站,檔案變更後會直接渲染顯示,如果過一陣子都沒出現修改內容,可能是文章內容格式有誤導致渲染失敗,可回到 Terminal 查看原因。
從 Medium 下載文章並轉成 Markdown 放入 Jekyll
有了基本的 Jekyll 知識後我們繼續向前邁進,使用 ZMediumToMarkdown 工具將現有在 Medium 網站上的文章下載並轉換成 Markdwon 格式放到我們的 Blog 資料夾中。
cd 到 blog 目錄下後,下以下指令將 Medium 上的該使用者所有文章都下載下來:
ZMediumToMarkdown -j 你的 Meidum 帳號
等待所有文章下載完成。。。
下載完成後,回到本地網站就能預覽成果囉。
完成!!我們已經無痛地將 Medium 文章導入到 Jekyll 囉!
可以檢查一下文章有無跑版、圖片有無缺失,如果有一樣歡迎回報給我協助修復。
上傳內容到 Repo
本地預覽內容沒問題後,我們就要將內容 Push 到 Github Repo 囉。
依序使用以下 Git 指令操作:
git add .
git commit -m "update post"
git push
Push 完成後回到 Github 上,可以看到 Actions 有 CD 再跑:
約等待 5 分鐘…
部署完成!
首次部署完成設定
首次部署完成要更改以下設定:
否則前往網站只會出現:
--- layout: home # Index page ---
「Save」後不會馬上生效,要回到「Actions」頁面再一次重新等待部署。
重新部署完成後,就能成功進入網站了:
Demo -> zhgchg.li
現在你也擁有一個免費的 Jekyll 個人 Blog 囉!!
關於部署
每次 Push 內容到 Repo 都會觸發重新部署,要等到部署成功,更改才會真正生效。
綁定自訂網域
如果不喜歡 zhgchgli0718.github.io Github 網址,可以從 Namecheap 購買您喜歡的網域或是使用 Dot.tk 註冊免費 .tk 結尾的網域。
購買網域後進到網域後台:
加上以下四個 Type A Record 紀錄
A Record @ 185.199.108.153
A Record @ 185.199.109.153
A Record @ 185.199.110.153
A Record @ 185.199.111.153
網域後台新增好設定後,回到 Github Repo Settings:
在 Custom domain 的地方填入你的網域,然後按「Save」。
等待 DNS 通了之後,就可以用 zhgchg.li 取代掉原本的 github.io 網址。
⚠️ DNS 設定至少需要 5 分鐘 ~ 72 小時才會生效,如果一直無法認證過;請稍後再試。
雲端、全自動 Medium 同步機制
每次有新文章都要用電腦手動跑 ZMediumToMarkdown 然後再 Push 到專案,嫌麻煩嗎?
ZMediumToMarkdown 其實還提供貼心的 Github Action 功能,可以讓你解放電腦、全自動幫你同步 Medium 文章到你的網站上。
前往 Repo 的 Actions 設定:
點擊「New workflow」
點擊「set up a workflow yourself」
- 檔案名稱修改為:
ZMediumToMarkdown.yml
- 檔案內容如下:
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 你的 Meidum 帳號'
- cron: 設定執行週期 (每週?每個月?每天?),這邊是設定每個月 15 號凌晨 1:15 會自動執行
- command: 填入你的 Medium 帳號在 -j 後面
點擊右上方「Start commit」->「Commit new file」
完成 Github Action 建立。
建立完成後回到 Actions 就會出現 ZMediumToMarkdown Action。
除了時間到自動執行外還可以依照以下步驟,手動觸發執行:
Actions -> ZMediumToMarkdown -> Run workflow -> Run workflow。
執行後,ZMediumToMarkdown 就會直接透過 Github Action 的機器跑同步 Medium 文章到 Repo 的腳本:
跑完後同樣會觸發重新部署,重新部署完成後到網站就會出現最新的內容了。🚀
完全無需人工操作!也就是說未來你還是可以繼續更新 Medium 文章,腳本都會貼心地自動幫你從雲端同步內容到你自己的網站上!
我的 Blog Repo
有任何問題及指教歡迎與我聯絡。