#77 搭建 Ghost 全流程
2023 年不懂代码搭建 Ghost 是什么体验
你好啊,这里是许久不见的 Design Scenes,别来无恙。
在重新开始写 newsletter 前,一件确定的事便是考虑更换平台。竹白的基础功能有些波动,最主要的是产品几乎不再更新,这在做产品的眼中基本已经☠️了。剩下的邮件服务平台很多都是以盈利目的使用,自身大多需要月供一定的费用。最终在 Substack 和自己搭建 Ghost 之间抉择。Telegram 上的 @informavore 告诉我可以试试 Fly.io + Ghost 组合,被我第一时间本能地拒绝了。但由于垂涎 Ghost 已久,本着「反正建不好有 Substack 兜底」,还是着手试了试,果然不会一帆风顺。
本文较长,可以访问原文使用目录功能查看。希望对像我一样少有代码接触也想建立自己的 Ghost 的读者有所帮助。
初次接触 Ghost
高中时我对 CMS 博客理解基本就是 WordPress,用过一小段时间 Typecho,后面就对 Hugo、Next.js 之类的没什么印象。时过境迁,CLI 中几行命令即可迅速部署好一个博客。推荐下面这篇文章中的流程。
这步需要注意的是 Fly.io 的服务器节点区域选择,就近最好。依梯子那边线路延迟来看,我这里选择的是香港节点(hkg),更多区域节点详见文档。
Fly.io 可以随时对配置升级,免费配置中只有 256MB 内存有点捉急。Ghost 官方推荐是 1GB,网站在刚搭建完基本没做什么时内存占用都在200MB 左右,偶尔还会出现 502 错误,Fly.io 官方也向我发送过崩溃报错邮件……所以我升级到了 512MB,算是稳定下来。
按照文章顺便配置了自己的域名,没有乱七八糟额外收费 Fly.io 这点也很不错。改域名后需要重新部署。
Ghost 后台很简洁,大致填写了一遍表单后,剩下的就是配置邮件和主题样式。
Mailgun 的小心思
Mailgun 是 Ghost 官方那个推荐的发件系统,后台也集成了相关功能,简单配置发送域名和 API 就可以使用——但没想象的那么简单。
首先 Mailgun 的注册就把我卡住了,账号需要邮箱和手机号验证。虽然支持 +86 短信发送,但是目前为止一封验证短信也没收到。之后搜到了 Her Blue 这篇文章。
最后我也是邮件客服,由于时区等了 10 几个小时后才解决。
Mailgun 套餐改过很多次,2023 年版本是:首先 Mailgun 有一定的免费配额,也就是 Free Plan,每月免费 5000 封邮件,看着很吸引人。但是默认只能使用 sandbox 前缀域名,而且只能发送给 Authorized Recipients(认证的接收者),且最多 5 个人。Mailgun 解释说这可以用做测试。完全体需要掏出信用卡升级。
然而点击 upgrade 后,却发现和 Ghost 官方说的不一样。
上来就 35 美元每月直接差点劝退我,我又找了几篇文章才发现,很多小型博主使用的是 Flex Plan,我擦亮眼镜也没发现这 8 个字母在哪。又是一番搜索,发现了这个帖子。
原来 Mailgun 早就取消了直接升级 Flex Plan 的渠道,需要添加信用卡后,升级到 Foundation (Trial) 后再 downgrade 即可在下个月变为 Flex Plan。高,真的高。
接下来就是按照官方流程,添加新的自定义域名,生成新的 Mailgun API keys,填写到 Ghost 后台。这段流程中,无论是 DNS 配置还是 Ghost 调用 Mailgun 都有一定的延迟,所以测试邮件时别像我一样心急……
Ghost 会员邮件配置
后台配置好 Mailgun 后文章可正常推送到目标邮箱。但是前台会员注册/登录的 maigc link 验证邮件提示无法发送。回到 Ghost 安装的文章,原因是 fly.toml
一些字段没配置。
mail__from = "noreply@example.com"
mail__options__auth__pass = "<YourMailgunPassword>"
mail__options__auth__user = "postmaster@example.com"
mail__options__host = "smtp.mailgun.org"
mail__options__port = "465"
mail__transport = "SMTP"
在 Ghost 的 FAQ 页面可以看到,Ghost 的发信分为两种:
- 1 对 1 的事务邮件,使用 SMTP,一般用于会员注册/登录;
- 1 对多的批量邮件,使用 Mailgun 的 Bulk API,一般用于群发 newsletter;
上面 toml 文件中的用户和密码使用的不是 Mailgun 账号密码,而是 Sending > Overview > SMTP credentials
中的账号密码。要查看密码,点击 Reset Password 才可以……这个交互逻辑也是闻所未闻。听说这还是 Mailgun 新版后台界面,到底改了什么😅。
这样 SMTP 部分便部署完毕, 过程中也加深了对配置文件 fly.toml
的认识 (毕竟是没用过 docker 的人🥹)。
Ghost 配置之路
不同于 Framer 设计师能亲自上手造车,Ghost 主题需要「老一套」代码编译。好在结构和其他博客系统差不多,官方文档覆盖比较全面,Handlebars 门槛也不高,多翻看几次也能稍微理解一些表面。
本地部署
我使用的是古典主义 Ghost 后台上传压缩包方式更换主题,为了改主题方便一点需要本地部署一个 Ghost,然后直接修改内部主题文件。
这个很简单,安装 Ghost-CLI 后,cd 到新建好的目录直接安装。很快,都不用配置数据库。
主题本地化
挑选一个合适的主题很重要,Ghost 有很多优雅但又价格不菲的主题,但囊中羞涩。好在 Ghost 官方有一些不错的免费主题,便选择了 Journal 这一款。
Ghost 支持文本和一部分 handlebars 表达式的本地化。
主题根目录中 locales 文件夹里面的 json 文件便是主题中的可定制翻译文本。有最好,没有的话可能就得像我一样挨个找出来新建……还要把对应的文本用 {{t }}
方式包起来。
之后在 Ghost 后台设置 > General > Publication Language
中填上 zhcn 即可生效。但有一些组件 Ghost 尚不支持定制,详见文档。这些文档都存在于核心服务中,还没有单拿出来定制。
比较遗憾的是 Ghost 无法深度定制邮件模板 CSS 样式,5.65.1 版本目前可以在 /versions/5.65.1/node_modules/@tryghost/email-service/lib/email-templates/template.hbs
找到模板,如果要更改其样式可在上层文件夹里的 ../partials/styles.hbs
修改,但也只能硬改,无法有效地编译。
另外在 /versions/5.65.1/core/server/services/mail/templates/
中能发现很多邮件模板,/versions/5.65.1/core/server/services/newsletters/emails/verify-email.js
中能看到验证邮件的字段。理论上都可以更改,但是一旦更新版本这些改动又有概率重置,得不偿失。我只在本地测试了一下,没有上线改动。
改动这些文件需要执行 ghost restart
生效。
添加文章目录
Ghost 有一个栏目介绍自家系统的拓展用法。我比较纳闷为啥有些功能为啥不直接做进系统里……想起之前文档里有说过,如果要支持其他 ESP 请自己提 pull request,看来其他功能也是如此。
目前先打算添加一个比较基础的目录功能,使用的如下文章方法。
过程中碰到了两个点:
一是CSS 调用问题:主题调用的是 assets/built/screen.css
文件,而不是 assets/css/screen.css
,前者需要使用 Yarn 和 Gulp 编译后者生成。为了省事我直接把样式写字了 hbs 文件头部。
二是布局问题:按照文章的代码会导致无法浮动固定在指定位置。
原文涉及侧栏的样式代码:
@media (min-width: 1300px) {
.gh-sidebar {
position: absolute;
top: 0;
bottom: 0;
margin-top: 4vmin;
grid-column: wide-start / main-start; /* Place the TOC to the left of the content */
}
.gh-toc {
position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
top: 4vmin;
}
}
如果给 .gh-sidebar
设置 position: sticky;
时就会因 css grid 而影响文章的首段行高,后来去查阅了一下 MDN 发现是容器高度问题,.gh-sidebar
在主题默认样式中 height:max-content;
内容多高容器就多高,当然无法滚动固定。更换为 height: auto;
即可使容器高度和文章一致,solved。为了不妨碍主题样式,我直接重新起了个 class 名。
后面如果使顶部导航栏浮动的话,相对高度改变,.gh-toc
也要改一下数值,整体变为:
@media (min-width: 1300px) {
.article-sidebar {
position: absolute;
top: 0;
bottom: 0;
margin-top: 4vmin;
height: auto;
grid-column: wide-start / main-start; /* Place the TOC to the left of the content */
}
.gh-toc {
position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
top: 16vmin;
}
}
Ghost 这个 Do more 栏目还有实践,如果之后代码使用很多的话也考虑优化一下。阅读进度条先不打算加,毕竟不加 100% 不会减慢访问速度。
目前的主题已经传到了 GitHub 上,可自行参考。
加速 JsDelivr 和 Gravatar
Ghost 很多组件 js 托管在 JsDelivr,虽然现在也能访问,但是速度较慢,我这测试不用梯子时,有的 js 载入 10s 有余。
在 /versions/[当前版本号]/core/shared/config/
文件中,default 配置了 Ghost 许多核心功能。如上图为 JsDelivr 托管的一些文件。可以把这些文件都下载下来放到自己服务器上,也可以把所有 cdn.jsdelivr.net
替换成 fastly.jsdelivr.net
。两种方法可分别参考下面两篇文章。
我使用的是前者方法,速度更有保障,但每次更新版本可能需要覆盖更新。将这些 js 文件下载下来后(除了 editor,会导致后台编辑器不可用),如何传到 Fly.io 上又是一难题——所有文章都没写过。
这个时候就该说明一下为什么推荐第一篇文章安装 Ghost 了,原文第 6 节提到了如何备份和恢复 Ghost,其中使用了 SFTP,查询关键词,果然是这个。
照葫芦画瓢,原文有些方法不适用,但还是成功进入到 VM 文件内部:
cd [安装路径]
flyctl auth login
先登录,然后 shell 命令中,可以多种方法进入自己 VM 的存储卷,比如应用名、区域或组织名等等,详见帮助说明。
flyctl ssh sftp shell -r [地区代码]
flyctl ssh sftp shell -a [应用名]
flyctl ssh sftp shell -o [组织名]
进入后会显示红色的 >> 符号,然后就可以执行各种命令了。
cd #改变目录
ls #列出当前文件列表
put #上传
get #下载
chmod #权限设置
然后打开 fly.toml
,按照文章把上传后的 js 路径写到 [env]
里去。注意这里要加上双引号,以符合 Fly.io 这边语法。
到这里可以顺手把 gravatar 地址改了,官方源不用梯子无论如何也访问不了,裂图效果很糟心。这里使用的是这篇文章提到的镜像源:
[env]
gravatar__url = "https://use.sevencdn.com/avatar/{hash}?s={size}&r={rating}&d={_default}"
最后重新部署一遍就可以了。
客服邮件备置
Ghost 的 Portal settings设置中可配置客服邮箱,为了隐私性和统一性,我打算再开个域名邮箱。这一看才发现 QQ 的免费域名邮箱早已不再提供,被企业微信融合。鉴于目前是早期试运行阶段,付费域名邮箱也并非首选。考察一番后,发现飞书在提供免费域名邮箱,只需要新创建一个企业,未认证的也可以。
创建后在企业后台 > 产品设置 > 邮件
中直接添加域名,跟着流程走就可以。最后在成员列表设置邮箱即可在飞书收信。如果更方便点,还可以在设置里自动转发到常用邮箱。
这里我使用 hi@fenx.work
作为客服邮箱。
客服邮箱配置好后,我在网站页面底部加上了「反馈」入口,它的本质是一段 mailto 链接:
mailto:hi@fenx.work?subject=[网站反馈]&body=请写下反馈页面地址和具体问题
其中 subject 和 body 作用是默认在标题和正文加上一段文字,辅助用户更好地反馈。
更近一步优化的话,验证邮件也可以帮忙打开,Growth Design 有一期讲的是这事,利用邮箱的筛选功能配置网址直接找到验证邮件。
其他网站设置
Design Scenes 更换到塔状 logo,以塔的各种形式重新诠释。使用了 Newsagent 字体。颜色也比之前更亮了一点,以匹配网页上 sRGB 的低亮度。
剩下就是一些页面文案撰写工作,花了一些时间。
哦对了,还有邮件批量导入。Ghost 提供了 csv 模板,也是比较方便的。
部署 umami 分析
如今已不是Google Analytics 横行的时代,大量隐私优先的统计工具出现。我在之前 newsletter 里提到过 creativerly 的文章:
大部分平台依然需要月供十几美元来支付「隐私费」,使用较为广泛且有开源的 Plausible 和 umami 成为半决赛选手。Plausible 使用 docker 设计搭建,无论在 DigitalOcean Droplet 还是 Fly.io 上都需要额外再掏钱。所以我选择了可以直接部署在 Vercel 的 umami。
网上有很多相关文章,基本流程大同小异,建立 supabase 数据库,Fork 官方库后到 Vercel 配置环境变量,部署上线。推荐这篇文章是它帮我解决了一个啼笑皆非的错误。
我在最后一步部署时,Vercel 总是提示 unable to connect to the database 报错,因为要填写的 string 一共也没几个所以很纳闷……后来看到这篇文章时突然察觉到,是不是我填写 DATABASE_URL
时密码写错了!原本要求的是 [YOUR-PASSWORD]
全部替换为密码,但是我保留了两边方括号……呃。
更改分析域名与脚本名
在查找资料时发现有人提到 *.vercel.app 域名国内访问问题,无 cookie 统计已经不是很准确,我想尽量排除一些干扰要素。于是到 Vercel 项目设置 (Project Settings) 页面,加上了自己的二级域名。这下域名那边的 DNS 解析已经两页之多了😅。
Umami 现在默认使用的是 script.js,虽说不像之前 umami.js 那么容易被去广告插件识别,但说不准什么机制会有干扰。Umami 提供了更改 js 名这一服务,直接在 Vercel 项目设置 Environment Variables 中添加一条 TRACKER_SCRIPT_NAME
即可。命名规则如下:
TRACKER_SCRIPT_NAME= # Unset. By default, the script is fetched from: /script.js
TRACKER_SCRIPT_NAME=custom # Fetched from: /custom
TRACKER_SCRIPT_NAME=custom.js # Fetched from: /custom.js
至此,跟踪代码已经自定义为:
<script async src="https://[自定义二级域名]/[自定义脚本名]" data-website-id="[网站id]"></script>
Umami 不使用 cookie,不搜集带有个人标识的数据,虽然不准确(比如我自己的活动数据也会计入),但这样你的数据隐私不会被跨站利用。Plausible 写过一篇统计原理可以看看:
另外 CloudFlare 也有一款免费的网站分析产品,同样是隐私优先,但省事许多,之后看看是否有机会尝试一下。
结语
至此 Ghost 平台首发版本初步搭建完毕,本文也将作为第一次群发邮件测试。7k 字内容不知道在邮箱哪里会被截断……实际工作量远没有文章中那样谈笑风生,期间至少有 3 次我觉得还是去 Substack 吧。但没到山穷水尽那一刻总觉得不是放弃的时机,磕磕绊绊也算前进了。
另外在本次流程中没有使用带有 LLM 的 AGI 对话工具,依然全程使用 Google 搜索。我之后在 Poe 和 Bing 试了下,不是出现「幻觉」就是重复信息,帮助不多。
如果你觉得文章对你有些帮助,可以请我的猫吃罐头 ↓