pre_get_posts 改主循环,多刷了一次页面才发现分页崩了

· wordpress · 阅读 9
摘要

需求很简单:首页只显示某个分类的文章。我顺手在 functions.php 里写了段 pre_get_posts,本机看着挺对,上线第二天发现翻到第 2 页就 404。 原因不复杂,但我当时确实没往那想。 没加 is_main_query

需求很简单:首页只显示某个分类的文章。我顺手在 functions.php 里写了段 pre_get_posts,本机看着挺对,上线第二天发现翻到第 2 页就 404。

原因不复杂,但我当时确实没往那想。

没加 is_main_query 会连累到侧栏和分页

// 错误示范,functions.php
add_action( 'pre_get_posts', function ( $query ) {
    if ( $query->is_home() ) {
        $query->set( 'cat', 12 );
    }
} );

看着没毛病。is_home() 也确实只在首页成立。问题在于页面上不止一个查询——侧栏最新文章、相关推荐、主题自带的某个 widget,都可能走同一个 hook。is_home() 是布尔值,它不告诉你「这是不是那个主查询」。

所以条件得写全:

add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() ) {
        return;
    }
    if ( ! $query->is_main_query() ) {
        return;
    }
    if ( $query->is_home() ) {
        $query->set( 'cat', 12 );
    }
} );

is_main_query() 是 WP_Query 的实例方法,写在 query 对象上,不是全局函数那种用法。这点容易手滑。

分页 404 的另一个来源:posts_per_page

如果你的需求是「每页显示 8 篇」,光 set posts_per_page 不够。WordPress 算分页总数用的是它自己那套逻辑,你得让它知道总数变了。

我现在习惯这么写:

add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    if ( $query->is_home() ) {
        $query->set( 'cat', 12 );
        $query->set( 'posts_per_page', 8 );
    }
} );

一般来说这样就够了。但如果主题在模板里自己又调了一次 query_posts(),那你会被打回原形——query_posts() 会覆盖主查询,pre_get_posts 白干。这函数在官方文档里是明确不推荐的,可真有不少老主题还在用。

怎么确认到底哪个查询在捣乱

临时开一下:

// wp-config.php
 define( 'SAVEQUERIES', true );

// 模板里想看的地方
 global $wpdb;
 error_log( print_r( $wpdb->queries, true ) );

日志会告诉你这次请求跑了多少条 SQL、每个查询花了多久、从哪个文件哪一行来的。比猜有用得多。记得用完删掉,这玩意儿在生产环境上挺吃内存的。

哦对,还有一个我踩过的:条件里用 is_front_page() 而不是 is_home()。两者在「静态首页」和「博客首页」的配置下是反的。我没法给你一个统一答案,得看你后台「阅读」设置里选的是哪种,这点确实绕。