前言:一次完整的站点优化,到底改了什么
很多人做站点优化,习惯一个一个插件地试:装个缓存插件、开个图片压缩、再改改标题标签。改完感觉"好像快了",但说不清到底哪一项起了作用,也说不清哪些改动其实互相打架。
这篇文章记录一次完整的优化过程,涉及四个方向:图片格式、缓存插件冲突、CSS 字号陷阱、内部链接结构。重点不在于"我改了什么",而在于每一步怎么判断、怎么验证——这些判断方法可以直接复用到你自己的站点上。
文中的域名、路径均已脱敏处理(统一为 www.example.com 和 static/),不影响理解。
一、WebP:两种方案,差别比想象中大
1.1 为什么要上 WebP
同样的图片,WebP 的体积优势非常明显:
| 原格式 | 转 WebP 后 | 典型场景 |
|---|---|---|
| PNG | 小 50%~70% | logo、截图 |
| JPG | 小 25%~35% | 文章配图、照片 |
图片通常占页面总重量的 60%~75%。图片小一半,页面加载时间大致也砍半。
1.2 方案 A:改 URL(插件常用做法)
缓存插件在生成 HTML 缓存时,直接把 <img src="photo.jpg"> 改写成 <img src="photo.jpg.webp">。
问题在于:它不检查这个 .webp 文件是否真的存在。
如果某张图转换失败(超时、损坏、新上传还没转),URL 照样被加上 .webp 后缀——结果就是这张图直接 404,永久裂图。
1.3 方案 B:内部重写(推荐)
Apache 的 .htaccess 可以在服务器内部完成替换,URL 一个字都不用改:
<IfModule mod_rewrite.c>
RewriteEngine On
# 只在浏览器声明支持 WebP 时处理
RewriteCond %{HTTP_ACCEPT} image/webp
# 关键:检查 .webp 文件是否真的存在
RewriteCond %{REQUEST_FILENAME}.webp -f
# 内部重写,浏览器地址栏不变
RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.webp [T=image/webp,L]
</IfModule>
<IfModule mod_headers.c>
# 同一 URL 有两种响应,必须声明按 Accept 区分缓存
<FilesMatch "\.(jpe?g|png)$">
Header append Vary Accept
</FilesMatch>
</IfModule>
核心是这一行 RewriteCond %{REQUEST_FILENAME}.webp -f——文件不存在就自动跳过,回退原图,永远不会裂图。
1.4 两种方案对比
| 对比项 | 方案 A(改 URL) | 方案 B(内部重写) |
|---|---|---|
| HTML 里的 URL | 变成 .jpg.webp | 始终 .jpg |
| 文件不存在时 | 404 裂图 | 自动回退原图 |
| 覆盖范围 | 只改 HTML 里的 img | 所有图片,含 CSS 背景图 |
| 已有文章 | 需重新生成 | 一个字不用改 |
| 将来想关掉 | 重新生成全站缓存 | 删条规则即可 |
对已有大量文章的站点,方案 B 的价值尤其明显——几千篇文章里的 img 标签一个都不用动。
二、排查实录:同一张图为什么时有时无
2.1 现象
配好之后发现一个奇怪现象:无痕模式下看是 photo.jpg,硬刷新后却变成 photo.jpg.webp。反复清缓存十几次,问题依旧。
2.2 第一步:先划清责任边界
这条铁律能省掉大量时间:
如果 HTML 里的 URL 出现了 .webp 后缀,那必然是"改 URL"方案在起作用;内部重写是服务器行为,URL 永远不会变。
看到 URL 变了,就不用再去查 .htaccess 了——方向直接锁定到缓存插件。
2.3 第二步:测试工具可能是干扰源
这是最容易踩的坑。用 curl 测试时,默认发出的 Accept 头不含 image/webp:
# 第一次测:看起来没问题 curl -s https://www.example.com/demo.html | grep photo # 输出:src=".../photo.jpg" # 加上浏览器的 Accept 头再测 curl -s -H "Accept: text/html,image/webp,*/*" \ https://www.example.com/demo.html | grep photo # 输出:src=".../photo.jpg.webp"
同一个地址,两次结果完全不同。测试工具本身的行为差异,会让你误判为"缓存没清干净"。
2.4 第三步:清缓存没用,是因为它本来就该生成两份
缓存插件开启 WebP 后,会生成两套 HTML 缓存:
cache/demo.html ← 给不支持 WebP 的浏览器(URL 是 .jpg) cache/demo-webp.html ← 给支持 WebP 的浏览器(URL 是 .jpg.webp)
它靠请求的 Accept 头决定给哪一份。清一次缓存,它重新生成两份——清多少次都是两套。这不是"缓存脏了",是设计如此。
2.5 解决
在缓存插件的"缓存行为"设置里,找到 WebP 相关选项并取消勾选,然后清空一次缓存。(不同版本文案不同,常见的是"创建一个缓存版本以支持 WebP"。)
验证:两种 Accept 头请求,HTML 里的 URL 应该都变成 .jpg,同时服务器响应仍是 Content-Type: image/webp。
三、CSS 字号陷阱:一句 62.5% 让全站缩水
3.1 现象
首页主标题和简介段落特别小,比正文还小,怎么调都不对。
3.2 根因
主题样式表里有这么一句:
html {
font-size: 62.5%; /* = 10px,不是默认的 16px */
}
这是个老式 hack,目的是让 1em = 10px 方便计算。但它坑了 Bootstrap——Bootstrap 所有尺寸单位都是 rem,设计时假设 1rem = 16px。现在 1rem 只有 10px,于是:
| 元素 | Bootstrap 原值 | 实际值 |
|---|---|---|
| h1 | 2.5rem = 40px | 25px |
| .lead | 1.25rem = 20px | 12.5px |
.lead 本该是醒目的导语段落,结果只有 12.5px,比 16px 的正文还小。
3.3 解决:用 px 显式复位
/* 绕开 rem,用 px 显式指定 */
.home-h1 {
font-size: 34px;
line-height: 1.4;
font-weight: 600;
}
.home-intro {
font-size: 16px; /* 覆盖被缩水的 .lead */
line-height: 1.9;
max-width: 920px; /* 防止宽屏一行拉太长 */
}
@media (max-width: 768px) {
.home-h1 { font-size: 24px; }
.home-intro { font-size: 14px; }
}
为什么不直接改 html 的 62.5%?因为全站所有用 rem 的地方(栅格、间距、字号)会同时放大 60%,等于整站排版推倒重来。定点用 px 只影响目标元素,其他页面纹丝不动。
四、标题截断:PHP 按宽度,CSS 按像素
4.1 问题:截断了还留一大块空白
用 mb_strimwidth($title, 0, 38, '') 截断标题,结果末尾空出好几个字的距离。
原因在于它的计数规则是宽度单位,不是字符数:
中文/全角 = 2 个宽度单位 英文/数字 = 1 个宽度单位
所以同样设 38:
- 纯中文标题 → 只显示 19 个字
- 纯英文标题 → 显示 38 个字母
- 中英混排 → 长度飘忽不定
中文标题按 2 倍计费,"买"到的字符数只有一半——这就是留白的真相,不是数字设小了。
4.2 解决:交给 CSS 按像素截断
PHP 是"猜"宽度,CSS 是"量"像素。浏览器按真实可用空间计算,标题会一直延伸,直到贴住日期才截断。
/* PHP 输出完整标题,不截断 */ <?php echo esc_html( get_the_title() ); ?>
/* CSS 负责截断 */
.post-list li {
display: flex;
align-items: baseline;
}
.post-list li a.title {
flex: 1 1 auto;
min-width: 0; /* 关键:允许收缩 */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.post-list li time {
flex: 0 0 auto;
margin-left: 8px;
}
效果对比:
PHP 截断:WordPress 站点 SEO 自查与权…… 10/11 ← 提前截断留白 CSS 截断:WordPress 站点 SEO 自查与权重恢复… 10/11 ← 填满才截断
额外好处:Chrome / Edge / 手机表现一致,窗口缩放自动重算,天然适配移动端。
4.3 关于省略号
这里的 text-overflow: ellipsis 和 PHP 版的 '…' 参数不是一回事:
| 对比项 | PHP 版省略号 | CSS 版省略号 |
|---|---|---|
| 何时出现 | 到 38 宽度就截 | 真的装不下才出现 |
| 是否占预算 | 占 1 个宽度单位 | 不占 |
| 留白 | 中英混排常留一大块 | 填满才截断 |
CSS 版没有"预算"概念,排不满就一个点都不出现。保留它比硬切要好看得多。
五、内部链接结构:真正影响权重的部分
5.1 一个容易被忽略的事实
搜索引擎排名的是页面,不是站点。它不会想"这个站是主站所以排前面",只判断"这个 query 下哪个页面最该排第一"。
所以"主站权重输给子站"这个说法并不精确。真实发生的是:你的某个子站页面,在搜索结果里挤掉了主站的同类页面。
5.2 问题结构
假设主站有 1900 篇文章,子站不到 100 篇,而主站每个页面的导航栏里都有子站链接:
主站 1900 篇 ──每篇投一票──> 子站的少数几个页面
你在用 1900 篇文章,去养一个只有 100 篇的站。子站那一篇收到的内部链接支持,可能比主站任何一篇都高——所以它在具体 query 上赢了。
这跟搜索引擎"聪不聪明"无关,链接投票是所有搜索引擎的通用机制。
5.3 解决
- 把子站链接从全站导航撤下,只保留首页一个入口——这一步收回 1900 票,是整套动作里最关键的一步
- 子站的 header 和 footer 反向链回主站
- 子站文章正文里,在相关内容处插入主站内链(正文语境内链的权重远高于导航和页脚)
5.4 关于 nofollow 的取舍
如果想保留用户通路但切断权重传递,给链接加 rel="nofollow" 即可——用户照常点击、蜘蛛照常抓取,只是不传递权重。
自家站点之间加 nofollow 听起来奇怪,但如果你的目标就是"不再给子站加权",这个做法完全合理。
六、结构化数据:别重复输出
常见错误:插件已经输出了结构化数据,又在主题 header 里手写一段。
结果是同一个页面出现两个 Organization、两个 WebSite。搜索引擎遇到同类实体会困惑:到底哪个是你?严重时两个都忽略,反而把插件那份正确的也带下水。
判断方法:
curl -s https://www.example.com/ | grep '"@type"'
看输出里有几个 Organization / WebSite。如果各有两个,删掉手工那段,统一交给插件。
用 wp_json_encode() 而不是手写 JSON 字符串,可以让 PHP 保证语法永远正确——手写时只要站名里出现引号或换行,整个 JSON 就废了,而且不报错,只是被搜索引擎静默忽略。
七、几个值得记下的排查工具
# 看响应头(验证 WebP、缓存头、X-Robots-Tag)
curl -s -D - -o nul -H "Referer: https://www.example.com/" \
-H "Accept: image/webp,*/*" https://www.example.com/static/photo.jpg
# 模拟浏览器 Accept(避免测试工具本身造成误判)
curl -s -H "Accept: text/html,image/webp,*/*" https://www.example.com/demo.html
# 统计图片总体积(PowerShell)
$p = "D:\webroot\static"
$jpg = (Get-ChildItem $p -Recurse -Include *.jpg,*.png | Measure-Object -Sum Length).Sum
$webp = (Get-ChildItem $p -Recurse -Include *.webp | Measure-Object -Sum Length).Sum
"节省比例: {0:P1}" -f (1 - $webp/$jpg)
Windows 下注意:PowerShell 里的 curl 是 Invoke-WebRequest 的别名,参数完全不通用,要写成 curl.exe。
八、诚实地说:各项改动的价值排序
| 项目 | 价值 |
|---|---|
| 撤掉全站子站链接 | ★★★★★ 决定性 |
| canonical 各自自引用 | ★★★★ 防重复内容 |
| 正文内链 | ★★★ 长期有价值 |
| 子站反向链接 | ★★ 聊胜于无 |
| WebP | ★ SEO 边际改善 |
| 结构化数据 | ★ 不传递权重 |
关于 WebP 的 SEO 价值,说句实在话:它通过 Core Web Vitals 的 LCP 指标和百度的闪电算法起作用,但权重很低,大概在 5%~10% 的边际改善量级。
排名的决定性因素永远是:内容质量 + 外链权威 >>> 页面速度 >>> 是否用 WebP。
WebP 真正的价值在带宽成本和用户体验——尤其没用 CDN 的站点,省下的流量是实实在在的。排名提升是附带的,别指望靠它翻盘。
九、两条方法论
9.1 先划责任边界,再动手改
"URL 变了必然是改 URL 方案在起作用"——这类边界判断能一秒排除错误方向。每次排查前先问一句:这个现象的成因,可能落在哪几个模块里?
9.2 测试工具本身可能是干扰源
curl 默认不带 image/webp 的 Accept 头,导致你看到的"现象"和浏览器完全不同。当你发现"改了没效果"时,先怀疑测试方式,再怀疑改动本身。
结语
这一整套改下来,真正起决定作用的只有一件事:撤掉那个全站子站链接。其余都是顺手清理。
而 WebP 这套方案最值钱的地方在于——它用内部重写实现,几千篇文章的 img 标签一个字没改,且不依赖任何插件。CMS 换不换都在,哪天想关掉删条规则就行。
优化之后就是等。搜索引擎需要时间重新计算链接结构,通常两周左右能在站长平台看到索引量的变化。