本文约 5,396 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:分页按钮长得都对,点第 2 页却跳回第 1 页
用 new WP_Query() 做了个”全部产品”页面,paginate_links() 渲染出的页码 1 2 3 … 很正常。鼠标移上去看链接地址:/products/page/2/,点下去页面刷新,内容还是第 1 页的产品。或者更糟:直接 404。
问题出在 paged 参数。自定义查询不会自动读取 URL 里的页码,必须手动传进 WP_Query,而且静态首页与文章列表页用的参数名还不一样。
原理分析:paged 与 page,两个参数两个场景
| 场景 | 参数名 | 读取方式 |
|---|---|---|
| 普通归档、搜索、分类页 | paged |
get_query_var( 'paged' ) |
| 静态首页(front page)上的分页 | page |
get_query_var( 'page' ) |
原因:静态首页的 URL 是 /page/2/,而 WordPress 把 /page/N/ 这个段在普通页面上解释为”文章内部分页”(<!--nextpage-->),在静态首页上才解释为列表分页。所以:
// 通用写法,两种场景都覆盖
$paged = get_query_var( 'paged' ) ? absint( get_query_var( 'paged' ) ) : 1;
if ( is_front_page() && get_query_var( 'page' ) ) {
$paged = absint( get_query_var( 'page' ) );
}
第二条容易漏的规律:paginate_links() 的 base 与 format 要跟固定链接结构匹配。
| 固定链接结构 | URL 形态 | base / format |
|---|---|---|
朴素(?p=123) |
/?paged=2 |
format => '?paged=%#%',base 用 get_pagenum_link(1) . '%_%' |
| 数字型 | /page/2/ |
format => 'page/%#%/' |
| 带后缀 | /page/2/(在 /products/ 下) |
base => '/products/page/%#%/' |
%#% 是页码占位符,%_% 是”是否分页”占位符,两者都由核心替换。
第三个坑是 max_num_pages:自定义查询的分页总数必须来自该查询自己的 $q->max_num_pages,不能拿全局 $wp_query 的。
保姆级实操
第 1 步:把 paged 传进 WP_Query
$paged = max( 1, get_query_var( 'paged' ) ? absint( get_query_var( 'paged' ) ) : absint( get_query_var( 'page' ) ) );
$q = new WP_Query( array(
'post_type' => 'product',
'posts_per_page' => 12,
'paged' => $paged,
// 分页时必须让它算总数,不要开 no_found_rows
) );
while ( $q->have_posts() ) :
$q->the_post();
get_template_part( 'template-parts/content/content', 'card' );
endwhile;
第 2 步:用 paginate_links 输出页码
$big = 999999999; // 用一个不可能出现的页码,保证 get_pagenum_link 生成完整 URL
echo paginate_links( array(
'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
'format' => '?paged=%#%',
'current' => $paged,
'total' => $q->max_num_pages,
'prev_text' => '« 上一页',
'next_text' => '下一页 »',
'mid_size' => 2, // 当前页两侧各显示几页
'end_size' => 1, // 首尾各显示几页
'type' => 'list',// 输出 <ul><li>,便于写样式
) );
type 可选 plain(默认,<a> 串)、list(<ul class="page-numbers">)、array(返回数组,供进一步处理)。做主题时一律用 list,样式更好控制。
第 3 步:带自定义筛选条件时保留查询串
页面上有排序下拉、价格筛选时,翻页必须带上这些参数,否则第 2 页筛选条件丢失:
echo paginate_links( array(
'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
'format' => '?paged=%#%',
'current' => $paged,
'total' => $q->max_num_pages,
'add_args' => array(
'orderby' => isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : '',
'min_price' => isset( $_GET['min_price'] ) ? absint( $_GET['min_price'] ) : '',
),
'add_fragment' => '',
'type' => 'list',
) );
add_args 里的值会被 urlencode 后拼接,但仍要自己先 sanitize_* 清理——add_args 只负责拼 URL,不做安全过滤。
第 4 步:主查询分页直接用 the_posts_pagination
主循环(不需要自定义查询)时不必手搓 paginate_links,核心提供了封装:
the_posts_pagination( array(
'mid_size' => 2,
'prev_text' => __( '上一页', 'mimangfei' ),
'next_text' => __( '下一页', 'mimangfei' ),
'screen_reader_text' => __( '文章导航', 'mimangfei' ),
) );
它内部读的是全局 $wp_query->max_num_pages,只适用于主循环。
第 5 步:文章内部分页用 wp_link_pages
一篇长文拆成多页(<!--nextpage--> 分割)时,用的是另一个函数:
// 在 single.php 的 the_content() 之后
wp_link_pages( array(
'before' => '<nav class="page-links"><span class="page-links__title">' . esc_html__( '分页:', 'mimangfei' ) . '</span>',
'after' => '</nav>',
'link_before' => '<span class="page-links__num">',
'link_after' => '</span>',
'next_or_number' => 'number',
'pagelink' => '%',
) );
注意:文章内部分页用的是 get_query_var( 'page' ),与列表分页的 paged 不是一回事,别混用。
第 6 步:修复第 2 页 404
自定义文章类型归档页出现”第 1 页正常、第 2 页 404″,多半是 posts_per_page 与后台「设置 → 阅读」的”每页文章数”冲突。解决方式是在 pre_get_posts 里统一数量:
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_post_type_archive( 'product' ) ) {
$query->set( 'posts_per_page', 12 );
}
} );
如果用的是自带分页的静态页面(把某个 Page 当”产品列表页”用),必须让主查询的 posts_per_page 与自定义查询一致,否则主查询判定”第 2 页不存在”直接抛 404。自定义查询本身的写法与常见误区见WP_Query 与主查询的正确用法。
常见踩坑
- 忘记把
paged传进WP_Query。 页码显示正常但内容一直是第 1 页,这是最高频的失误。 - 开了
no_found_rows => true。 它会跳过总数计算,max_num_pages恒为 1,分页只显示一页。分页场景必须关掉。 total用了$wp_query->max_num_pages。 自定义查询要用$q->max_num_pages。- 静态首页没处理
page参数。 静态首页上get_query_var('paged')恒为 0,必须回退到get_query_var('page')。 paginate_links的base写死。 站点换固定链接结构后分页链接全错。用get_pagenum_link( $big )动态生成。- 案例: 某外贸站做了个”全部案例”页面,用
new WP_Query查case类型,前台分页点击后跳转 404。排查发现:该页面是一个普通 Page,主查询只查出 1 条(这个页面自己),于是 WordPress 判定/cases/page/2/不存在,在模板执行前就抛了 404。修复方式不是改paginate_links,而是改用「归档页 +pre_get_posts控制数量」,或给该 Page 加一条重写规则,让主查询也按同样数量分页。判断依据就是:paginate_links只负责”生成链接”,页面存不存在由主查询说了算。归档页的固定链接与重写规则细节见自定义文章类型归档模板。 wp_reset_postdata()放在了分页之前。 会导致paginate_links拿到的max_num_pages出错,顺序应为:循环 →wp_reset_postdata()→ 分页(分页只依赖$q对象,不受影响,但依赖$post的其他函数会出错)。
总结
自定义查询分页的完整公式是三行:$paged 正确读取(paged 优先、page 兜底)→ paged 传进 WP_Query 且不开 no_found_rows → paginate_links 的 current 用 $paged、total 用 $q->max_num_pages、base 用 get_pagenum_link( $big ) 动态生成。主循环直接用 the_posts_pagination(),长文内部分页用 wp_link_pages()。出现 404 时先判断”是链接错了还是页面本身不存在”——后者要去改主查询的 posts_per_page,而不是改分页函数。
