搜索全站
Google SEO 10 分钟阅读

404 与软 404 的区别:WordPress 状态码规范与处理流程

页面没了却返回 200,就是软 404。本文讲清 404 与软 404 的判定差异、GSC 报告解读,给出 WordPress 用 pre_handle_404 正确返回状态码的代码与处理流程。

U
UFO_PF 2021-08-04 · 阅读 10 分钟
40 GOOGLE SEO
阅读提示
本文约 3,909 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:页面删了,谷歌却说”这个页面没问题”

在 Search Console 的”网页”报告里,有一类状态叫”软 404(Soft 404)”。它的意思是:Google 请求这个 URL 时,服务器返回了 200(或 404 以外的状态),但页面内容实质上是在表达”找不到内容”——比如显示”抱歉,没有找到你想要的页面”,或者干脆是一个空壳模板。搜索引擎无法从状态码判断页面不存在,只能靠内容猜测,于是标记为软 404。

软 404 的危害比正常 404 更大。正常的 404 会被快速从索引中剔除;软 404 则可能被反复抓取、长期滞留在索引库里,用户点进去看到的是无意义页面,既损害体验也浪费抓取预算。WordPress 站点里最常见的原因是空归档页、已删除但未处理的文章页、以及分页超出范围。

原理分析:状态码与内容的一致性

HTTP 状态码是服务器与爬虫之间的”契约”。契约一旦不一致,爬虫就只能降级依赖启发式判断。

情况 应返回 常见错误
内容已删除且无替代 404 / 410 返回 200
内容已迁移 301 返回 200 + JS 跳转
空分类/空标签 404 或 noindex 返回 200 空壳
分页超出总页数 404 返回 200 空列表
参数组合无结果 404 或 noindex 返回 200 空结果

410 Gone 比 404 更明确地表达”永久删除”,Google 处理速度略快,但两者在 SEO 结果上差别很小,用 404 即可。

WordPress 内部有一个 404 判定流程:主查询 WP_Query 执行后,若没有结果且不属于首页等情况,handle_404() 方法会设置 404 状态。问题在于有些场景它判定不到——比如分类存在但没有文章、自定义查询未走主查询、或者主题用 query_posts 覆盖了主查询导致状态被重置为 200。这时就需要用 pre_handle_404 钩子(WordPress 4.0 引入)提前干预。

保姆级实操

步骤 1:从 GSC 导出软 404 清单。 进入”索引 → 网页”,点击”软 404″卡片导出全部 URL,按路径规律归类:是文章页、分页、还是参数页。

步骤 2:手工复核几个典型 URL。 用 curl 看真实状态码,不要只看浏览器。

# 查看服务器真实返回状态码
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/old-post/

# 关注重定向链与最终状态
curl -sIL -o /dev/null -w "%{http_code} -> %{url_effective}\n" https://example.com/old-post/

步骤 3:对”确实不存在”的页面返回真 404。 用 pre_handle_404 在 WordPress 判定前处理空归档与超范围分页。

<?php
// 空归档页与超范围分页返回真正的 404
add_action('pre_handle_404', function ($preempt) {
    // 情况一:分类/标签存在但没有任何文章
    if ((is_category() || is_tag()) && !have_posts()) {
        status_header(404);
        nocache_headers();
        return true; // 阻止后续默认处理
    }
    // 情况二:请求的页码超过实际总页数
    $paged = get_query_var('paged');
    if ($paged > 1 && isset($GLOBALS['wp_query']->max_num_pages)
        && $paged > $GLOBALS['wp_query']->max_num_pages) {
        status_header(404);
        return true;
    }
    return $preempt;
});

注意:让空归档返回 404 之前,先确认这些归档页没有外链价值;如果没有,也可以改用 noindex 保留页面。

步骤 4:优化 404 页面本身。 404 页面不要是死胡同,提供搜索框、热门文章和返回首页的链接,降低跳出。

<?php
// 404.php 模板核心片段
get_header(); ?>
<main class="error-404">
  <h1>页面不存在</h1>
  <p>这个地址可能已经变更或被删除,你可以试试搜索:</p>
  <?php get_search_form(); ?>
  <nav aria-label="热门内容">
    <h2>热门内容</h2>
    <?php wp_nav_menu(['theme_location' => 'footer_primary', 'depth' => 1, 'fallback_cb' => false]); ?>
  </nav>
  <p><a href="<?php echo esc_url(home_url('/')); ?>">返回首页</a></p>
</main>
<?php get_footer();

步骤 5:对”有替代内容”的旧 URL 改 301。 若旧页面有等价新页面,不要留 404,直接 301 到新地址,外链权重才能继承。批量处理流程见 批量 301 重定向规划。

步骤 6:确认 Nginx 不要把 404 转成 200。 常见错误配置是把 error_page 404 指向一个返回 200 的 PHP 页面,导致 PHP 层返回 404 但 Nginx 层吞掉。

# 正确:让 PHP 返回的 404 状态透传,不要改成 200
server {
    error_page 404 /404.html;
    location = /404.html {
        internal;
    }
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        # 不要开启 fastcgi_intercept_errors,否则 PHP 的 404 会被 nginx 页覆盖
    }
}

步骤 7:复查。 提交 GSC 的”网址检查”重新抓取,确认状态从软 404 变为”已排除”;两周后再看报告中数量是否下降。

常见踩坑

  1. 案例: 某站把 404 页面做成 error_page 404 = /index.php,Nginx 把状态码改写成 200,全站 800 多个已删除 URL 全部被判定为软 404,长期占据索引库。改回 error_page 404 /404.html 并去掉 = 号后,两个月内这些 URL 基本清空。
  2. 主题用 query_posts() 覆盖主查询,404 状态被重置为 200。应改用 pre_get_posts 修改主查询。
  3. 把 404 页面做成 302 跳转到首页,Google 会把它当成首页的重复内容,进而可能索引首页内容却又给出错误 URL,属于典型的”URL 劫持”。
  4. 空标签页数量巨大且全部返回 200,被视为低质量页面群。要么删标签,要么统一 noindex。
  5. 只处理了 is_404() 模板,没处理”内容为空但状态为 200″的场景,GSC 的软 404 数量纹丝不动。
  6. 站点用了缓存插件,404 状态码被缓存成 200,排查时以为是代码问题。需要在缓存规则中排除 404 响应,可参考 WordPress 缓存插件配置。
  7. 把已删除的商品页 404 掉,却忽略了这些页面还有外链,白白损失权重,应先查外链再决定 404 还是 301。

总结

判断标准只有一句:页面的 HTTP 状态码必须与其表达的内容一致。内容是”没有”,就返回 404;内容是”搬走了”,就返回 301;内容是”存在但不想被索引”,才用 noindex。行动清单为:从 GSC 导出软 404 → curl 核实真实状态码 → 用 pre_handle_404 修正空归档与超范围分页 → 检查 Nginx 的 error_page 是否吞掉状态码 → 有替代内容的改 301 → 复查报告数量。把这套流程跑一遍,索引库的干净程度会有肉眼可见的改善。

U
UFO_PF作者

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

留下你的想法

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