搜索全站
建站 15 分钟阅读

WordPress 文档站与 Wiki:层级、搜索与版本管理的实现方案

文档站最难的是"让人三秒内找到那一段"。本文讲清文档 CPT 的层级设计、如何让搜索只搜文档、以及用修订版本做版本切换的做法。

U
UFO_PF 2026-09-23 · 阅读 15 分钟
Wo 建站
阅读提示
本文约 5,661 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:文档站的用户是带着问题来的

文档站和博客的访问行为完全不同。博客读者是”逛”,文档读者是”找”——他遇到一个具体报错,希望在三秒内定位到那一段说明。这意味着文档站的三个技术指标和博客不一样:层级必须反映心智模型、搜索必须能命中代码块里的内容、同一份文档的不同版本必须能并存。如果这三点没做好,用户会直接去搜搜索引擎,然后落在你的竞争对手站点上。

原理分析:文档的模型是”树 + 标签 + 版本”

文档天然是树形结构:产品 → 模块 → 具体页面。WordPress 的 hierarchical CPT 能直接表达这棵树,配合 wp_list_pages() 或 get_pages() 就能渲染侧边栏目录,不需要额外的导航插件。

但树形结构有两个天然缺陷:同一篇文档可能属于多个模块(交叉引用),以及搜索时树形结构帮不上忙。前者用分类法补(一个分类法做”产品线”,一个做”标签”),后者需要单独处理搜索。

版本管理是文档站的独有需求。WordPress 原生的”修订版本”(revision)存的是同一篇文档的历次修改,天然适合做”版本快照”,但默认不对外展示。做法是:用分类法 doc_version 区分主版本(如 v2.x / v1.x),同一篇文档按版本存为不同文章,互相用 _version_group meta 关联;修订版本则用于追溯单次修改。

需求 实现方式 说明
目录树 hierarchical CPT post_parent + menu_order
交叉分类 两个分类法 产品线 + 标签
站内搜索 pre_get_posts + 权重 标题权重高于正文
版本切换 分类法 + 关联 meta 前端提供版本下拉

保姆级实操

步骤 1:注册层级文档类型。 关键参数:hierarchical => true、publicly_queryable、show_in_rest(便于做前端搜索)。

add_action( 'init', 'mimangfei_register_doc_type' );
function mimangfei_register_doc_type() {
    register_post_type( 'doc', array(
        'labels'       => array( 'name' => '文档', 'singular_name' => '文档' ),
        'public'       => true,
        'hierarchical' => true,
        'supports'     => array( 'title', 'editor', 'page-attributes', 'revisions', 'thumbnail' ),
        'rewrite'      => array( 'slug' => 'docs', 'hierarchical' => true ),
        'show_in_rest' => true,
        'has_archive'  => true,
    ) );

    register_taxonomy( 'doc_version', array( 'doc' ), array(
        'labels'       => array( 'name' => '文档版本', 'singular_name' => '文档版本' ),
        'public'       => true,
        'hierarchical' => true,
        'show_in_rest' => true,
        'rewrite'      => array( 'slug' => 'docs/version' ),
    ) );
}

步骤 2:让搜索限定在文档内。 文档站最好给一个”文档内搜索”,避免结果混杂博客文章。用一个 post_type 参数即可,配合 s 参数走原生搜索。

add_filter( 'pre_get_posts', 'mimangfei_doc_search' );
function mimangfei_doc_search( $query ) {
    if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
        return;
    }
    if ( isset( $_GET['scope'] ) && 'docs' === $_GET['scope'] ) {
        $query->set( 'post_type', 'doc' );
        $query->set( 'posts_per_page', 20 );
    }
}

步骤 3:提升标题命中权重。 WordPress 默认搜索只按相关性排序,标题命中和正文命中权重接近。用 posts_search 过滤器改写 SQL,把 post_title 的命中前置。

add_filter( 'posts_search', 'mimangfei_search_weight', 10, 2 );
function mimangfei_search_weight( $search, $query ) {
    global $wpdb;
    if ( ! $query->is_search() || empty( $query->get( 's' ) ) ) {
        return $search;
    }
    $term = $wpdb->esc_like( $query->get( 's' ) );
    $extra = $wpdb->prepare(
        " OR ({$wpdb->posts}.post_title LIKE %s)",
        '%' . $term . '%'
    );
    return $search . $extra;
}

步骤 4:渲染侧边栏目录。 用 wp_list_pages() 传入 post_type 参数,它支持层级 CPT,会自动按 menu_order 排序并输出嵌套 <ul>。

function mimangfei_doc_sidebar( $doc_id ) {
    $ancestors = get_post_ancestors( $doc_id );
    $root      = $ancestors ? end( $ancestors ) : $doc_id;

    return wp_list_pages( array(
        'post_type'    => 'doc',
        'child_of'     => $root,
        'title_li'     => '',
        'echo'         => false,
        'sort_column'  => 'menu_order',
        'link_before'  => '<span>',
        'link_after'   => '</span>',
    ) );
}

步骤 5:做版本切换。 同一篇文档的不同版本用 _version_group 关联,切换时找到同组的其他版本并重定向。

function mimangfei_switch_version( $doc_id, $target_version ) {
    $group = get_post_meta( $doc_id, '_version_group', true );
    if ( ! $group ) {
        return new WP_Error( 'no_group', '该文档没有版本组' );
    }
    $found = get_posts( array(
        'post_type'      => 'doc',
        'post_status'    => 'publish',
        'posts_per_page' => 1,
        'meta_query'     => array(
            array( 'key' => '_version_group', 'value' => $group ),
        ),
        'tax_query'      => array(
            array(
                'taxonomy' => 'doc_version',
                'field'    => 'slug',
                'terms'    => sanitize_title( $target_version ),
            ),
        ),
    ) );
    if ( empty( $found ) ) {
        return new WP_Error( 'not_found', '该版本暂无对应文档' );
    }
    return get_permalink( $found[0]->ID );
}

步骤 6:用修订版本做变更追溯。 文档底部展示”最近更新时间”与”本次更新了什么”,数据来自修订版本。

function mimangfei_doc_revisions( $doc_id, $limit = 5 ) {
    $revisions = wp_get_post_revisions( $doc_id, array(
        'posts_per_page' => $limit,
        'orderby'        => 'date',
        'order'          => 'DESC',
    ) );
    $out = array();
    foreach ( $revisions as $rev ) {
        $out[] = array(
            'time'   => get_the_modified_date( 'Y-m-d H:i', $rev->ID ),
            'author' => get_the_author_meta( 'display_name', $rev->post_author ),
        );
    }
    return $out;
}

步骤 7:控制修订版本数量。 文档频繁更新会产生大量修订,在 wp-config.php 里限制最多保留 10 条。

define( 'WP_POST_REVISIONS', 10 );

步骤 8:用现成插件兜底。 BetterDocs(WPDeveloper)与 weDocs(weDevs)都是官方目录里的成熟文档插件,自带即时搜索、目录与反馈按钮,自己写之前先评估是否能满足。

常见踩坑

  1. 层级 URL 没开 hierarchical rewrite。 子文档 URL 不带父路径,导致 /docs/a/b/ 无法访问。
  2. 搜索结果混着博客文章。 用户搜报错信息却返回三篇营销文章,体验极差。
  3. 侧边栏目录全量渲染。 文档上千篇时,wp_list_pages 不限制 child_of 会生成巨大 DOM,应只渲染当前分支。
  4. 案例: 某 SaaS 文档站把版本信息写在文章标题里(”xxx v1″”xxx v2″),两年后积累 400 多篇同名文档,编辑自己都分不清哪个是最新版,搜索引擎也把两个版本都收录了。改成 doc_version 分类法 + _version_group 关联后,只保留最新版可被索引,旧版本加 noindex。
  5. 修订版本无限增长。 数据库膨胀到几 GB,后台列表变慢。
  6. 代码块内容搜不到。 默认搜索只查 post_content,被代码块包裹的报错信息其实在 HTML 里,需要额外处理。
  7. 文档 CPT 未加入站点地图。 用 Yoast 或 Rank Math 时要确认 doc 类型在站点地图中是开启状态。

总结

文档站的三个硬指标:目录只渲染当前分支、搜索能限定范围且标题优先、旧版本明确加 noindex。层级用原生 hierarchical CPT,交叉分类用分类法,版本用分类法 + 关联 meta,修订版本限制在 10 条以内。判断标准:让一个不熟悉站点的人找一段报错说明,超过 30 秒就是结构有问题。资讯类站点的信息流组织方式见杂志资讯站首页设计;专题聚合的做法见专题聚合页实现。

U
UFO_PF作者

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

留下你的想法

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