部落格架構轉換筆記 - 從Wordpress 到 Ghost
經過疲憊滿滿的外科實習後,回來看自己的部落格,覺得各種細項設定太過繁瑣,不利於維持心力更新文章。故嘗試從Wordpress轉換到Ghost

經過疲憊滿滿的外科實習後,回來看自己的部落格,才發現很久沒有更新內容了。不但有一些伺服器管理的細節忘記,也覺得以前為了維護網站運作順暢,載入快速的各種細項設定太過繁瑣,不利於在未來疲憊繁忙的從醫之路上,維持心力更新文章。
以前習慣的網站架設平台是Wordpress, 然而,自從學會Markdown語法後,發現從Markdown編輯器複製文字到Wordpress,總是容易產生問題,無法順暢的銜接整個寫作流程。而且,因為沒有時間精力設計重新打造整個佈景主題以及擴充套件,變成要用別人寫好的東西拼湊,導致整個站台會有部分細節無法最佳化,以及冗余的語法躲在各個角落影響站台運行。
基於以上想法,當換站之後,偷得一點閒暇,便研究起了之前聽說輕量快速的Ghost平台,做了一點功課之後,花了一兩天時間便把整個網站轉換過去了,對於結果十分滿意,以下是整個轉換過程的紀錄、兩個平台的優缺點比較,以及個人的一些評述。
Ghost vs Wordpress 使用比較評述
用了幾天之後,目前主要覺得Ghost的優點有:
-
使用node.js 撰寫,在效能配備較差的主機上較順暢
-
後台撰寫介面乾淨簡潔,完整支持Markdown
-
完整支持自訂Meta & Opengraph等標籤
-
可預覽分享至社群網站的樣貌(封面圖片、摘要)
-
支援Google AMP可自訂模版
不過Ghost畢竟才剛起步開發,相較於Wordpress仍有許多不足的,像是:
-
不支援分類,僅支援標籤
-
導覽列貌似只支援一層
-
缺少大量的外掛來擴充功能
-
頁面元件自訂性較仰賴程式碼修改,不若Wordpress小工具編輯方便
-
與其他平台流通性仍低,匯入不易
我在匯入Wordpress文章時,其實標籤跟分類全部跑掉,所以我必須一篇一篇手動檢視重新上標籤,這是一件惱人的事。另外Wordpress因為外掛數量極多,許多有用的功能都可以透過外掛添加,如圖片上傳自動壓縮優化、自動分享至多個社群平台等等,Ghost目前還缺少這個部分,不過App架構正在開發中,未來或許可以擴充更多功能。
依照設計哲學,Ghost是Wordpress開發者最初理念再度經簡化的產物,寫作、出版、分享,將這一整個流程簡化並順暢執行,構建簡潔不費力的個人部落格平台,是Ghost的目標,也確實完美達成。而Wordpress發展到現在,擁有更完善成熟的架構,擴充性強,也因此泛用性高,從個人部落格到商業形象網站都可以用,但是與Ghost相比在單純寫作出版的目標上略顯臃腫。
轉換事前準備
備份文章與圖片
首先當然要備份資料,主要是圖片跟文章。文章的話Ghost官方有提供一個Wordpress擴充套件可以轉出Ghost平台可以讀的JSON檔,但是看更新紀錄是只更新到五年前且僅在Wordpress舊版測試過,我自己在Wordpress 4.8 無法成功轉出。後來我在Github找到一個npm工具wp2ghost,他是吃wordpress 本身可以匯出的XML備份檔然後在轉成JSON檔。
Wordpress [工具]-> [匯出] -> wordpress-XXXXX.xml
下載wp2ghost.js, 然後依照說明npm 安裝後使用
node bin/wp2ghost.js
> ghost.json
在轉出以前,可以利用編輯器的搜尋取代功能,先改XML內隨著網站變動的內容。像因為我從wordpress轉換到ghost, 圖片的路徑位址會跑掉,所以我必須尋找取代如下:
https://example.com/wp-content/uploads ->
https://example.com/content/images
PS: wordpress路徑
|-wordpress
|--wp-admin
|--wp-content
|---uploads //所有上傳的圖片資料
|--index.php
|--etc......
Ghost如果放在/var/www/ghost 底下
|-ghost
|--content
|---apps
|---data
|---images //Ghost所有上傳的圖片儲存處
|---logs
|---thmes
|--system
|--etc......
然而,我在terminal輸入指令轉換時碰到問題,wp2ghost.js無法成功轉換,幸虧有輸出在哪一行出現問題,所以就開Sublime text 起來看XML檔案,一開始看不懂錯誤訊息指定的那一行出什麼問題,結果比對作者在原始碼資料夾內附的範例檔案後,發現可能是舊的wordpress版本輸出的XML檔案每篇文章都會空一行間隔
<item>
......文章資料內容
</item>
///舊版在這邊有空一行,新版沒有
<item>
......文章資料內容
</item>
所以我用Sublime text 搜尋取代全部加入空行後,果然wp2ghost就成功轉過去了,剩下不成功的都是特殊字元,自行增刪處理就可以了。
圖片的話就是依照上面路徑所列,自行下載圖片後在重新上傳到Ghost相對應的資料夾內即可。
留言回覆轉移
這部份比較麻煩,因為Ghost目前沒有自己的留言系統,未來也可能不想要有(我懷疑有多少人會用Wordpress內建的留言系統嗎XD),所以官方建議是先在Wordpress啟用Disqus以後,在轉移原本的Wordpress留言到Disqus內,然後在新的Ghost站台加入Disqus留言系統,原本的留言就會回復了,所以如果本來就有在用Disqus留言系統,那基本上是小菜一碟。
網址轉移(?)
或許有人想問網站文章產生的網址結構,從舊的wordpress搬到ghost可以保留嗎?很抱歉,我這部份是直接放棄(反正我的讀者群比較少,泣~)再加上wordpress原先產生的網址是中文照搬,可是ghost的網址系統會自動把中文轉成羅馬拼音,又加深了保留連結的困難度,所以除非有更好的辦法,否則建議有龐大讀者群不想失去太多的話不要輕易搬移。
Ghost安裝
Ghost 依照官方文件要求的環境如下:
- Ubuntu 16.04
- MySQL
- NGINX (minimum of 1.9.5 for SSL)
- Systemd
- Node v6 installed via NodeSource
- At least 1GB memory (swap can be used)
- A non-root user for running ghost commands
我的網站是放在Google Cloud platform, 因為各家VPS同樣需求的操作應該都大同小異,所以這邊僅簡單紀錄一下:
1. 製作snapshot,包含原先wordpress舊網站的系統
2. 刪除VM個體
3. 重新製作一個VM個體,根據需求選擇配備地點線路
4. Linux開機,ssh連入, 新增一個non-root user(不用特地命名ghost)
5. 安裝nginx, mysql-server, 設定好root帳號密碼
6. curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash //加入node套件源
7. sudo apt-get install -y nodejs //安裝Node.js
8. sudo npm i -g ghost-cli //安裝Ghost
9. sudo mkdir -p /var/www/ghost
10. sudo chown ghost:ghost /var/www/ghost //官方文件寫要使用自己創的帳號,但是我實際跑起來會出現問題......用Ghost反而沒問題
11. cd /var/www/ghost && ghost install //開始安裝囉
安裝過程中會問你Mysql的資料庫帳號密碼,跟wordpress一樣,另外也整合了Let's encrypt 的功能,可以直接幫你設定好憑證跟nginx讓網站使用https,如果要套用https的話一開始的網址就記得要填https開頭喔。
Ghost基本設定
安裝好以後,就可以從網址連進去跑跑看了,一開始看到的應該是Ghost預設的佈景Casper以及一些預設範例內容,可以連到https://example.com/ghost進行後台設定。創建帳號時輸入Gmail地址好像會自動讀取Gmail的大頭貼。
Ghost的能設定的項目相較wordpress簡單,我大概設定了以下幾項:
- Labs -> Deletes all content 先把原先的範例刪除
- Labs -> Import content 把準備好從wordpress轉出的json檔輸進來
- General -> 輸入網站標題、描述,上傳Publication icon(favicon)
- Design -> 輸入導覽列項目及連結,以及上傳佈景主題啟用
其他的就依照需求填入了。
Ghost進階修改
從結構上來說,其實Ghost的佈景主題元件構成很像wordpress, 不過也可以理解,因為起頭者是wordpress的前執行長。
可以參考Ghost的官方Documentation, 結構從命名上很好理解,舉例來說,post.hbs 就是程式會依照其輸出每篇文章的模版,所以要加Disqus 留言系統的話就是要放在這裡。要在sidebar裡添加小工具的話就是找sidebar.hbs,依此類推。
加入Disqus以及Facebook留言系統
以下是我放在post.hbs裡facebook 留言系統以及Disqus系統的代碼片段
//以下這段放在前面任意處即可
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = 'XXXXXX'; //請自行找facebook留言社群外掛輸出
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
//以下這段要放在{{#post}} - {{/post}}裡面
{{content}} //這是Ghost用來輸出文章內文的標記,所以之後就是可以放留言的地方
<div class="fb-comments" data-href="{{@blog.url}}{{url}}" data-width="100%" data-numposts="5"></div>
<div class="comments-area">
<div class="comments-inside">
<div id="disqus_thread"></div>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus</a>.</noscript>
</div><!-- .comments-inside -->
</div><!-- .comments-area -->
<script type="text/javascript">
var disqus_shortname = 'example'; //example請代換成自己的
if ( typeof disqus_shortname !== 'undefined' ) {
var disqus_title = '{{title}}';
var disqus_identifier = '{{comment_id}}';
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
}
</script>
在每篇文章開頭都再輸出一次封面圖片
我的佈景主題在文章總覽輸出過封面圖片以後,內文就沒有再呈現了。我想要再呈現一次,於是修改了一下:
{{#if feature_image}}
<p><img src="{{img_url feature_image}}" alt=""></p>
{{/if}}
{{content}} //文章內文輸出標記,所以前面就是有封面圖片
從語法可以看到,Ghost真的很像Wordpress, 有簡單的代碼標記構成元素如特色圖片、文章內文、文章標題、作者、日期等,也有判斷目前頁面是首頁、文章內文、固定頁面的語法。詳細情形可以參考Ghost官方說明
Nginx 設定檔優化
我是參考這一篇教學,主要分成幾項:
- Gzip壓縮
- 靜態檔案快取
- https連線優化
首先先在nginx.conf定義cache zone
# 定義一個cache zone 在 /tmp/nginx-cache
proxy_cache_path /tmp/nginx-cache levels=1:2 keys_zone=ghost:100m inactive=24h max_size=1g;
# 根據自己的配備網站調整
proxy_cache_key "$scheme$host$request_uri";
# 萬一Ghost出現錯誤本身沒有在執行,呼叫cache
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_http_version 1.1;
#在Header裡面顯示除錯資訊
add_header X-Cache $upstream_cache_status;
然後再去修改安裝Ghost時幫你新增的 /etc/nginx/sites-available/example.com-ssl.conf
server {
listen 443 ssl http2 fastopen=3 reuseport;
listen [::]:443 ssl http2 fastopen=3 reuseport;
server_name example.com;
root /var/www/ghost/system/nginx-root;
ssl_certificate /etc/letsencrypt/example.com/fullchain.cer;
ssl_certificate_key /etc/letsencrypt/example.com/example.com.key;
include /etc/nginx/snippets/ssl-params.conf;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 靜態檔案快取
location ~* \.(jpg|jpeg|svg|png|gif|ico|css|js|eot|woff)$ {
proxy_cache ghost;
proxy_cache_valid 200 30d;
proxy_cache_valid 404 10m;
proxy_ignore_headers "Cache-Control";
access_log off;
expires 30d;
proxy_pass http://proxy_app;
}
# API
location ~ ^/(?:ghost/api) {
expires 0;
add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";
proxy_pass http://proxy_app;
}
# 管理介面
location ~ ^/(?:ghost) {
expires 0;
add_header Cache-Control "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0";
proxy_pass http://proxy_app;
}
# 頁面
location / {
proxy_cache ghost;
proxy_cache_valid 200 1d;
proxy_cache_valid 404 10m;
expires 10m;
proxy_pass http://proxy_app;
}
location ~ /.well-known {
allow all;
}
client_max_body_size 50m;
}
upstream proxy_app {
server 127.0.0.1:2368;
}
主要就是將一些靜態資源以及常被瀏覽的網頁設到nginx cache裡面並設定比較大的過期時間。