3步搞定网站开发如何共用菜单栏实战案例
刚接手福建泉州一家外贸企业的官网项目,客户指着两个域名问我:为什么这两个站的导航栏长得一样,代码却各写了一套?改个菜名得改两遍,太蠢了。那一刻我意识到,很多新手对网站开发如何共用菜单栏的理解还停留在“复制粘贴”阶段,完全没搞懂域名和服务器背后的逻辑关系。别急,今天咱们不聊虚的,直接拿我最近做的一个真实实战案例拆解,让你明白怎么让多个子站或主站共享同一套导航,既省代码又方便后期维护。
需求分析与痛点拆解
很多老板觉得“共用菜单栏”就是技术炫技,其实它解决的是运营效率问题。想象一下,你公司在福州有主站 www.example.com,在厦门有个分店站 xm.example.com,还有几个独立的项目站。如果每个站的 <nav> 标签都单独写,今天调整菜单顺序,明天增加“联系我们”链接,你需要登录至少三台不同的后台去改。一旦漏改一个,用户点击404,品牌信任度直接掉线。
更头疼的是,很多新手分不清“静态资源”和“动态数据”。菜单栏看起来简单,但它往往包含当前页面的高亮状态(Active State)。如果强行用 JS 去动态加载,加载速度慢,首屏渲染会被阻塞,SEO 权重也会受影响。真正的实战案例中,我们追求的是一种“无感”的共用:用户感觉不到切换,开发者维护时只改一处。
这里有个常见的误区:很多人以为共用菜单必须依赖后端 API 接口。其实对于大多数企业站、展示型网站来说,利用前端模块化(Module System)配合构建工具,就能实现轻量级、高性能的菜单共享。我们要做的,是把菜单代码从各个页面的 HTML 中剥离出来,变成一个独立的、可复用的组件或片段。
环境准备与技术选型
在动手之前,你得把地基打好。很多小白卡在“我的服务器支持吗?”这个问题上。其实,只要你的服务器支持 Node.js 环境,或者你愿意在本地构建后上传静态文件,这事儿就能办成。
为什么推荐 Vite 或 Webpack? 因为现代前端开发讲究“一次编写,多处运行”。我们不用传统的 jQuery 动态插入 HTML,那样太笨重。我们用 ES Modules(ESM)语法,让菜单成为一个独立的 JS 模块。
工具链选择:
- 编辑器:VS Code(必装 Live Server 插件,方便本地预览)。
- 构建工具:Vite(速度快,配置简单,适合初学者)。
- 部署环境:Nginx(推荐,配置反向代理和静态资源缓存很方便)。
域名与服务器的小坑:
如果你用的是不同主域名的子站(比如 site-a.com 和 site-b.com),直接跨域加载 JS 文件会被浏览器拦截(CORS 问题)。这时候,要么在 Nginx 里配置 Access-Control-Allow-Origin,要么更简单的做法:将共享菜单打包成一个独立的静态 JS 文件,放在每个站点的 /shared/ 目录下,或者通过 CDN 统一分发。但在同一主域下的子站(如 a.example.com 和 b.example.com),可以直接引用公共路径,无需复杂配置。
我在腾讯云开发者社区上看到过不少类似的多站点架构讨论,大家普遍建议:能静态化就静态化,能本地引用就本地引用,避免跨域带来的安全隐患和性能损耗。 这个观点非常中肯,尤其是对于注重安全的企业站来说,减少外部依赖就是减少攻击面。
核心步骤:构建共享菜单模块
这一步是灵魂。我们要把菜单从 HTML 里“拔”出来,变成一个独立的 nav.js 文件。
第一步:创建共享目录
在项目根目录下创建一个 shared 文件夹,里面放 nav.js 和 nav.css。
第二步:编写菜单逻辑
nav.js 不仅要负责渲染 HTML,还要负责判断当前页面是哪个,从而给对应的菜单项加上 active 类名。这是共用菜单最难也最精彩的部分。
第三步:在各页面引入
在 index.html、about.html 等页面中,只保留一个空的 <div id="nav-container"></div>,然后引入 nav.js。
下面是一段可以直接运行的核心代码逻辑。请注意,这段代码使用了原生 JS,无需额外依赖,兼容性极好。
// shared/nav.js
/*** 共享菜单栏逻辑* 功能:动态注入菜单HTML,并根据当前URL高亮对应菜单项*/
const menuData = [{ name: '首页', url: '/', path: 'index' },{ name: '关于我们', url: '/about', path: 'about' },{ name: '产品中心', url: '/products', path: 'products' },{ name: '联系我们', url: '/contact', path: 'contact' }
];function renderNav() {// 获取当前页面的路径,例如 'about'// 去掉开头的斜杠和末尾的斜杠,处理根目录情况let currentPath = window.location.pathname.split('/').filter(Boolean).pop();if (currentPath === undefined || currentPath === '') {currentPath = 'index'; // 根目录默认指向首页}// 生成菜单项 HTML 字符串let navHTML = '<ul class="nav-list">';menuData.forEach(item => {// 判断是否高亮const activeClass = (item.path === currentPath) ? 'active' : '';navHTML += `<li><a href="${item.url}" class="nav-link ${activeClass}">${item.name}</a></li>`;});navHTML += '</ul>';// 找到容器并插入const container = document.getElementById('nav-container');if (container) {container.innerHTML = navHTML;console.log(`[Nav Debug] 当前高亮项: ${currentPath}`);}
}// 确保 DOM 加载完成后执行
document.addEventListener('DOMContentLoaded', renderNav);
关键解析:
menuData数组:这就是你的“数据源”。以后想改菜单,只改这个数组,所有引用它的页面瞬间更新。这就是“共用”的本质。currentPath计算:这段逻辑处理了 URL 解析的细节。比如用户访问/products/,split后得到['products'],pop得到'products'。如果是根目录/,split后是[''],filter后是[],pop是undefined,所以我们要手动赋值为'index'。innerHTML注入:虽然innerHTML有 XSS 风险,但这里的menuData是我们自己硬编码的静态数据,不包含用户输入,所以是安全的。
代码配置与样式隔离
有了 JS 逻辑,还得有样式。共用菜单最怕的就是样式污染(CSS Specificity)。如果你的菜单 CSS 写在全局 style.css 里,某个页面不小心改了 .nav-list 的间距,全站菜单都会乱套。
解决方案:BEM 命名规范 + 独立 CSS 文件
我们单独创建一个 shared/nav.css:
/* shared/nav.css */
/* 使用 BEM 命名,避免冲突 */
.nav-list {display: flex;gap: 20px;list-style: none;margin: 0;padding: 0;
}.nav-link {text-decoration: none;color: #333;font-weight: 500;transition: color 0.3s ease;
}.nav-link:hover {color: #0056b3; /* 品牌蓝 */
}/* 高亮状态:使用更深的颜色或下划线 */
.nav-link.active {color: #0056b3;border-bottom: 2px solid #0056b3;
}/* 响应式:手机端变成纵向或汉堡菜单 */
@media (max-width: 768px) {.nav-list {flex-direction: column;gap: 10px;}
}
在 HTML 页面中如何引用?
以 about.html 为例:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>关于我们 - Example Corp</title><!-- 引入共享样式 --><link rel="stylesheet" href="/shared/nav.css"><!-- 页面自身样式 --><link rel="stylesheet" href="/assets/about.css">
</head>
<body><!-- 只需要一个容器 --><div id="nav-container"></div><main class="container"><h1>关于我们</h1><p>这里是福建 Example Corp 的介绍...</p></main><!-- 引入共享逻辑 --><script type="module" src="/shared/nav.js"></script>
</body>
</html>
注意细节:
type="module":这行代码很重要。它告诉浏览器这是一个 ES 模块,支持import/export,并且默认是延迟执行的(等 DOM 解析完再跑),虽然我们在 JS 里也加了DOMContentLoaded,但双保险更稳妥。- 路径问题:
href="/shared/nav.css"使用的是绝对路径。如果你的站点部署在根目录,这样没问题。如果部署在子目录(如mysite/),则需要改为相对路径../shared/nav.css或者在构建时配置 Base URL。
为什么不用 iframe? 很多老派开发者喜欢用 iframe 嵌入菜单。大错特错!iframe 会导致 SEO 爬虫无法正确读取导航链接,且 iframe 内的焦点管理、无障碍支持(Accessibility)都很糟糕。现代前端开发,坚决抵制 iframe 做菜单。
常见报错与避坑指南
在实际操作中,尤其是新手,容易踩下面这几个坑。
坑一:404 错误 - 找不到 nav.js
- 现象:浏览器控制台报
Failed to load resource: 404。 - 原因:路径写错了。
- 解决:检查服务器上的实际文件路径。如果你用 Nginx,确保
root指向正确。如果是本地开发,VS Code 的 Live Server 根目录要选对。
坑二:菜单不显示,控制台无报错
- 现象:页面加载了,但导航栏空白。
- 原因:
document.getElementById('nav-container')返回了null。 - 解决:检查 HTML 中是否有
<div id="nav-container"></div>。注意,ID 是区分大小写的。另外,确保脚本放在<div>之后,或者使用了DOMContentLoaded事件。
坑三:高亮状态失效
- 现象:点击“关于我们”,菜单没有高亮。
- 原因:URL 解析逻辑没覆盖到边界情况。比如 URL 末尾带了斜杠
/about/,或者带了查询参数?id=1。 - 解决:优化
currentPath的获取逻辑。可以使用window.location.hash或更严谨的正则匹配。对于带查询参数的情况,window.location.pathname是不包含?后面的内容的,所以相对安全,但要处理末尾斜杠。
坑四:跨域 CORS 错误
- 现象:控制台报
Access to script at 'http://a.com/shared/nav.js' from origin 'http://b.com' has been blocked by CORS policy。 - 原因:不同主域名之间引用资源。
- 解决:
- 最简单:把
shared文件夹复制到每个站点目录下(不推荐,违背共用初衷)。 - 正规军:在 Nginx 配置中开启 CORS。
location /shared/ {add_header Access-Control-Allow-Origin *;add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; } - 最佳实践:使用 CDN 或对象存储(如腾讯云 COS)存放共享资源,CDN 通常自带 CORS 配置。
- 最简单:把
小结与部署优化
回到开头的问题:网站开发如何共用菜单栏? 答案很简单:模块化 + 数据驱动 + 统一引用。
通过上面的实战案例,我们实现了:
- 单点维护:改菜单只需改
nav.js中的menuData。 - 自动高亮:无需在每个页面手动加
class="active"。 - 样式隔离:CSS 独立,互不干扰。
- 性能友好:无额外 HTTP 请求(如果是同域静态资源),无重型框架依赖。
部署建议:
在 Nginx 中,建议对 /shared/ 目录开启缓存:
location /shared/ {expires 1y;add_header Cache-Control "public, immutable";
}
这样,用户第二次访问时,菜单 JS 和 CSS 直接从浏览器缓存读取,速度飞快。
对于福建乃至全国的企业建站来说,这种轻量级的共用方案性价比极高。它不需要你精通 React 或 Vue 的复杂状态管理,只要懂一点原生 JS 和 HTTP 基础,就能搞定。当然,如果你的业务复杂度极高,比如菜单需要根据用户登录状态动态变化,那时候再考虑引入后端 API 或 SSR(服务端渲染)也不迟。
技术没有银弹,选择最适合当前团队水平和业务需求的方案才是王道。别被那些花里胡哨的概念吓倒,回归本质,代码能跑、用户好用、维护成本低,就是好方案。
你的网站用的什么技术栈?评论区聊聊


