4.3 页面加载与静态资源优化
> 一个页面的时间花在哪,顺着这张账单改哪一栏。
4.3.1 先拿到账单:测出 TTFB 和页面体积
优化之前先测速,否则全是感觉。命令行就能测:
# TTFB(首字节时间)、总耗时、页面体积
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s Total: %{time_total}s Size: %{size_download}B\n" \
https://你的域名/forum.php
# 拆开看:DNS、握手、首字节各占多少
curl -o /dev/null -s https://你的域名/forum.php \
-w "DNS %{time_namelookup}s Connect %{time_connect}s TTFB %{time_starttransfer}s\n"
# 看响应头,确认 gzip 和缓存头是否生效
curl -I https://你的域名/forum.php
TTFB 决定「点下去到出现内容」有多久,主要吃数据库和 PHP;Total 减去 TTFB 那段基本是浏览器下载资源的时间。两个都测,才知道该往服务器使劲还是往浏览器使劲。
| 工具 | 适合场景 |
|---|---|
curl -w |
单页面快速对比,服务器上就能跑 |
| DevTools Network 面板 | 看具体哪个资源慢,按 Time 排序 |
| PageSpeed Insights | 看移动端评分,看趋势不看单次 |
| WebPageTest | 多地节点测速,适合验证 CDN |
4.3.2 优化项、收益、难度、风险
| 优化项 | 预期收益 | 难度 | 风险 |
|---|---|---|---|
| 开启 gzip / brotli 压缩 | 文本资源体积降 60%~80% | 低 | 低 |
| 静态资源加长缓存 + 版本号 | 二次访问快 200ms 以上 | 低 | 中,版本号没改会缓存不到 |
| 合并 CSS/JS 减少请求数 | 首屏少 5~10 个请求 | 中 | 中,合并顺序错了会出错 |
| 图片懒加载 | 长列表页首屏快 1s 以上 | 低 | 低 |
| 关闭不需要的插件 | 每次请求少几十毫秒 | 低 | 低,关前确认业务 |
| 减少模板循环层数 | 渲染时间下降 | 中 | 低 |
| 接入 CDN | 异地首字节降 200ms 以上 | 中 | 中,回源要配好 |
| 裁剪首页模块数 | 首屏渲染更快 | 低 | 低 |
| 模板区块静态化 | 页面查询数下降明显 | 中 | 高,动态数据会陈旧 |
风险高的先别碰。 模板区块静态化收益大,但会让「今天发帖数」这类实时数据不更新,除非有定时刷新机制。
4.3.3 模板层:把能缓存的区块做成静态
Discuz! 的模板先编译成 PHP 缓存到 data/cache/,再执行。每次循环、每个判断都在消耗 PHP 时间和数据库往返。三条实用规则:
- 循环层数越少越好。 三层嵌套(版块 → 分类 → 主题)改成先查主题再按版块分组,能少一轮循环。
- 判断写在循环外。
{if}放进循环体内部意味着每个元素都判断一次。 - 别在模板里现算。
{eval}能写 PHP 但每次都要执行,能预先算好就放到控制器。
<!--[diy=diy1]--><div id="diy1" class="area"></div><!--[/diy]-->
<!-- 判断放在循环外:只算一次 -->
{if $forumlist}
<ul>
{loop $forumlist $fid $forum}
{eval $itemcount = $forum['threads'] ? intval($forum['threads']) : 0;}
<li class="fitem f{$fid}">
<a href="forum.php?mod=forum&fid={$fid}">{$forum['name']}</a>
<span class="num">{$itemcount}</span>
</li>
{/loop}
</ul>
{/if}
X5 新增的细胞模板 {cell} / {cells} 适合这类场景:把「取数据 + 渲染」封装成固定单元,站长在后台 DIY 组合,不改模板源码。
4.3.4 静态资源:合并、压缩、缓存、懒加载
合并与压缩。 默认套系的 common.css、module.css 加上各插件的样式,动辄十几个文件。扩展型 CSS(template/{标识}/common/extend_common.css、extend_module.css)在默认文件基础上做覆盖,适合塞自己的样式而不动核心文件。
版本号 + 长缓存。 缓存头给一年,文件名带版本号,改内容时版本号跟着变:
# Nginx:静态资源长缓存 + gzip
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 365d;
add_header Cache-Control "public, immutable";
access_log off;
}
gzip on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types text/css application/javascript application/json image/svg+xml;
<link rel="stylesheet" href="{$_G['style']['cssdir']}/common.css?v=20260101">
图片懒加载与尺寸。 <img> 加 loading="lazy",同时写死 width 和 height,浏览器能提前留出位置,避免页面整体往下跳(CLS 指标)。头像和缩略图一律给固定尺寸。
字体与图标。 中文站点最忌讳全套 Web Font,一个中文字体 3MB 起。图标用 SVG sprite,必须用字体时做 subset 子集化。
4.3.5 HTTP 层与 CDN
gzip 打开后文本体积能降一大截,brotli 再多榨 10%~15%,需要 Nginx 装 ngx_brotli 模块。HTTP/2 值得开,它把多个小请求合并到一条连接上。
CDN 接入前先理清哪些路径能走:静态资源(CSS、JS、图片、附件)完全没问题;forum.php、portal.php 这类动态入口要走 CDN,必须在后台开启「CDN 支持」并配好回源 Host,否则会出现登录态丢失、验证码失效这类问题。开源版附件仍在本地磁盘,交给 CDN 反代最省事。
4.3.6 数据库层:削减页面查询次数
首页要查版块、查主题、查在线用户、查广告位、查导航,每项都是一次往返:
- 能不查就不查。 页面底部统计、「今日发帖」用数据缓存代替实时查询。
- 合并查询。 三次取列表改成一次
IN (id1, id2, id3)带回来。 - 结果本请求内复用。 同一页反复取同一份数据时,只在第一次查库。
- 砍掉不必要的插件。 统计类插件每次请求都要查库,用不到就关。
<?php
// 放进插件的 .inc.php:一次取回多个版块,避免逐个查
define('IN_DISCUZ', true);
define('DISCUZ_ROOT', dirname(dirname(dirname(__DIR__))));
require './source/class/class_core.php';
$discuz = C::app();
$discuz->cachelist = [];
$discuz->init();
$forums = C::t('forum_forum')->fetch_all(
array('type' => 'forum', 'displayorder' => array('between' => array(0, 100)))
);
savecache('myplugin_forumlist', $forums);
4.3.7 前后对比怎么测才靠谱
测速有三个纪律,否则等于白测:
- 同一台机器、同一时段测。 换机器测出来的差异全是网络抖动。
- 测三次取中间值。 第一次访问含 DNS、握手和回源,不算数。
- 清缓存测一次,不清理再测一次。 前者冷启动,后者才是常态。
# 冷启动:先清站点缓存,再测
curl -X POST -d 'formhash=xxx' https://你的域名/admin.php?action=clearcache > /dev/null
sleep 5
curl -o /dev/null -s -w "cold TTFB: %{time_starttransfer}s\n" https://你的域名/forum.php
# 常态:连测三次,看波动
for i in 1 2 3; do
curl -o /dev/null -s -w "warm TTFB: %{time_starttransfer}s Size: %{size_download}B\n" \
https://你的域名/forum.php
done
优化前先存一份基线(TTFB、体积、查询次数),改完再测一遍。改五项只快 30ms 不算成功,改一项快了 300ms 才叫改对地方。
