搜索全站
性能优化 15 分钟阅读

WordPress 插件与 CDN 缓存配合:清除联动与资源版本化

改完 CSS 用户看到的还是旧样式,多半是 CDN 边缘节点没失效。本文讲清浏览器缓存与 CDN 缓存两层机制,给出 filemtime 版本号方案、Cloudflare 单 URL 清除联动代码与验证方法。

U
UFO_PF 2026-08-14 · 阅读 15 分钟
Wo 性能优化
阅读提示
本文约 5,951 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:改完了,全世界都没看见

改完主题的 CSS,自己电脑上刷新是新样式,客户电脑上还是旧的;清了插件缓存、清了浏览器缓存、甚至重启了 Nginx,客户那边依然没变。最后发现是 CDN 边缘节点上还躺着一份 TTL 为 4 小时的旧文件——它根本没被通知到。

CDN 缓存和插件缓存是两套完全独立的系统,插件清自己的缓存不会通知 CDN。反过来,CDN 缓存的是「URL 到内容」的映射,只要 URL 不变,它就没有理由去回源。这就是为什么「版本化资源」比「清除缓存」更可靠:改了内容就换一个新 URL,CDN 自然会当成全新对象回源取一次。

本文把两件事讲透:静态资源用版本化,让失效自动发生;HTML 文档用清除联动,在内容变更时主动通知 CDN。

原理分析:两层缓存与两种失效策略

一次典型的 WordPress 请求要穿过的缓存层:

层级 位置 失效方式
浏览器缓存 用户设备 Cache-Control / ETag + URL 变化
CDN 边缘缓存 服务商节点 URL 变化、TTL 到期、API 主动清除
服务器层缓存 Nginx / LiteSpeed 插件或配置规则清除
插件页面缓存 wp-content/cache 插件后台或钩子清除
对象缓存 Redis / Memcached wp_cache_flush()

失效策略只有两种:

版本化(Versioning):资源 URL 带上内容指纹,如 main.css?ver=1728547200 或 app.a1b2c3.js。内容一变 URL 就变,所有层级自动失效,无需任何通知。缺点是构建/部署环节要保证指纹与内容严格对应。

清除(Purge):调 API 告诉 CDN「这个 URL 作废」。适合 URL 不能变的场景(HTML 文章页的 URL 是永久链接,不能加版本号)。缺点是依赖代码主动调用,漏调就出事故,且 API 有频率限制。

正确的分工是:CSS/JS/图片/字体一律版本化,HTML 文档一律走清除联动。 混用会两头不讨好。

保姆级实操

第一步:给主题与插件资源做内容指纹

最常见的错误是把版本号写死成 '1.0',那样版本号就失去了意义。用文件的修改时间最省事:

<?php
// 子主题 functions.php
add_action( 'wp_enqueue_scripts', function () {
    $base_uri = get_stylesheet_directory_uri();
    $base_dir = get_stylesheet_directory();

    $css = $base_dir . '/assets/css/main.css';
    $js  = $base_dir . '/assets/js/app.js';

    wp_enqueue_style(
        'mmf-main',
        $base_uri . '/assets/css/main.css',
        [],
        file_exists( $css ) ? (string) filemtime( $css ) : null
    );

    wp_enqueue_script(
        'mmf-app',
        $base_uri . '/assets/js/app.js',
        [],
        file_exists( $js ) ? (string) filemtime( $js ) : null,
        true
    );
}, 20 );

如果是 Vite、Webpack 这类构建产物,直接读 manifest 里的内容哈希文件(dist/assets/app-a1b2c3d4.js),连 query string 都不需要,CDN 和浏览器对「文件名变了」的处理最一致。

对第三方插件吐出来的资源,可以用 style_loader_src / script_loader_src 统一加版本号:

add_filter( 'style_loader_src', 'mmf_bump_asset_ver', 10, 2 );
add_filter( 'script_loader_src', 'mmf_bump_asset_ver', 10, 2 );
function mmf_bump_asset_ver( $src, $handle ) {
    // 只对本站资源生效,外链 CDN 资源不要动
    if ( str_starts_with( $src, home_url( '/' ) ) ) {
        $src = add_query_arg( 'ver', get_option( 'mmf_asset_release', '1' ), $src );
    }
    return $src;
}

改版时只需 update_option( 'mmf_asset_release', time() ),全站资源一次性换 URL。

第二步:HTML 文档不要在 CDN 上长缓存

文章页 URL 是永久链接,不能版本化,所以策略应该是:CDN 上要么不缓存 HTML,要么短 TTL + 主动清除。Cloudflare 可以用页面规则或 Cache Rules 把 /wp-admin/*、/wp-json/*、含 wordpress_logged_in Cookie 的请求设为 Bypass;HTML 的 Cache-Control 在 WordPress 侧控制:

// 让文章页在 CDN 上最多存活 5 分钟,且允许被清除
add_action( 'send_headers', function () {
    if ( is_singular( 'post' ) && ! is_user_logged_in() ) {
        header( 'Cache-Control: public, max-age=300, s-maxage=300' );
    }
} );

s-maxage 是给共享缓存(CDN)看的,max-age 是给浏览器看的,两者要分开设。

第三步:内容变更时联动清除 CDN

以 Cloudflare 为例,用 save_post 钩子在文章更新后清除该文章、首页、归档页几个 URL。注意不要在 save_post 里同步发 HTTP 请求(会拖慢后台保存),丢给计划任务异步执行:

add_action( 'save_post', function ( $post_id, $post, $update ) {
    if ( ! $update || wp_is_post_revision( $post_id ) ) {
        return;
    }
    if ( 'publish' !== get_post_status( $post_id ) ) {
        return;
    }
    $urls = [
        get_permalink( $post_id ),
        home_url( '/' ),
        get_post_type_archive_link( $post->post_type ),
    ];
    $urls = array_values( array_unique( array_filter( $urls ) ) );
    wp_schedule_single_event( time() + 5, 'mmf_cdn_purge', [ $urls ] );
}, 10, 3 );

add_action( 'mmf_cdn_purge', function ( $urls ) {
    if ( ! defined( 'CF_ZONE_ID' ) || ! defined( 'CF_API_TOKEN' ) ) {
        return;   // 凭证写在 wp-config.php 的常量里,不要硬编码
    }
    $response = wp_remote_request(
        'https://api.cloudflare.com/client/v4/zones/' . CF_ZONE_ID . '/purge_cache',
        [
            'method'  => 'DELETE',
            'timeout' => 15,
            'headers' => [
                'Authorization' => 'Bearer ' . CF_API_TOKEN,
                'Content-Type'  => 'application/json',
            ],
            'body'    => wp_json_encode( [ 'files' => array_slice( $urls, 0, 30 ) ] ),
        ]
    );
    if ( is_wp_error( $response ) ) {
        error_log( 'CDN purge failed: ' . $response->get_error_message() );
        return;
    }
    error_log( 'CDN purge: ' . wp_remote_retrieve_body( $response ) );
} );

请求体长这样,一次最多清 30 个 URL:

{
  "files": [
    "https://example.com/2026/10/some-post/",
    "https://example.com/",
    "https://example.com/blog/"
  ]
}

第四步:验证 CDN 是否真的失效了

for u in / /2026/10/some-post/ /wp-content/themes/mytheme/assets/css/main.css; do
  echo "== $u"
  curl -sI "https://example.com$u" \
    | grep -iE 'cf-cache-status|age:|cache-control|etag'
done

# 连打两次看状态迁移:MISS/EXPIRED -> HIT
curl -sI https://example.com/ | grep -i 'cf-cache-status'
sleep 2
curl -sI https://example.com/ | grep -i 'cf-cache-status'

CF-Cache-Status 的常见取值:HIT(边缘命中)、MISS(回源了)、EXPIRED(过期回源)、BYPASS(规则要求不缓存)、DYNAMIC(默认不缓存的内容,如 PHP 生成的 HTML)。如果 CSS 文件长期是 HIT 而 Age 一直增长,说明版本化没生效。

常见踩坑

  1. 版本号写死:wp_enqueue_style( 'x', $url, [], '1.0' ) 是无效版本化,改一百次内容 URL 都不变。

  2. 清除 API 同步调用:在 save_post 里直接 wp_remote_post,编辑器保存要等 2 秒,用户以为卡死,反复点保存,反而触发多次清除。

  3. 案例: 某站在每次 save_post 时调用 Cloudflare 的 purge_everything(清除全部)。编辑批量改 40 篇文章的当天,全站缓存被清了 40 次,回源流量暴涨,源站 4 核 8G 直接打满,前台间歇性 502。改成按 URL 批量清除后,回源 QPS 降了九成。

  4. 清除了 CDN 没清插件缓存:CDN 回源取到的仍然是插件缓存里的旧 HTML,看起来「清除无效」。清除顺序应该是:先清插件缓存,再清 CDN。

  5. Cache-Control: private 让 CDN 完全不缓存:有些插件为了安全给全站加 private,结果 CDN 对静态资源也不缓存了,带宽费用没省下来。静态资源必须单独设 public, max-age=31536000, immutable。

  6. 忽略 Vary 头:如果 HTML 会因 Accept-Encoding、User-Agent(移动端/桌面端不同输出)而不同,必须发 Vary 头,否则 CDN 会把移动端 HTML 发给桌面用户。

  7. 凭证写进主题文件:API Token 硬编码在 functions.php 里,主题一发版进 Git 就泄露了。用 wp-config.php 的 define() 或环境变量。

总结

判断用哪种策略,看 URL 能不能变:能变就用版本化(filemtime 或构建哈希),让失效自动发生,这是最省心的;不能变(永久链接、首页)就走清除联动,且必须异步、按 URL 精准清除,别滥用 purge_everything。

行动清单:把所有 CSS/JS/图片的版本号改成内容指纹;静态资源 Cache-Control 设长缓存 + immutable;HTML 设短 TTL 并区分 s-maxage 与 max-age;save_post 里只排计划任务,真正清除放异步;API 凭证放 wp-config.php;每次上线用 curl -I 看 CF-Cache-Status 与 Age 验收。WordPress 静态资源缓存策略

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。