搜索全站
主题 16 分钟阅读

自定义文章类型归档模板:archive-{post_type}.php 与刷新规则

register_post_type 之后归档页 404?讲清 has_archive 参数、archive-{post_type}.php 的创建与优先级、重写规则什么时候需要刷新,以及三种安全的 flush 时机。

U
UFO_PF 2026-02-05 · 阅读 16 分钟
自定 主题
阅读提示
本文约 6,199 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:文章类型建好了,归档页一直是 404

用插件建了个”产品”类型,后台能看到、能发布、详情页也能打开,唯独 /product/ 这个列表页 404。反复检查固定链接设置,点了好几次保存,偶尔好一次,过两天又 404。

这是自定义文章类型最经典的三个坑叠加:has_archive 没开、归档模板文件名不对、重写规则在错误的时机被刷新。

原理分析:归档页存在需要两个条件同时满足

条件一:文章类型声明了归档(has_archive)。

register_post_type( 'product', array(
    'public'       => true,
    'has_archive'  => true,          // 决定有没有 /product/ 这个归档页
    'rewrite'      => array( 'slug' => 'product', 'with_front' => false ),
    'show_in_rest' => true,
) );

has_archive 可以是 true(用 rewrite['slug'])也可以直接是字符串(指定归档别名)。它是重写规则生成的开关——没有它,核心根本不会为这个类型生成归档规则,/product/ 在 parse_request 阶段就匹配失败,直接抛 404,模板文件做得再对也没用。

条件二:重写规则里有对应条目。规则存在 wp_options 的 rewrite_rules 这个 option 里,是一张巨大的 正则 => query_vars 映射表:

product/?$                    → post_type=product
product/page/([0-9]{1,})/?$   → post_type=product&paged=$matches[1]
product/([^/]+)/?$            → post_type=product&name=$matches[1]

这张表在 register_post_type() 时不会自动写入数据库,只在刷新时重建。所以”注册完之后必须刷新一次”是硬要求。

刷新规则的三种方式:

方式 时机 代价
后台「设置 → 固定链接」点保存 手动 无,最安全
wp rewrite flush 命令行 无
flush_rewrite_rules() 代码 昂贵,重建整张表并写库

flush_rewrite_rules() 绝不能挂在 init 上——那等于每次请求都重建整张规则表,站点流量一上来数据库就崩。这是 Codex 反复强调的红线。

条件三(模板层):文件名必须为 archive-{post_type}.php,其中 {post_type} 是 register_post_type() 的第一个参数,且必须全小写。

保姆级实操

第 1 步:正确注册文章类型

add_action( 'init', function () {
    $labels = array(
        'name'          => '产品',
        'singular_name' => '产品',
        'add_new_item'  => '添加产品',
        'edit_item'     => '编辑产品',
        'all_items'     => '所有产品',
        'search_items'  => '搜索产品',
    );

    register_post_type( 'product', array(
        'labels'       => $labels,
        'public'       => true,
        'has_archive'  => 'products',        // 归档别名用复数,与详情页 slug 区分
        'rewrite'      => array( 'slug' => 'product', 'with_front' => false ),
        'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'show_in_rest' => true,              // 古腾堡 + REST API 支持
        'menu_icon'    => 'dashicons-cart',
        'taxonomies'   => array( 'product_cat' ),
    ) );
} );

小贴士:has_archive 用复数 products、rewrite['slug'] 用单数 product,可以让列表页 /products/ 与详情页 /product/xxx/ 一眼可辨,避免与同名的 Page 撞车。

第 2 步:在正确的时机刷新规则

三种安全写法:

# 方式一:命令行(推荐,最可控)
wp rewrite flush
// 方式二:插件激活时刷新
register_activation_hook( __FILE__, function () {
    myplugin_register_post_types();   // 先注册
    flush_rewrite_rules();            // 再刷新
} );

register_deactivation_hook( __FILE__, function () {
    flush_rewrite_rules();            // 卸载插件后清掉规则
} );
// 方式三:主题启用时刷新(类型注册写在主题里时用这个)
add_action( 'after_switch_theme', function () {
    flush_rewrite_rules();
} );

wp rewrite flush 之后用 wp rewrite list --match="/products" 抽查规则是否生成。

第 3 步:创建归档模板

<?php
/** archive-product.php —— 产品归档页 */
get_header();
?>
<main id="primary" class="site-main archive-product">
    <header class="page-header">
        <?php
        the_archive_title( '<h1 class="page-title">', '</h1>' );
        the_archive_description( '<div class="archive-description">', '</div>' );
        ?>
    </header>

    <?php if ( have_posts() ) : ?>
        <div class="product-grid">
            <?php
            while ( have_posts() ) :
                the_post();
                get_template_part( 'template-parts/content/content', 'product' );
            endwhile;
            ?>
        </div>

        <?php the_posts_pagination( array(
            'mid_size'  => 2,
            'prev_text' => __( '上一页', 'mimangfei' ),
            'next_text' => __( '下一页', 'mimangfei' ),
        ) ); ?>

    <?php else : ?>
        <?php get_template_part( 'template-parts/content/content', 'none' ); ?>
    <?php endif; ?>
</main>
<?php
get_footer();

the_archive_title() 会输出 post_type_archive_title() 的结果,前置文案可用 post_type_archive_title 相关过滤器调整:

add_filter( 'post_type_archive_title', function ( $title ) {
    if ( 'product' === get_query_var( 'post_type' ) ) {
        return '产品中心';
    }
    return $title;
} );

第 4 步:控制归档页的每页数量与排序

归档页数量必须在主查询层面改,不能在模板里 query_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 );
        $query->set( 'orderby', 'menu_order title' );
        $query->set( 'order', 'ASC' );
    }
} );

第 5 步:按分类法再细分模板

给产品分类单独做页时,模板层级是:

taxonomy-product_cat-phone.php   →  taxonomy-product_cat.php  →  taxonomy.php  →  archive.php  →  index.php

归档页里常用的查询对象获取方式:

$term = get_queried_object();        // 归档页上是 WP_Term 或 post type 对象
if ( is_post_type_archive() ) {
    $post_type = get_query_var( 'post_type' );
    $obj       = get_post_type_object( $post_type );
    echo esc_html( $obj->labels->name );
}

分类法归档模板的命名与优先级规则见分类模板 category-slug.php 用法。

第 6 步:验证清单

wp post-type list --fields=name,public,has_archive,rewrite
wp rewrite list --match="/products"
wp eval 'var_dump( is_post_type_archive( "product" ) );'

常见踩坑

  1. 漏了 has_archive。 详情页正常,归档页 404 且没有任何提示。
  2. register_post_type 挂在 init 之后。 规则生成早于类型注册,刷新也没用。
  3. archive-posttype.php 用了注册时的标签名。 必须用第一个参数($post_type),如 product 而非 产品。
  4. 把 flush_rewrite_rules() 挂在 init 上。 每次请求重建规则表,高并发下数据库压力剧增,是官方明令禁止的写法。
  5. 归档别名与某个页面别名相同。 /products/ 既有 Page 又有归档规则时,规则表里排在前面的胜出,表现随机。改名或改 with_front 规避。
  6. 案例: 某站用代码注册了 case 类型却在调试时把 flush_rewrite_rules() 写进了 init 钩子里,本地一切正常。上线后日均 PV 三万,wp_options 里 rewrite_rules 每次访问都被重写,数据库写入量飙升,站点在流量高峰直接 502。排查靠 Query Monitor 发现每次请求都有一次 UPDATE wp_options。改成部署脚本里执行 wp rewrite flush 后恢复正常。教训是:刷新规则是一次性动作,永远不要放进常驻钩子。
  7. show_in_rest 没开导致区块编辑器不可用。 文章类型在前台正常,但后台编辑时回退到经典编辑器,REST 接口返回 404。
  8. 换了主题后归档页 404。 类型注册被写在了旧主题的 functions.php 里,换主题即失效,应按functions.php 与插件职责划分迁到插件里。

总结

归档页出问题按这个顺序查:wp post-type list 确认 has_archive 是否为真 → wp rewrite list 确认规则是否已生成(没有就 wp rewrite flush)→ 确认模板文件名是 archive-{post_type}.php 且 post_type 全小写 → 用 is_post_type_archive() 确认查询条件。三条红线记住:flush_rewrite_rules() 只放在激活钩子或命令行里,绝不放 init;文章类型注册代码放插件不放主题;归档页数量用 pre_get_posts 改,不在模板里另起查询。

U
UFO_PF作者

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

留下你的想法

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