搜索全站
安装部署 16 分钟阅读

用 WordPress 搭建网址导航站完整教程:分类、卡片、跳转与外链

想做一个自己的网址导航站?本文从数据模型设计讲起,给出站点 CPT 与分类法注册、卡片布局模板、/go 跳转中间页实现,以及外链 rel 属性与统计的完整代码,可直接照抄上线。

U
UFO_PF 2024-11-21 · 阅读 16 分钟
用 安装部署
阅读提示
本文约 6,274 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:导航站看似简单,做起来全是细节

网址导航是最”看起来容易”的站点类型:不就是一堆链接排成格子吗?真动手就会发现一堆问题——链接放在文章里还是独立类型?分类层级怎么定?点击后是直接跳走还是走中间页?外链要不要加 nofollow?站点截图怎么自动生成?死链怎么发现?

很多人第一版是把所有链接写死在一个页面里。短期能用,等链接超过一百条就彻底失控:改一个 URL 要翻半天,新增分类要重排整页,无法做筛选也无法统计点击。正确的做法是把”一个网址”当成一条结构化数据来管理,用自定义文章类型承载,再配一个分类法做分组。这样后台就能像管理文章一样管理链接,还能复用 WordPress 现成的查询、缓存与权限体系。

原理分析:导航站的数据模型

一个导航条目至少包含这些字段:站点名称、网址、简介、分类、图标/截图、排序权重、是否推荐、点击次数。其中”分类”应当做成分类法(taxonomy)而不是自定义字段,因为分类法自带归档页与 URL,利于 SEO;”点击次数”和”排序权重”是典型的自定义字段。

跳转方式有三种,取舍如下:

方式 优点 缺点
直接外链 最简单,无中间跳转 无法统计,外链权重直接传递
/go/id 中间页 可统计、可加提示 多一次跳转,需处理缓存
JS 跳转 可拦截、可延迟 爬虫不易识别,体验略差

推荐用 /go/{id} 中间页 + wp_safe_redirect(),既能统计点击,又能在跳转前做一层安全提示(比如提示”即将离开本站”)。注意这里必须用 wp_safe_redirect() 而非 wp_redirect(),前者会校验目标域名是否在允许列表内,可避免开放重定向漏洞。

保姆级实操

步骤 1:注册站点 CPT 与导航分类法。

add_action( 'init', 'mimangfei_register_site_cpt' );
function mimangfei_register_site_cpt() {
    register_post_type( 'nav_site', array(
        'labels'      => array(
            'name'          => '网址收录',
            'singular_name' => '网址',
            'add_new_item'  => '添加网址',
        ),
        'public'       => true,
        'has_archive'  => true,
        'rewrite'      => array( 'slug' => 'sites', 'with_front' => false ),
        'supports'     => array( 'title', 'thumbnail' ),
        'show_in_rest' => true,
        'menu_icon'    => 'dashicons-admin-links',
    ) );

    register_taxonomy( 'nav_cat', 'nav_site', array(
        'labels'       => array(
            'name'          => '网址分类',
            'singular_name' => '分类',
        ),
        'public'       => true,
        'hierarchical' => true,
        'rewrite'      => array( 'slug' => 'sites/cat' ),
        'show_in_rest' => true,
    ) );
}

步骤 2:加自定义字段(网址、简介、推荐)。

add_action( 'add_meta_boxes_nav_site', function () {
    add_meta_box( 'nav_site_url', '站点信息', function ( $post ) {
        wp_nonce_field( 'save_nav_site', 'nav_site_nonce' );
        $url  = get_post_meta( $post->ID, '_site_url', true );
        $desc = get_post_meta( $post->ID, '_site_desc', true );
        echo '<p><label>网址:<input type="url" name="site_url" value="' . esc_url( $url ) . '" style="width:80%"></label></p>';
        echo '<p><label>简介:<textarea name="site_desc" rows="3" style="width:80%">' . esc_textarea( $desc ) . '</textarea></label></p>';
    }, 'nav_site', 'normal', 'high' );
} );

add_action( 'save_post_nav_site', function ( $post_id ) {
    if ( ! isset( $_POST['nav_site_nonce'] )
        || ! wp_verify_nonce( $_POST['nav_site_nonce'], 'save_nav_site' ) ) {
        return;
    }
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    if ( isset( $_POST['site_url'] ) ) {
        update_post_meta( $post_id, '_site_url', esc_url_raw( $_POST['site_url'] ) );
    }
    if ( isset( $_POST['site_desc'] ) ) {
        update_post_meta( $post_id, '_site_desc', sanitize_text_field( $_POST['site_desc'] ) );
    }
} );

步骤 3:注册 /go/{id} 跳转规则并计数。

add_action( 'init', function () {
    add_rewrite_tag( '%go_id%', '([0-9]+)' );
    add_rewrite_rule( '^go/([0-9]+)/?$', 'index.php?go_id=$matches[1]', 'top' );
} );

add_action( 'template_redirect', function () {
    $id = (int) get_query_var( 'go_id' );
    if ( ! $id ) {
        return;
    }
    $url = get_post_meta( $id, '_site_url', true );
    if ( ! $url ) {
        wp_safe_redirect( home_url() );
        exit;
    }
    $clicks = (int) get_post_meta( $id, '_site_clicks', true );
    update_post_meta( $id, '_site_clicks', $clicks + 1 );

    wp_safe_redirect( $url, 302 );
    exit;
} );

步骤 4:写卡片模板。 在 archive-nav_site.php 里按分类循环输出。

<?php
$cats = get_terms( array( 'taxonomy' => 'nav_cat', 'hide_empty' => true ) );
foreach ( $cats as $cat ) :
    $sites = get_posts( array(
        'post_type'      => 'nav_site',
        'posts_per_page' => -1,
        'meta_key'       => '_site_order',
        'orderby'        => array( 'meta_value_num' => 'DESC', 'title' => 'ASC' ),
        'tax_query'      => array( array(
            'taxonomy' => 'nav_cat',
            'field'    => 'term_id',
            'terms'    => $cat->term_id,
        ) ),
    ) );
    ?>
    <section class="nav-block">
        <h2><?php echo esc_html( $cat->name ); ?></h2>
        <div class="nav-grid">
        <?php foreach ( $sites as $s ) :
            $url  = get_post_meta( $s->ID, '_site_url', true );
            $desc = get_post_meta( $s->ID, '_site_desc', true );
            ?>
            <a class="nav-card" href="<?php echo esc_url( home_url( '/go/' . $s->ID ) ); ?>"
               target="_blank" rel="noopener">
                <span class="nav-card-icon">
                    <?php echo get_the_post_thumbnail( $s->ID, 'thumbnail' ); ?>
                </span>
                <strong><?php echo esc_html( get_the_title( $s ) ); ?></strong>
                <small><?php echo esc_html( $desc ); ?></small>
            </a>
        <?php endforeach; ?>
        </div>
    </section>
    <?php
endforeach;

步骤 5:刷新重写规则。 注册完 add_rewrite_rule 必须刷新一次,否则 /go/123 会 404。

wp rewrite flush --hard --path=/var/www/mimangfei

步骤 6:处理外链的 rel 属性。 出站链接统一加 noopener(安全)与 nofollow(不传递权重),可在输出卡片时直接写死,也可以用 wp_targeted_link_rel 过滤器统一处理。

常见踩坑

  • 加了重写规则却没刷新,/go/123 始终 404。
  • 用 wp_redirect() 跳转到外部域名,触发开放重定向风险(应改用 wp_safe_redirect())。
  • 卡片里放了 target="_blank" 却没加 rel="noopener",新页面可通过 window.opener 操作原页面。
  • 缓存插件把 /go/ 路径也缓存了,点击统计全部失效(需把 go 加入缓存排除规则)。
  • 站点图标直接用对方 favicon 地址,对方改地址后全站图标裂开(应下载到媒体库)。
  • 案例:某导航站上线时图省事把外链写成 href="https://目标站",半年后发现自己的搜索流量被一个采集站抢走——采集站整页搬走了导航内容,连外链一起搬。后来改成 /go/id 中间页 + nofollow,采集站搬走的就是一堆指向自己站内的 /go/ 路径,反而给原站带来了入口。
  • 分类层级做到三级,URL 变成 /sites/cat/a/b/c,又长又难收录。
  • 死链从不检查,几百条链接里三成已经打不开。

总结

导航站是否做对,看两件事:后台能否在 30 秒内完成”新增一个网址并归类”,以及前台点击是否都能被统计到。行动清单:① 网址用独立 CPT + 层级分类法;② 分类层级不超过两级;③ 外链走 /go/id 中间页;④ 用 wp_safe_redirect() 而非 wp_redirect();⑤ 图标本地化;⑥ 每月跑一次死链检查。数据量上来后接着看 导航站链接批量导入、导航站收录与 SEO 与 WordPress 自定义字段教程。

U
UFO_PF作者

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

留下你的想法

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