本文约 3,426 字,预计阅读 9 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:改了样式,同事说没变,客户说变了
这个场景几乎每个站长都遇到过:你在子主题里改了按钮颜色,本地和自己的浏览器上一切正常,同事打开还是旧样式,客户隔天打开又是新的。于是你一遍遍教人「按 Ctrl+F5 强制刷新」,刷新完好了,但下次改又要重来一遍。更糟的是有人坚信是代码没生效,开始加 !important、改选择器,把简单的缓存问题搞成真正的样式事故。
「改了没生效」的成因基本就三类:资源 URL 没变(浏览器/CDN 用旧副本)、中间层缓存没清(页面缓存、对象缓存、CDN 缓存)、改的文件根本不是实际加载的那个。三者表现几乎一样,必须用可验证的方法逐层排除,而不是靠反复刷新碰运气。本文给出一条从服务端到客户端的排查链,每层都配一个可以直接执行的验证动作。
原理分析:缓存发生在哪几层
一次 CSS 请求会经过四道缓存,任意一层返回旧副本都会让改动「看不见」:
| 层级 | 位置 | 由谁控制 | 清除方式 |
|---|---|---|---|
| 浏览器缓存 | 用户本机 | HTTP 响应头 Cache-Control / Expires |
资源 URL 加版本号,或强制刷新 |
| CDN / 反向代理 | 边缘节点 | CDN 缓存规则、缓存键 | CDN 后台刷新 / Purge API |
| 页面缓存插件 | 服务端 | WP Rocket、W3 Total Cache、LiteSpeed Cache | 插件的「清除缓存」 |
| 对象缓存 | 服务端 | Redis / Memcached | wp_cache_flush() |
第一道最关键,也最好解决:WordPress 的 wp_enqueue_style() 第四个参数是版本号,它会被拼成 ?ver=1.2.3 追加在 URL 上。URL 一变,浏览器和 CDN 都认为是新资源,立刻回源。所以正确的发布流程不是让用户刷缓存,而是让资源 URL 变。
很多主题把版本号写死成 1.0.0,或者直接用主题版本号——后者的问题是:子主题 CSS 改了但主题版本没升,URL 不变,缓存照旧。更稳妥的做法是用文件的修改时间 filemtime() 作为版本号,文件一存 URL 就变。
保姆级实操
- 确认加载的是哪个文件:前台查看源代码,找到你的 CSS 对应的
<link>,复制完整 URL(含?ver=)。这一步能立刻排除「改错文件」的可能。 - 比对线上文件内容:把 URL 粘到浏览器直接打开,搜索你新加的样式。搜不到说明服务端就是旧文件,问题在发布流程;搜得到说明是客户端/CDN 缓存。
- 给版本号换成
filemtime():本地开发时每次保存自动换 URL;生产环境要注意filemtime()会触发一次 stat,用对象缓存挡一层即可。 - 清服务端缓存:页面缓存插件清一次,对象缓存清一次。WordPress 自带
wp_cache_flush()。 - 刷 CDN:到 CDN 后台提交 URL 刷新(或目录刷新),不要整站刷新,代价太大。
- 验证:用无痕窗口 +
curl -I看响应头里的Cache-Control与返回的版本号,确认边缘节点已更新。
<?php
// 子主题 functions.php:用文件修改时间做版本号,改完即换 URL
add_action( 'wp_enqueue_scripts', function() {
$css_path = get_stylesheet_directory() . '/assets/css/child.css';
$ver = file_exists( $css_path ) ? filemtime( $css_path ) : wp_get_theme()->get( 'Version' );
wp_enqueue_style(
'mimangfei-child',
get_stylesheet_directory_uri() . '/assets/css/child.css',
array(),
$ver
);
}, 20 );
<?php
// 统一给所有样式追加构建号(适合有打包流程的项目)
add_filter( 'style_loader_src', function( $src, $handle ) {
$build = get_option( 'mimangfei_build_id' );
if ( ! $build ) {
$build = gmdate( 'YmdHi' );
update_option( 'mimangfei_build_id', $build );
}
return add_query_arg( 'b', $build, $src );
}, 10, 2 );
// 发布后调用一次即可全站换 URL
function mimangfei_bump_build() {
update_option( 'mimangfei_build_id', gmdate( 'YmdHi' ) );
wp_cache_flush();
}
# 静态资源设长缓存:正因为 URL 会变,才敢设 1 年
location ~* \.(css|js)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# HTML 页面不缓存,保证骨架每次回源
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-store, must-revalidate";
}
# 验证线上资源:看版本号与缓存头
curl -I "https://example.com/wp-content/themes/child/assets/css/child.css?ver=1729500000"
# 关注输出里的 HTTP/2 200、cache-control、x-cache(CDN 是否命中)
常见踩坑
- 只在自己浏览器验证:自己的浏览器有历史缓存,必须换无痕窗口或用
curl。 - 清了页面缓存但没清 CDN:页面 HTML 更新了,引用的 CSS 版本号也变了,但 CDN 上旧版本号的副本依旧存在,部分用户仍拿到旧文件。
filemtime()在生产环境被禁用:部分主机禁用了相关函数或 stat 缓存未刷新,导致版本号不更新。可用clearstatcache()或改用构建号方案。- 修改的是父主题文件:父主题升级后改动丢失,且缓存里可能残留旧文件。所有改动必须进子主题,原则见
子主题创建与使用规范。 - 整站刷新 CDN:一次 purge all 会让全站瞬间回源,流量大时可能把源站打挂。只刷变更的具体 URL。
- 案例:一个电商站在大促前改了商品卡片样式,发布后一小时仍有大量用户看到旧版。排查发现:版本号用的是主题版本(未变),页面缓存插件缓存了含旧 URL 的 HTML,而 CDN 按 URL 缓存了一年。三者叠加导致「新 CSS 已上传但没人拿到」。最终改为
filemtime()版本号 + 发布脚本自动清页面缓存 + CDN 按 URL 刷新,问题解决,之后每次改版都走同一条脚本。 - 忽略移动端运营商缓存:个别移动网络会缓存静态资源更久,只能靠版本号变更规避,无法靠刷新解决。
总结
排查「改了没生效」按固定顺序走:确认加载的 URL → 直接访问该 URL 看内容新旧 → 换版本号 → 清服务端缓存 → 刷 CDN。判断标准很简单:只要资源 URL 变了,浏览器和 CDN 就一定会重新取;不变则无论怎么刷新都可能拿到旧副本。因此版本号策略应当在项目初期就定好,别等出事再补。相关样式冲突问题的排查请配合 主题 CSS 权重排查方法 一起看,避免把缓存问题误判成权重问题。
