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 时间和数据库往返。三条实用规则:

  1. 循环层数越少越好。 三层嵌套(版块 → 分类 → 主题)改成先查主题再按版块分组,能少一轮循环。
  2. 判断写在循环外。 {if} 放进循环体内部意味着每个元素都判断一次。
  3. 别在模板里现算。 {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 前后对比怎么测才靠谱

测速有三个纪律,否则等于白测:

  1. 同一台机器、同一时段测。 换机器测出来的差异全是网络抖动。
  2. 测三次取中间值。 第一次访问含 DNS、握手和回源,不算数。
  3. 清缓存测一次,不清理再测一次。 前者冷启动,后者才是常态。
# 冷启动:先清站点缓存,再测
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 才叫改对地方。