这篇文章写给「想有一个自己的站点」的人。它不假设你已经懂运维,但也不打算哄你——里面有一些必须接受的麻烦事,我会明确标出来。
整篇的结构是按真实顺序走的:先想清楚要什么,再买机器、装环境、选数据库、选程序、改主题、处理图片和评论,最后解决性能和备份。每一步我都会给出「为什么是它,而不是另一个」的理由,以及我实际踩过的坑。
全部内容基于一个已经跑起来的真实站点:Typecho 1.3 + PostgreSQL 15 + 自研主题,两个 Docker 容器,一台 2 核 2G 的云服务器。
序:为什么还要自建
先说结论:自建博客在流量上没有任何优势,在便利性上也是完败的。 你在公众号、知乎或者任何内容平台上写东西,都有人帮你搞定服务器、CDN、评论、反垃圾、SEO 和备份。自建意味着这些全部由你负责。
那为什么还要做?
我自己的理由有三个,按重要性排序:
第一,内容的所有权。 平台的规则会变,推荐算法会变,甚至平台本身会关门。你写的东西放在别人的数据库里,它的命运就不在你手上。自建之后,最坏的情况是我欠费停机,但数据在我自己的硬盘上,随时可以搬到任何地方。
第二,形态的自由。 公众号的文章不能有自定义排版,不能嵌交互式的内容,不能做「一篇长文配一个可以点的表格」。自建站想做多奇怪都可以——这是我做主题的动力来源。
第三,技术上的确定性。 平台出问题时你只能等,自建站出问题时你能查。这种「问题的边界是可掌控的」感觉,对我来说是重要的。
如果你的动机里没有一条跟这三条沾边,那我的建议是别自建,去平台上写字。这不是劝退,是节省双方时间。
一、先想清楚你需要什么
买服务器之前,先回答四个问题。这四个答案会直接决定后面每一个技术选择。
① 你打算写多少? 一年二十篇和一年两百篇,是完全不同的负荷。前者用最便宜的方案都绰绰有余,后者才需要考虑缓存和 CDN。
② 你需要数据库吗? 如果你的文章是纯静态的、不需要评论、不需要搜索,那静态站点生成器加对象存储是最优解——快、便宜、几乎不会坏。一旦你需要评论、搜索、标签聚合、动态的阅读统计,就必须有服务端和数据库。 这一步分岔之后,路线完全不同。
③ 你会不会改主题? 如果不会,选一个生态大、模板多的程序(比如 WordPress)。如果会,甚至以此为目的,那就挑一个代码干净、结构简单的(比如 Typecho)。
④ 你的预算是多少? 我给一个参考:一台 2 核 2G 的入门云服务器,一年一百多到三百块;域名一年几十块。这是自建博客的全部固定成本。 再便宜方案就是静态托管,可能免费。
把这四个答案写下来,后面的选择就没有纠结了。
二、服务器与域名
2.1 机器怎么选
我的判断标准简单粗暴:2 核 2G 起步,系统盘 40G。 理由如下:
- 1 核 1G 不是不能用,但一旦你要跑数据库 + Web 服务 + 偶尔编译个东西,内存会不够。PHP 的进程会吃掉比你想像中多的内存,PostgreSQL 默认配置也会吃。省下的那点钱,换来的是一到晚上就跑满的负载曲线。
- CPU 不是瓶颈。 博客是典型的 IO 和内存型负载,2 核完全够撑住日均几千的访问量。
- 磁盘要留余量。 系统加软件大概占 3G,剩下的是图片。按一张图 200KB 算,30G 能放十五万张。够你写很久。
系统选 Debian 或者 Ubuntu LTS。不要选那些「一键部署面板」的镜像,除非你确实需要面板——它们通常会在你不知情的情况下占用端口和资源。
2.2 安全的第一小时
新机器到手,先做三件事,顺序不能变:
① 改 SSH 端口并禁止密码登录。 只保留密钥登录。网络上扫描 22 端口的机器人比你想象的勤奋得多,机器开出来几分钟内日志里就会有登录尝试。
② 建一个普通用户。 日常操作不要用 root。真要提权就 sudo,让每一次特权操作都留下痕迹。
③ 开防火墙,只放必要的端口。 HTTP、HTTPS,加上你改过的 SSH 端口。数据库端口绝对不要暴露到公网,这一点后面在容器化部署时会天然满足。
# 一个够用的防火墙基线(以 ufw 为例)
ufw default deny incoming
ufw default allow outgoing
ufw allow 443/tcp
ufw allow 80/tcp
ufw allow 2222/tcp # 改过的 SSH 端口
ufw enable2.3 域名与解析
域名选什么都行,注意两点:后缀不要选太小众的(部分邮箱和浏览器对它不友好),不要用连字符(口头传播时是灾难)。
解析只需要一条 A 记录指向服务器 IP。如果你以后想换服务器,把 TTL 设短一点(比如 300 秒)会方便很多。
HTTPS 现在是必需项,不是加分项。 没有 HTTPS 的站点,浏览器会在地址栏里直接标记「不安全」,而且很多功能(比如剪贴板 API、地理位置)会直接不可用。好在现在申请证书是免费的,用 Let's Encrypt 加自动续期,装上之后基本不用再管。
域名和服务器的钱,是自建博客里唯一不能省的部分。省下来的时间成本远高于那几十块。
三、容器化部署:把环境关进盒子里
我强烈建议用 Docker。理由不是「流行」,而是三个具体的问题它能解决:
- PHP 版本和扩展的纠缠。 Typecho 1.3 在 PHP 7.4 和 8.x 上的行为不完全一样,某些扩展的版本也会影响。用官方镜像可以固定住这套组合,不会因为某天系统自动更新了一个包而崩掉。
- 迁移的代价。 换服务器时,如果环境是手工装的,你得把每一步重来一遍,还要祈祷没漏掉什么。容器化之后,
docker compose up -d就完了。 - 数据库的隔离。 数据库在独立的容器里,只和 Web 容器在同一个内部网络,天生不对外暴露端口。
3.1 一份够用的编排文件
services:
app:
image: joyqi/typecho:1.3.0-php7.4-apache
container_name: typecho-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
volumes:
- ./usr/uploads:/app/usr/uploads
db:
image: postgres:15-alpine
container_name: typecho-db
restart: unless-stopped
environment:
POSTGRES_USER: typecho
POSTGRES_PASSWORD: change-me
POSTGRES_DB: typecho
healthcheck:
test: ["CMD-SHELL", "pg_isready -U typecho"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:几个关键点:
- 端口映射写成
127.0.0.1:8080:80。 前面的127.0.0.1很关键,它意味着这个端口只对本机开放,外网扫不到。外面套一层反向代理(Nginx 或 Caddy)来做 HTTPS 和转发。 - 数据库不映射端口。 完全不写
ports,它就只能被同网络的容器访问。 depends_on配condition: service_healthy。 只写depends_on是不够的——它只保证「容器启动了」,不保证「数据库能接受连接了」。Web 容器可能先于数据库就绪而启动失败,表现为首次部署时白屏。- 上传目录挂出来。 图片是数据,不能放在容器里。容器重建时里面的文件会丢。
3.2 反向代理和 HTTPS
外层用 Caddy 最省事,它申请和续期证书是全自动的:
example.com {
encode gzip
reverse_proxy 127.0.0.1:8080
}三行。证书、续期、HTTP 到 HTTPS 的跳转,全都不用管。如果习惯 Nginx,也能做,只是多写二三十行配置和一条续期定时任务。
容器的价值不在于「先进」,而在于它把「环境」这件事变成了一个可以版本控制、可以整体复制的文件。部署的确定性,是长期维护一个站点最重要的资产。
四、数据库选型:从 SQLite 到 PostgreSQL
Typecho 支持三种数据库:SQLite、MySQL / MariaDB、PostgreSQL。这个选择比大多数人以为的重要。
4.1 三种选择的真实差别
| 数据库 | 优点 | 代价 | 适合谁 |
|---|---|---|---|
| SQLite | 零配置,单文件,备份就是复制文件 | 并发写能力弱,一次只能一个写操作 | 单机、流量小、无所谓 |
| MySQL / MariaDB | 生态最大,资料最多,几乎所有教程都基于它 | 需要单独容器,参数调优项多 | 想抄教程、不折腾 |
| PostgreSQL | 类型系统严格,JSON 支持好,全文检索强 | 大小写敏感等「严格的代价」,中文资料相对少 | 要写插件、要跑复杂查询 |
我最初选了 SQLite,图的是「零配置」。真正让我换掉它的是一个很实际的场景:后台保存文章的时候,如果同时有人提交评论,写入会排队。 平时无感,但一旦遇到(比如你正在后台批量导入,前台读者正在留言),保存按钮会卡住几秒。
这不是 SQLite 的缺陷,是它的设计取舍。它是嵌入式数据库,不是并发服务器。当你的站点有「多个写入源」时,它就不再合适了。
4.2 PostgreSQL 的两个特性值得注意
第一,大小写敏感。 这是最容易踩的坑。PostgreSQL 里不带引号的标识符会被折叠成小写,而带引号的会原样保留。Typecho 建表时用的是驼峰列名(authorId、commentsNum),也就是带引号存下来的。
于是你手写这么一句就会报错:
-- 报错:column "commentsnum" does not exist
SELECT commentsNum FROM typecho_contents;正确写法是加引号:
SELECT "commentsNum" FROM typecho_contents;但更好的是根本不要手写这套 SQL。 Typecho 的查询构建器会自动处理引号,用 $db->select(...) 这类方法就永远不会遇到这个问题。所以规则很简单:能用构建器就别拼裸 SQL。
第二,取主键的方式不同。 没有 lastInsertId() 这种通用方法,因为 PG 的做法是让插入语句自己带回来。Typecho 的适配器在遇到 INSERT 时会自动追加一句 RETURNING,而 Db::query() 对 INSERT 动作会直接把主键返回给你:
$cid = (int)$db->query($db->insert('table.contents')->rows($row));一行。比「先插入再查一遍」既快又安全。
4.3 备份:数据库和文件要分开做
这是很多人第一次做备份会漏掉的地方。一个完整的备份包含两部分:
- 数据库:文章、评论、分类、设置。
- 上传目录:图片、附件。
只备份数据库,恢复之后所有图片都是裂的。只备份文件,恢复之后文章全没了。
数据库的备份用 pg_dump,配合定时任务:
# 每天凌晨 3 点导出一次,保留 14 天
0 3 * * * docker exec typecho-db pg_dump -U typecho typecho | gzip > /backup/db-$(date +\%F).sql.gz
0 4 * * * find /backup -name 'db-*.sql.gz' -mtime +14 -delete上传目录直接打包,或者用同步工具推到对象存储。
并且一定要验证备份能恢复。 没有验证过的备份等于没有备份——这是我用血换来的教训,后面第十二节会细说。
五、Typecho 上手
5.1 为什么是 Typecho
在几个主流选择里,Typecho 的特点是代码量小。它的核心代码是可以一晚上读完的量级,主题模板只有十几个文件,数据表一共十来张。对于「我想看懂它、并且改它」的人来说,这个属性比功能多寡重要得多。
代价是生态小。现成的插件和主题比 WordPress 少一个数量级,很多功能得自己写。如果你的目标是「装完就能用,插件一键搞定」,Typecho 会让你失望。
5.2 安装时唯一需要注意的事
安装向导本身很顺,只有一步要留心:表前缀。
默认前缀是 typecho_,很多人直接跳过。但如果你以后打算在同一个库上跑第二个站点,或者想区分「核心表」和「插件表」,就该在这里换一个自己好认的前缀,比如 tc_ 或者 typeo_。
安装完之后,想办法把 install.php 之类的一次性文件处理掉,别留在服务器上。
5.3 内容是用什么写的
Typecho 的编辑器支持两种模式:Markdown 和纯 HTML。切换的时候,它会在正文的最开头插入一个魔术标记来记住你的选择:
<!--markdown-->渲染的时候,程序读正文的第一个字符,看是不是这个标记,是就走 Markdown 解析,不是就只做最简单的段落包裹。
这件事在正常使用中是透明的——你在后台选了什么,它就写什么。但当你用脚本批量导入文章时,这个标记必须自己写上,否则你会得到一篇把所有 # 和 ** 都原样显示出来的文章,看起来像是「Markdown 功能坏了」。
我在这上面浪费过半小时。
5.4 内容渲染链路
如果你要改主题,值得先把这条链路记住:
- 数据库里存的是原始文本(可能带 Markdown 标记)。
- 模板里调用内容输出方法,程序剥掉魔术标记、按模式解析成 HTML。
- 主题可以通过插件钩子接管输出,做二次加工——比如把连续的图片自动拼成网格、给只有一张图的段落加上特殊类名。
- 最后输出到页面。
第二步和第三步的区别很重要:如果你在插件里拿到的内容是「原始文本」,那它一定是没渲染过的 Markdown,里面的 #、**、图片语法都还在。想拿它做摘要,就必须自己清洗。
六、主题开发
6.1 模板文件的组织
Typecho 的主题就是一堆 PHP 文件,其中几个名字是保留的,程序按约定调用:
| 文件名 | 用途 |
|---|---|
index.php | 首页 / 列表页 |
post.php | 文章详情页 |
page.php | 独立页面 |
archive.php | 归档页 |
header.php / footer.php | 公共头尾 |
comments.php | 评论区域 |
functions.php | 主题的函数库、选项面板定义、钩子注册 |
404.php | 404 页 |
先把这几个之间的关系理顺,再动手。 我见过太多人从改 CSS 开始,改到一半才发现某个区块是 sidebar.php 里输出的,跟自己改的文件根本不是一个。
6.2 从结构开始,不要从颜色开始
一个具体的建议:先用边框把每个区块画出来,把布局调对,最后再上颜色和圆角。
原因是颜色会骗人。一个配色好看但结构松散的页面,在视觉上会显得还行;而一个结构严谨但配色朴素的页面,改颜色是几分钟的事。先做颜色,等于把最难的部分留到了最后,而且还被好看的配色掩盖着看不见。
6.3 内容与布局的边界
主题代码里最容易失控的是内容样式和布局样式的混写。比如给正文里的图片写上固定宽度——这在单栏布局里没问题,一旦换成两栏或者把内容嵌进卡片里,图片就会溢出。
规则应该是:内容样式只管「内容长什么样」,布局样式只管「它在多宽的框里」。 一旦有了容器查询,这条规则就能真正落地,因为组件可以自己感知容器宽度,不再需要外层布局给它传信号。
6.4 关于「抄」
抄设计不丢人,但要抄对。
- 抄信息层级——哪些信息放大、哪些缩小、哪些隐藏。这是设计里真正有价值的部分。
- 抄交互反馈——hover 时发生什么、点击后去哪里。这决定了页面的手感。
- 别抄具体数值——别人的 680px 列宽是他那套字体和行高下的最优解,照搬到你这里可能就偏窄或偏宽。数值要在你自己的页面上量出来。
我调正文列宽调了三次:760 太宽,每行字数超过 45 之后阅读时容易串行;620 又太窄,频繁换行会打断节奏;最后停在 680,行高定在 1.9,每行落在 35 到 40 字之间,这个区间读长文最舒服。
排版的参数不是审美问题,是阅读问题。每行字数是可以用「读一屏会不会串行」来检验的客观指标。
七、让文章读起来舒服
功能性做完之后,「读起来舒服」是唯一拉开差距的东西。而它由几个非常具体的参数决定。
7.1 列宽和行高
这两个参数必须一起调,单独调任何一个都会出问题。
| 正文列宽 | 行高 | 每行字数(中文) | 体感 |
|---|---|---|---|
| 760 | 1.6 | 约 42 | 偏宽,长文容易串行 |
| 720 | 1.8 | 约 40 | 尚可 |
| 680 | 1.9 | 约 38 | 长文最舒服的区间 |
| 620 | 1.9 | 约 34 | 偏窄,换行频繁打断节奏 |
每行 35 到 40 个汉字是长文阅读的甜区。 这个区间之外,读者的眼睛在换行时容易跳错行。
行高单独说一句:1.6 是「能看」,1.9 是「能读」。中文的笔画密度比拉丁字母高,同样的行高下中文看起来更挤,所以中文正文的行高普遍要比英文大 0.1 到 0.3。
7.2 段距是比行高更重要的参数
这一点很多人忽略。
行高管的是段落内部的呼吸感,段距管的是段落之间的切分感。如果段距太小,整篇文章会糊成一大块,读者找不到「换了个意思」的位置。
我的取值是段距 ≈ 行高。行高 1.9 配 18px 字号等于 34.2px,段距给 28px 略小一点,视觉上段落边界清晰但又不至于断裂。
但这里有个 CSS 层面的坑必须知道:相邻块级元素的垂直外边距会折叠(margin collapse)。
意思是,如果你给段落设了 margin-bottom: 28px,又给标题设了 margin-top: 24px,它们之间不会是 52px,而是 max(28, 24) = 28px。你想让「段落 → 小标题」这个位置更空一点,光调段距是没用的——因为标题的 margin-top 还压在那里,取的是两者的最大值。
我一开始就是把段距从 16 调到 28,然后发现「段 → 段」确实拉开了 12px,「段 → 标题」只动了 4px(因为原本是 max(16,24)=24,现在是 max(28,24)=28)。要让那一处也拉开,得单独调标题的 margin-top。
调间距的时候,别只盯着你改的那个值。浏览器最终用的是折叠后的结果,用开发者工具量实际像素,不要用推算。
7.3 中英文混排
中文文档里混英文单词时,如果两边不加空格,观感会明显变差。这件事严格来说应该由排版引擎处理,但 CSS 里有个近似方案:
.article-content {
word-break: normal;
overflow-wrap: break-word;
text-spacing-trim: space-all;
}text-spacing-trim 目前支持度还在推进,但加上没有坏处。更重要的是在写作时就自己加空格——「使用 Docker 部署」而不是「使用Docker部署」。这是最容易做到、效果最明显的一条。
7.4 深色模式
深色模式不是把背景变黑就完了,有几个必须处理的地方:
- 纯黑背景上的纯白文字会产生光晕(halation),长时间阅读会累。用
#F0F0F0左右的浅灰而不是#FFF。 - 纯黑背景不适合长时间阅读,用
#121212这种接近黑的深灰会舒服得多。 - 图片需要降一点亮度,否则在深色页面里会像一块发光的板子:
filter: brightness(.9)。 - 阴影要反过来用,深色下靠的是更深的黑和更亮的边框来区分层级,而不是投影。
还有一个实践细节:别只做「跟随系统」和「手动切换」二选一,两个都做。 默认跟随系统,用户手动点过之后就固定下来,记在本地存储里。
7.5 阅读偏好这种小功能
字号加减、行距切换、专注模式——这些功能不改变内容,但会让读者觉得「这个站点考虑过我」。
实现上没什么难度,难度在别做成默认打扰。我见过不少站点把这排按钮做得又大又亮,占据正文上方一整条,每次进来都在眼前晃。它应该在视线之外,但伸手可及。
八、图片与灯箱
图片是博客里最重的资源,也是最容易做砸的地方。
8.1 三种图片处理路径
| 方式 | 优点 | 缺点 |
|---|---|---|
| 上传到站点 | 完全自控,尺寸可以读取,灯箱和相册都能用 | 占服务器空间和带宽 |
| 外链图床 | 不占自己空间 | 依赖第三方,可能失效被墙,拿不到尺寸 |
| 对象存储 + CDN | 快、便宜、稳定 | 需要额外配置 |
最关键的选择依据是「能不能读到图片的宽高」。
如果你的图片存在自己服务器上,程序可以在输出 HTML 时读一下文件,给 <img> 补上 width 和 height 属性。这一步能彻底消除图片加载前的布局抖动——浏览器提前知道要占多大位置,就不会在图片加载完成时把下面的内容猛地推下去。
而外链图拿不到这个信息。结果是页面一边加载一边跳,读者的阅读位置不断被打断。这个体验的差距,比图片清晰度的差距大得多。
8.2 九宫格:让相册不成为插件
我原本打算为「图片集」做一个独立的内容类型,后来发现没必要。
思路是这样的:如果一篇文章里连续出现多张图片,程序在输出时自动把它们归组,渲染成一个网格。 于是「相册」不再是一种特殊内容,它只是「一篇图片很多的普通文章」,不需要额外的后台界面,不需要额外的数据表。
这套逻辑的判定条件可以很简单:
- 提取正文里所有图片的地址。
- 图片数量达到阈值(比如 2 张)的文章,视为「图集」,收进相册页。
- 相册页用文章的第一张图作为封面。
好处是内容生产路径极短:写文章、贴图、发布,相册页自动更新。坏处是「图片多」和「是图集」这两件事不能完全划等号——一篇技术文章里如果碰巧贴了五张截图,它也会出现在相册里。
解决办法是给个后门:文章可以设置一个「封面」自定义字段,设了的就一定是相册;没设的按图片数量判断。这样默认省事,需要精确控制时也有手段。
8.3 灯箱
灯箱的实现要点不多,但这几个容易漏:
- 图片归组。 同一篇文章(或同一个网格)的图片应该能左右切换,而不是关掉再点开下一张。
- 预加载相邻图。 打开第 3 张时,第 2 张和第 4 张应该已经在缓存里了,否则左右切换会有明显的空白期。
- 键盘操作。 左右方向键切图,Esc 关闭。这不是锦上添花,是长图浏览的基本效率。
- 关闭时的滚动位置。 关掉灯箱后页面应该停在原来的位置,不要跳回顶部。
- 别阻止页面滚动之后忘了恢复。 这是最常见的 bug:灯箱打开时给
body加了overflow: hidden,关闭时忘了去掉,页面就再也滚不动了。
九、评论系统
评论是自建博客里最值得自己做的部分,也是最容易被低估的部分。
9.1 为什么不用第三方
第三方评论(各种 embed 式的服务)的好处是省事,代价是:
- 数据不在你手上。 服务关了,你的评论也没了。这样的事发生过不止一次。
- 加载慢。 一个跨域的 iframe 加几个外部脚本,在移动网络上常常是首屏最慢的资源。
- 样式不可控。 它长得永远不像你的站点。
- 隐私问题。 读者的信息经过第三方。
如果你的站点已经决定自建,评论再交给第三方,等于最关键的用户数据又交出去了。这和「内容所有权」的初衷是矛盾的。
9.2 自己写要注意的事
第一,反垃圾必须自己做。 一个很容易犯的错误是:以为后台开了「反垃圾保护」就万事大吉。实际上这类开关通常只挂在原生表单的提交路径上,你自己写的接口完全不在它的管辖范围内。
结果就是你用脚本连发二十条测试评论,一条都没被拦。
需要自己补的至少有两层:频率限制(同一个 IP 在短时间内只能发几条)和内容过滤(关键词、链接数量、是否含大量重复字符)。
第二,HTML 白名单。 允许评论带 HTML 是危险的,必须过白名单:只放行最基本的几个标签,属性也只留安全的那些。这件事没有捷径,必须显式列举。
第三,评论数和分页。 文章列表上的评论数、评论的分页、分页之后 rel 链接的处理——这些细节不做,功能上不影响,但用起来会觉得「差一点」。
第四,回复的层级。 支持两级就够了。三级以上的缩进在窄屏上会把内容挤成一条竖线,而且实际使用中多级回复的阅读体验都不好。
评论系统的价值不在于功能多,而在于它是不是你的。数据在自己手里,你才有资格谈「要不要做点什么」。
十、性能:能省下的每一毫秒
博客的性能优化有一条原则:先量,再改,改完再量。 凭感觉优化,十次里有八次会把时间花在根本不影响体感的地方。
10.1 先看三个指标
| 指标 | 含义 | 目标 |
|---|---|---|
| 首字节时间 | 服务器处理请求的时间 | < 200ms |
| 首次内容绘制 | 用户看到第一块内容的时间 | < 1.5s |
| 布局偏移 | 页面元素加载中移动的程度 | < 0.1 |
第一个指标管服务器,后两个管前端。先修服务器。 如果首字节要两秒,前端做再多优化也没用。
10.2 服务器端的三个动作
① 打开 opcache。 PHP 的开销里,编译脚本占的比例很大,而一个博客的 PHP 文件在运行期间几乎不变。opcache 把它编译后的结果缓存在内存里,收益立竿见影,通常是两位数百分比。它基本不需要调参,打开就有用。
② 给数据库加索引。 检查你常用的查询条件有没有索引:按时间排序的列表、按分类筛选、按标签聚合。Typecho 的核心表默认已经建了必要的索引,但你自己写的插件表往往一个都没有。
③ 合并查询。 首页一次要展示十几篇文章,如果每篇都去查一次分类、查一次阅读数、查一次图片,就是几十次数据库往返。把它们合并成一次查询,或者在最开始就把需要的关联数据一次性取出来。
这一点对「插件取数据」尤其重要。我做的专栏插件最早是按篇查的,二十篇文章就是二十次查询;改成一次取回全部关联关系、在 PHP 里用数组归并之后,首页的数据库查询次数从二十几次降到三次。
10.3 前端的三个动作
① 图片给宽高。 前面说过,这一步直接消灭布局抖动。它比压缩图片大小更重要——一张 300KB 但尺寸已知的图,体验好于一张 100KB 但会让页面跳动的图。
② 懒加载。 首屏之外的图片全部加上懒加载。但首屏的第一张图不要懒加载,否则会增加它自己的出现时间,得不偿失。
③ 避免无 Pjax 的全页跳转。 如果站点是传统的多页跳转,每次点文章都会重新加载一遍完整的 HTML、CSS 和 JS。Pjax 的思路是只替换变化的部分(内容区),其余保持不变。实现得当的话,跳转会快到几乎感觉不到。
但 Pjax 有个必须处理的坑:跳转之后原来的脚本要重新初始化。 目录高亮、图片灯箱、点赞按钮——这些绑定在旧 DOM 上的事件在内容替换后就失效了。解决办法是在替换完成后重新跑一遍初始化,并且注意别重复绑定。这一块很容易积累出「点第二次才有反应」这类诡异 bug。
10.4 缓存
如果流量不大,不要急着上缓存层。 一个配置错误的缓存会让你的站点出现「登录了还是看到旧页面」「刚发的文章不出现」这类问题,排查成本远高于它省下的那点资源。
真正需要缓存的顺序是:
- PHP 的 opcache(必须,零风险)
- 浏览器端缓存静态资源(CSS / JS / 字体,加长缓存时间加版本号)
- 反向代理的静态文件直出(让 Nginx / Caddy 直接返回静态文件,不经过 PHP)
- 页面级缓存(最后才考虑)
前三步都是零风险的,第 4 步才需要小心。
10.5 关于 CDN
CDN 的收益取决于你的服务器在哪里、读者在哪里。
- 服务器和读者在同一个地区,CDN 的收益有限。
- 服务器在海外、读者在国内,CDN 几乎是必需的,否则访问会慢到不可用。
- 但要注意备案问题:如果你的域名没有备案,国内的 CDN 服务用不了。
另外一个容易被忽略的点:CDN 缓存的是静态资源还是 HTML? 如果它把 HTML 也缓存了,那你的新文章可能别人看不到。给 HTML 设置较短的缓存时间或者不缓存,是最安全的做法。
十一、SEO 与可发现性
我不打算讲「怎么讨好搜索引擎」,只讲怎么不让你的内容被埋没——前者是技巧,后者是义务。
11.1 必须做对的基础项
| 项目 | 要求 | 常见错误 |
|---|---|---|
| 每页标题 | 文章标题 + 站点名 | 所有页面标题一样 |
| 描述 | 每篇一个自然的摘要 | 复制正文前 150 字,或空缺 |
| 永久链接 | 稳定、可读 | 中途改结构导致旧链接全断 |
| 站点地图 | 输出全部内容 | 没有,或者不更新 |
| 移动端友好 | 真机可用 | 只做了响应式但没有测试 |
永久链接的结构一旦定了就不要再改。 如果非改不可,必须做 301 跳转。数字 ID 形式的链接(/123.html)看起来不好看,但它是最稳定的——标题会改,ID 永远不会。这也是我最后选它的原因。
11.2 摘要怎么写
摘要不要用「截取正文前 N 个字」。这段话往往是文章的开场白,包含大量铺垫,放在搜索结果里信息密度极低。
好的摘要是一句话说清这篇文章解决了什么问题。这需要人为写,或者从正文里挑出信息量最大的那一句。
如果你坚持自动生成,至少做两件事:跳过标题和图片,只从正文段落里取;优先取包含数字或具体名词的句子。
11.3 别做的事
- 别做关键词堆砌。 它现在只会让文章读起来更糟,没有任何正面效果。
- 别用隐藏文字。 得不偿失。
- 别在每篇文章末尾塞一堆不相关的链接。 这会稀释站点的主题相关性。
- 别为了 SEO 牺牲可读性。 读者停留时间才是真正被衡量的东西——如果读者点进来三秒就走,任何技巧都救不了。
十二、备份、监控与长期维护
这一节是全文最不有趣的部分,但它是决定你的站点能不能跑三年的部分。
12.1 备份的三二一原则
- 三份副本:生产环境一份,本机一份,异地一份。
- 两种介质:比如服务器硬盘 + 对象存储。
- 一份离线:防止勒索软件或误删波及全部副本。
对个人博客来说,落地版是:数据库每天导出一次,上传目录每天同步一次,导出的压缩包同步到对象存储,本地再留一份最近的。
12.2 比备份更重要的是「验证恢复」
我曾经连续三个月每天看到备份任务成功执行,直到有一次真的需要恢复,才发现备份文件是零字节的——因为磁盘满了,任务静默失败,但退出码是 0。
一个从来没被恢复过的备份,不能算备份。
我的做法是每季度做一次演练:把最近的备份恢复到一台临时机器上,看看站点能不能跑起来、图片在不在、评论数对不对。整个过程大概二十分钟。
12.3 监控什么
不用上重型方案,监控三件事就够:
- 站点能不能访问。 从外部定时请求首页,连续失败就告警。这是最重要的一条。
- 磁盘用了多少。 博客最常见的宕机原因就是磁盘满了。
- 证书还有多久过期。 自动化续期偶尔会失败,留个提前量。
免费方案和自建方案都有很多,重点不是选哪个,而是告警要能真的送到你手机上。设置完记得故意触发一次,确认能收到。
12.4 长期维护的心理准备
最后说点和技术无关的。
自建博客的日常维护量其实很小——一个月可能就花十分钟看看备份、偶尔更新一下镜像。真正消耗人的不是维护,是「改版冲动」。
我见过太多博客死于「推倒重来」:作者对现有主题不满,决定重写,写了两个月没写完,然后整个站点荒废。内容一篇没发,技术债却越堆越高。
给自己定一条线:主题只允许增量修改,不允许推倒重来。 想重构的冲动,先记在待办里,放两周。两周后还想做,再衡量当时的代价。
结:运维一个博客的真相
回到最开始的问题:为什么还要自建?
写到这里我的答案比开头更清楚了。自建博客让你付出的,不只是服务器的钱,还有决定权——每一个细节你都要做决定,然后承担结果。这个负担是真实的。
但它换来的东西也很实在:你写下的每一个字都在自己的硬盘上;站点的形态完全由你决定;出了问题你能查到底。「可控」这件事本身,对一部分人来说就是意义。
如果你读到这里觉得麻烦多于兴奋,那说明自建不是你的路,去平台上写字挺好的,把精力留给内容本身。
如果你读到这里已经打开了终端,那祝你好运。第一篇文章不用写得好,写出来就行。
暂无评论
还没有评论,来说点什么吧