本文约 4,699 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:改一次菜单,流量掉了一半
有个做 SaaS 工具站的读者反馈:设计师把顶部导航从纯文字改成图片 logo + 图标,同时把三级下拉改成 JS 动态加载,上线后两周内几个核心功能页的自然流量下降近 50%。内容没动、外链没动,唯一变化的就是导航。原因很直接——原来出现在 HTML 里的 20 条菜单链接,变成了 JS 运行时才注入的 DOM,Googlebot 在第一次抓取时看不到它们,这些页面的内部入链数瞬间从全站几百条降到个位数。
导航菜单是全站权重最高的内链区域:它出现在每一个页面上,向搜索引擎持续声明”哪些页面最重要”。它的设计失误,代价远高于正文里少加几个链接。
原理分析:菜单为什么能决定权重分布
搜索引擎判断页面重要性时,内部入链的数量与位置都是信号。导航链接的特点是覆盖全站:一个有 500 个页面的站点,顶部菜单放 6 个链接,就意味着这 6 个目标页各自获得约 500 条内部入链,是普通文章的几十倍。
由此可以推出三条设计原则:
| 原则 | 说明 | 反例 |
|---|---|---|
| 少而精 | 一级菜单控制在 5–7 项 | 塞 20 项稀释权重 |
| 扁平化 | 商业页深度 ≤ 3 层 | 隐藏在三级下拉 |
| HTML 可见 | 服务端输出 <a href> |
JS 动态注入 |
另外两个容易被忽略的点:一是锚文本,菜单项文字就是锚文本,写”产品中心”比写”更多”有效得多;二是移动端可访问性,Google 采用移动优先索引,若移动端菜单需要点击汉堡按钮后才由 JS 生成 DOM,虽然 Google 能执行 JS,但在渲染队列紧张时仍可能漏抓,稳妥做法是让菜单链接始终存在于 HTML 中,用 CSS 控制显隐。
保姆级实操
步骤 1:注册并使用导航菜单位置。 主题的 functions.php 里注册位置,header.php 里输出。
<?php
// 1) 注册菜单位置
add_action('after_setup_theme', function () {
register_nav_menus([
'primary' => '主导航',
'footer' => '页脚导航',
'mobile' => '移动端导航',
]);
});
// 2) 在 header.php 输出
wp_nav_menu([
'theme_location' => 'primary',
'container' => 'nav',
'container_class'=> 'site-nav',
'container_aria_label' => '主导航', // WP 5.5+ 支持
'menu_class' => 'nav-list',
'depth' => 2, // 限制层级,避免过深
'fallback_cb' => false, // 未设置菜单时不回退到 wp_page_menu
]);
步骤 2:精简菜单输出的 HTML。 WordPress 默认的 Walker_Nav_Menu 会输出大量 menu-item-123、current-menu-item 等类名和 id,页面体积膨胀且不利于缓存命中。自定义 Walker 可去掉冗余属性。
<?php
// 精简版 Walker:只保留必要属性,去掉冗余 id/class
class MMM_Clean_Walker extends Walker_Nav_Menu {
public function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
$classes = empty($item->classes) ? [] : (array) $item->classes;
$is_current = in_array('current-menu-item', $classes, true);
$output .= sprintf(
'<li class="nav-item%s"><a href="%s"%s>%s</a>',
$is_current ? ' is-active' : '',
esc_url($item->url),
$item->target ? ' target="' . esc_attr($item->target) . '" rel="noopener"' : '',
esc_html($item->title)
);
}
public function end_el(&$output, $item, $depth = 0, $args = null) {
$output .= "</li>\n";
}
}
调用时加上 'walker' => new MMM_Clean_Walker(), 即可。
步骤 3:规划层级。 一级菜单 5–7 项:首页、产品/服务、解决方案、博客、关于我们、联系我们。核心商业页直接放一级;细分页面放二级下拉,但要确保二级链接同样出现在 HTML 里,而不是 hover 后才由 AJAX 拉取。
步骤 4:给菜单项加语义化标记。 大型站点可在导航上叠加 Schema.org 的 SiteNavigationElement,让结构化数据更完整。
<?php
// 为导航容器补充结构化数据(JSON-LD 方式,输出在 wp_footer)
add_action('wp_footer', function () {
if (!has_nav_menu('primary')) return;
$locations = get_nav_menu_locations();
$items = wp_get_nav_menu_items($locations['primary']);
if (!$items) return;
$elements = array_map(fn($i) => [
'@type' => 'SiteNavigationElement',
'name' => $i->title,
'url' => $i->url,
], $items);
$json = wp_json_encode([
'@context' => 'https://schema.org',
'@graph' => $elements,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo '<script type="application/ld+json">' . $json . '</script>';
});
步骤 5:移动端与可访问性。 汉堡按钮必须带 aria-expanded、aria-controls,并且菜单链接在 DOM 中始终存在,仅用 CSS 的 display 控制显示。
<nav class="site-nav" aria-label="主导航">
<button class="nav-toggle" aria-expanded="false" aria-controls="nav-list-1"
aria-label="展开导航菜单">菜单</button>
<ul id="nav-list-1" class="nav-list" hidden>
<li class="nav-item"><a href="/services/">WordPress 建站服务</a></li>
<li class="nav-item"><a href="/blog/">技术博客</a></li>
</ul>
</nav>
步骤 6:验证。 禁用浏览器 JS 后刷新首页,确认菜单链接都在源码里;再用 Screaming Frog 看各页面的入链数分布,确认商业页进入前五。菜单层级与内链策略的配合可参考 WordPress 内部链接优化。
常见踩坑
- 案例: 某站把”产品”菜单做成 hover 后 AJAX 拉取子分类,HTML 里只剩一个
<span>产品</span>。两个月后 8 个子分类页全部跌出前 100 名。改成服务端直接输出<a href>列表后,六周内恢复原有排名。 - 一级菜单塞 15 个以上条目,权重被稀释,且移动端折叠成一团,用户体验与 SEO 双输。
- 菜单链接用
#或javascript:void(0)占位,点击才跳转,爬虫拿到的是死链。任何可点击项都必须是真实<a href>。 - 只做桌面端菜单,移动端用完全不同的 JS 组件且链接更少,移动优先索引下 Google 只看移动端,等于少了一半内链。
- 菜单锚文本全部是”首页””产品””更多”这类泛词,没有传递任何主题信号。
- 页脚与顶部菜单用同一个菜单位置,导致全站每个页面出现两套相同链接,内链重复度过高,可配合 网站页脚链接优化。
- 改版时删除旧菜单却没有给已下线 URL 做重定向,形成大量 404,需配合 301 重定向设置教程 处理。
总结
导航菜单的设计判断标准:核心商业页是否在一级或二级菜单中、菜单链接是否存在于服务端 HTML、锚文本是否包含业务关键词、移动端 DOM 是否与桌面端一致。行动清单为——限制一级菜单 5–7 项、核心页压到二级以内、用自定义 Walker 精简 HTML、移动端链接常驻 DOM、叠加 SiteNavigationElement 结构化数据。菜单是站点里最便宜也最有效的内链资源,值得在每次改版时当作一级事项来对待。
