搜索全站
主题 15 分钟阅读

WordPress 404 页面设计:带搜索与热门内容的自定义做法

404 页直接流失访客太可惜。本文讲清 404.php 的模板优先级、必须返回 404 状态码的原因,给出带搜索框、热门文章、分类入口的 404 页完整代码与服务器配置。

U
UFO_PF 2020-10-13 · 阅读 15 分钟
Wo 主题
阅读提示
本文约 5,664 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:访客撞上死链,你的页面只给了他一句「找不到」

站点改版、文章删除、URL 规则变更、外链拼错——这些都会产生 404。默认主题的 404 页面通常只有一行「抱歉,找不到你要的页面」,最多再加一句「试试搜索」。访客此时只有一个选择:关掉标签页。而你为了把他引到这个页面付出的一切(外链建设、社交传播、广告投放)全部白费。

一个合格的 404 页面应该完成三件事:告诉用户发生了什么(这个地址不存在,不是你的错)、给他继续往下走的路(搜索框、热门内容、分类入口、返回首页)、顺便帮你发现问题(记录 404 URL,便于后续做 301)。技术上还有一条硬要求:404 页面必须真正返回 HTTP 404 状态码,而不是 200——否则搜索引擎会把它当成正常页面收录,产生大量「软 404」。

原理分析:模板优先级与状态码是两个独立问题

环节 负责者 关键点
模板选择 404.php → index.php 只要有 404.php 就优先用它
状态码 wp_redirect_admin_locations / handle_404() 由 WP 主查询决定,模板改不了
争夺模板 template_include 过滤器 可用于特殊场景接管

WordPress 在 WP::handle_404() 里判断当前请求是否为 404,并调用 status_header( 404 )。这里有个常见误解:如果你在 404.php 里用 query_posts() 或 new WP_Query() 输出内容,状态码不会改变,仍然是 404,这点不用担心。真正会把状态码变成 200 的是两种情况:服务器层面把 404 重写成了别的可访问页面;或者用了某些「智能重定向」插件把未知 URL 全部 302 到首页,这会产生大量软 404。

pre_handle_404 过滤器(在 handle_404() 开头触发)允许你提前干预:返回 true 表示「我已处理」,WordPress 不再做后续判断。这个钩子适合做自定义路由,普通 404 页面不需要动它。

内容层面,get_search_form() 会输出主题的 searchform.php,如果主题没有这个文件则输出内置表单。热门文章可以用 WP_Query 按 comment_count 或自定义浏览量字段排序。要注意 404 页面上的查询不要太重——它可能被爬虫大量访问。

保姆级实操

  1. 建 404.php:放在主题根目录,优先级高于 index.php。
  2. 确认状态码:用 curl -I 检查返回的是 404 而不是 200。
  3. 放搜索框:get_search_form() 一行搞定,样式用主题现有搜索样式。
  4. 加热门内容:WP_Query 取评论数最多的 5–8 篇,或取指定分类的最新文章。
  5. 加分类与标签入口:wp_list_categories() 或 wp_tag_cloud(),给访客更多选择。
  6. 记录 404 URL:写一个轻量的日志,便于后续批量做 301。
  7. 服务器配置:确保 nginx/Apache 把 404 交给 WordPress 处理,而不是返回服务器的默认错误页。
<?php
/**
 * 404.php
 *
 * @package lumen-mimangfei-child
 */

get_header();
?>
<main id="primary" class="site-main site-main--404">

    <section class="not-found">
        <h1 class="not-found__code">404</h1>
        <p class="not-found__desc">
            这个地址已经不存在了,可能是链接过期或拼写有误。试试下面的搜索,或看看这些内容。
        </p>

        <div class="not-found__search">
            <?php get_search_form(); ?>
        </div>
    </section>

    <section class="not-found__popular" aria-labelledby="not-found-popular">
        <h2 id="not-found-popular">热门文章</h2>
        <?php
        $q = new WP_Query( array(
            'posts_per_page'      => 6,
            'ignore_sticky_posts' => true,
            'orderby'             => 'comment_count',
            'order'               => 'DESC',
            'no_found_rows'       => true,
        ) );

        if ( $q->have_posts() ) :
            ?>
            <ul class="not-found__list">
                <?php
                while ( $q->have_posts() ) :
                    $q->the_post();
                    ?>
                    <li>
                        <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
                        <span class="not-found__meta">
                            <?php echo esc_html( get_the_date() ); ?>
                        </span>
                    </li>
                    <?php
                endwhile;
                wp_reset_postdata();
                ?>
            </ul>
            <?php
        endif;
        ?>
    </section>

    <section class="not-found__cats" aria-labelledby="not-found-cats">
        <h2 id="not-found-cats">按分类浏览</h2>
        <ul>
            <?php
            wp_list_categories( array(
                'title_li'    => '',
                'orderby'     => 'count',
                'order'       => 'DESC',
                'number'      => 10,
                'show_count'  => true,
                'hide_empty'  => true,
            ) );
            ?>
        </ul>
    </section>

</main>
<?php
get_footer();
<?php
// 子主题 functions.php:轻量记录 404 URL,便于后续补 301
add_action( 'wp', 'mimangfei_log_404' );
function mimangfei_log_404() {
    if ( ! is_404() ) {
        return;
    }

    $path  = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
    $transient = 'mmf_404_log';
    $log = get_transient( $transient );
    if ( ! is_array( $log ) ) {
        $log = array();
    }

    if ( isset( $log[ $path ] ) ) {
        $log[ $path ]++;
    } elseif ( count( $log ) < 200 ) {
        $log[ $path ] = 1;
    }

    set_transient( $transient, $log, WEEK_IN_SECONDS );
}
# nginx:把 404 交给 WordPress 处理,不要返回服务器默认页
error_page 404 /index.php?error=404;

location / {
    try_files $uri $uri/ /index.php?$args;
}
# 验证 404 页面返回的状态码,必须是 404 而不是 200
curl -I https://example.com/this-page-does-not-exist

常见踩坑

  1. 返回 200 状态码:装了「404 重定向到首页」的插件,或用 wp_redirect() 把 404 转走,搜索引擎会收录大量软 404。正确做法是保留 404 状态码,只在页面内容上给出出路。
  2. 服务器配置抢先:nginx 配了 error_page 404 /404.html,WordPress 的 404.php 根本不会被用到。静态错误页无法使用主题函数,必须把 404 交给 PHP。
  3. 案例:某站在 Search Console 里出现两千多条「已编入索引,但已提交 noindex」与「软 404」警告。排查发现他们为了让 404 页好看,用插件把所有未知 URL 302 到了首页——搜索引擎把首页当成了这些 URL 的内容,判定为软 404。改成返回真正的 404 状态码并保留好看的 404.php 后,警告在两个月内逐步清空。教训:重定向到首页是最糟糕的 404 处理方式。
  4. 404 页放了重量级查询:按浏览量排序 + 多篇全文,爬虫大量访问时数据库压力陡增。用 no_found_rows => true、限制条数,并加对象缓存。
  5. 搜索框指向了错误的 action:主题自建的搜索表单把 action 写成了具体 URL,切换域名后失效。get_search_form() 输出的表单用 home_url( '/' ),更安全。
  6. 404 页放在了 noindex:404 页本身不会被索引(状态码决定),不要给它加 noindex,也不要在 robots.txt 里屏蔽。
  7. 改版后旧 URL 没做 301:404 页做得再好,也不如把旧地址正确重定向到新地址。定期看记录的 404 列表,把高频的做 301。
  8. 移动端样式没适配:404 页常被忽略响应式测试,两个按钮在窄屏下叠在一起。按 主题响应式断点设置建议 的断点做一次窄屏检查。

总结

一个合格 404 页的判断标准是:状态码正确(404)+ 页面上有至少两条继续浏览的路径 + 高频 404 被记录下来并逐步做 301。行动清单:新建 404.php → 用 curl -I 确认状态码 → 加 get_search_form() 搜索框 → 加 6 条左右热门文章与分类入口 → 轻量记录 404 路径 → 检查 nginx/Apache 的 error_page 配置 → 每月看一次 404 记录补重定向。做完这步,可以接着优化 主题搜索结果页体验优化,让站内发现内容的第二条通路同样好用。

U
UFO_PF作者

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

留下你的想法

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