本文约 4,947 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:点「下一篇」,跳到了完全不相干的栏目
文章底部那组「上一篇 / 下一篇」链接,本意是留住读者继续浏览。但如果你的站点既写 WordPress 教程又写主机评测,读者看完一篇教程点「下一篇」,很可能跳到一篇主机广告文——内容断层,跳出率飙升。默认行为是按发布时间排序取相邻文章,完全不考虑分类、标签或相关性。
另一个问题是样式:默认只有两个纯文字链接,窄屏下挤成一团,看不出是导航还是正文。很多人干脆装插件了事,但插件往往带来额外的 CSS 与查询。实际上 WordPress 自带的 previous_post_link() / next_post_link() 已经支持同分类过滤,剩下的只是补样式和补内容。本文给出完整做法,并说明什么时候该用「相关文章」替代上下篇导航。
原理分析:相邻文章的查询逻辑与参数
previous_post_link() 和 next_post_link() 底层都调用 get_adjacent_post(),它会构造一条 SQL:按 post_date(默认)或 ID 比较,取 post_type 为当前类型、post_status 为 publish、且排除了指定分类的一篇文章。参数签名如下:
| 参数 | 含义 | 常用值 |
|---|---|---|
$format |
输出模板,%link 是链接占位符 |
'%link' |
$link |
链接文字模板,%title 是标题占位 |
'%title' |
$in_same_term |
是否限定同一分类法下的同 term | true |
$excluded_terms |
排除的 term ID | '' 或数组 |
$taxonomy |
$in_same_term 为 true 时按哪个分类法 |
'category' |
关键点在第五个参数:默认分类法是 category,如果你的文章主要靠标签或自定义分类法(比如「教程系列」)组织,必须显式传入对应分类法名称,否则 in_same_term 会按默认的 category 过滤,结果可能是空的。
性能上,get_adjacent_post() 每次调用都会产生一条带 WHERE 与 ORDER BY 的查询,加上排除逻辑可能用到 LEFT JOIN。一页两次调用影响不大,但如果在循环里反复调用就会拖慢页面。有对象缓存时这些查询会被缓存,问题不明显。
还要注意「边界情况」:第一篇文章没有上一篇,最后一篇文章没有下一篇。默认会输出空字符串,导致布局缺一块。要么用 CSS 让另一侧占满,要么在两端补上推荐内容。
保姆级实操
- 确认组织维度:先想清楚读者最可能想看的是「同分类下一篇」还是「同标签下一篇」,据此决定
$taxonomy。 - 改成同分类导航:在
single.php里把默认调用换成带true参数的版本。 - 补样式:做成左右两块的卡片式布局,加标题前缀与箭头图标。
- 处理边界:无上一篇/下一篇时输出推荐内容或占位。
- 加缩略图:用
get_the_post_thumbnail()让导航更有点击欲。 - 清缓存后验证:在分类边界文章(每个分类的第一篇和最后一篇)上测试。
<?php
// single.php 里的上下篇导航,限定同一分类
$prev = get_previous_post( true, '', 'category' );
$next = get_next_post( true, '', 'category' );
?>
<nav class="post-nav" aria-label="文章导航">
<?php if ( $prev ) : ?>
<a class="post-nav__item post-nav__item--prev" href="<?php echo esc_url( get_permalink( $prev ) ); ?>">
<span class="post-nav__label">上一篇</span>
<span class="post-nav__title"><?php echo esc_html( get_the_title( $prev ) ); ?></span>
</a>
<?php endif; ?>
<?php if ( $next ) : ?>
<a class="post-nav__item post-nav__item--next" href="<?php echo esc_url( get_permalink( $next ) ); ?>">
<span class="post-nav__label">下一篇</span>
<span class="post-nav__title"><?php echo esc_html( get_the_title( $next ) ); ?></span>
</a>
<?php endif; ?>
</nav>
<?php
// 想保留默认函数、只改输出格式时,用过滤器统一加工
add_filter( 'previous_post_link', 'mimangfei_wrap_adjacent_link', 10, 5 );
add_filter( 'next_post_link', 'mimangfei_wrap_adjacent_link', 10, 5 );
function mimangfei_wrap_adjacent_link( $output, $format, $link, $post, $adjacent ) {
if ( ! $post ) {
return '';
}
$thumb = get_the_post_thumbnail( $post, 'thumbnail', array( 'class' => 'post-nav__thumb' ) );
$title = esc_html( get_the_title( $post ) );
$href = esc_url( get_permalink( $post ) );
return sprintf(
'<a class="post-nav__item post-nav__item--%1$s" href="%2$s">%3$s<span class="post-nav__title">%4$s</span></a>',
esc_attr( $adjacent ),
$href,
$thumb,
$title
);
}
.post-nav {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
margin: 2rem 0;
}
@media ( min-width: 600px ) {
.post-nav { grid-template-columns: repeat( 2, minmax( 0, 1fr ) ); }
.post-nav__item--next { text-align: right; }
}
.post-nav__item {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px 16px;
border: 1px solid var( --border, #d0d7de );
border-radius: 8px;
text-decoration: none;
color: inherit;
min-height: 72px;
}
.post-nav__label {
font-size: 0.8rem;
color: var( --muted, #656d76 );
}
.post-nav__title {
font-weight: 600;
line-height: 1.4;
/* 标题过长时截断,避免卡片高度失控 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
常见踩坑
in_same_term传 true 但没传$taxonomy:默认按 category 过滤,如果你的文章没分类(只有标签),导航会直接消失。显式传分类法名。- 文章属于多个分类:
get_adjacent_post()的逻辑是「属于其中任一分类」,不是「属于全部分类」,结果仍可能跨栏目。分类体系要设计成互斥的单分类结构。 - 案例:某站把站点从「按时间组织」改成「按系列组织」,上线后发现系列的第一篇文章完全没有「上一篇」。原因是他们用了
previous_post_link( '%link', '%title', true ),同分类过滤后第一篇确实没有上一篇,页面只剩右侧一块,布局塌陷。最后改成:无上一篇时输出「返回系列目录」的链接,无下一篇时输出该系列的最新一篇,既填满布局又提升连续性。教训:边界情况必须在设计阶段就考虑。 - 标题过长撑破卡片:长标题让两侧卡片高度差很大。用
-webkit-line-clamp限制两行。 - 密码保护文章的标题泄漏:
get_the_title()对加密文章会加「受保护:」前缀,但链接点进去要密码。可用post_password_required()判断后跳过或标注。 - 缓存导致导航不变:全页缓存会缓存上下篇 HTML,发新文章后旧页面的「下一篇」还指向老文章。要么清缓存,要么把这块做成 AJAX 加载。
- 与 SEO 插件重复输出:部分主题/插件会在内容下方再插一组导航,出现两组重复链接。检查
the_content过滤器里是否有人挂载了导航。 - 性能上无节制扩展:为了「推荐 6 篇相关文章」在导航区又跑了 3 条
WP_Query。相关文章请用一次查询 +post__not_in,别循环调用相邻文章函数。
总结
上下篇导航的判断标准是:读者点完之后,是否还在同一个内容脉络里。如果答案是「经常跳到不相关栏目」,就该改成同分类/同标签导航,或直接换成相关文章推荐。行动清单:确定组织维度 → 用 get_previous_post( true, '', '分类法名' ) 限定范围 → 做成卡片式布局并限制标题行数 → 处理首末篇的边界情况 → 加密文章与草稿要排除 → 清缓存后在分类边界文章上验证。导航做好后,配合 主题面包屑与结构化数据配合 把层级关系一并表达清楚,读者和搜索引擎都能更好地理解站点结构。
