如何给多个页面,添加统一的导航栏?我罗列对比了 5 个方案

11,822 阅读7分钟

背景

之前我开发了一些工具,每个页面是一个html文件,整体是个多页面应用。包括这些:

当时,每一个工具都有一个URL,每个页面只有本工具的内容,没有统一的「导航栏」,这对于工具网站是非常不方便的。所以,我需要加一个统一的导航栏,方便用户在多个页面之间跳转。

我做事情很谨慎,一定要罗列多个方案,再做决策。

我把所有可行的方案都罗列到了本文中,并描述了各个方案的优点、缺点。方便大家遇到相同问题时做决定。

导航栏特点

罗列方案前,你需要知道:

  1. 导航栏是可变的,每当你新做一个页面、修改某页面的标题或URL,都需要更新导航栏。
  2. 所有页面的导航栏,应该具有一致性,更新时要统一更新(否则用户会比较困惑)。

方案一:服务端渲染

这里服务端渲染主要包括2种:

  1. 基于NodeJS框架做的SSR。
  2. 基于其它后端框架模版做的动态渲染。

他们都可以实现这种的效果:用户请求某个页面的html时,后端动态拼接好一份完整的html,返回给前端。在拼接过程中,把导航栏的html片段加进去。

优点

白屏时间短,SEO好。

缺点

  1. 服务端渲染是需要耗费服务端资源的,即使渲染结果可以缓存,我依然不建议浪费这些计算、存储资源。
  2. 服务端需要维护好导航html片段。而服务端代码和前端代码通常不在一个仓库,如果开发者手动更新导航html片段,效率低,容易忘记。即使你做了自动化方式同步,这也涉及到跨仓库同步,不是很方便。
  3. 开发过程中,为了达到跟线上一样的效果,可能还需要启动后端服务。

综上,如果你的网站本身没有服务端渲染,我不建议你仅仅为了增加导航栏而采用该方案。

方案二:前端编译时插入

前端增加编译环节,源代码不写导航栏,编译后,自动在特定位置插入导航栏的html片段。

优点

  • 白屏时间短,SEO好。
  • 可以放在CDN。

特点

  • 需要增加编译环节,可以借助Webpack等工具。

如果不想使用Webpack,也可以像我一样,手写编译脚本(基于NodeJS):

首先是build.js,它遍历src文件夹下的html文件,针对每个html文件,跑一遍函数addNavigation,把结果写入build文件夹。

// build.js
const fs = require('fs');
const addNavigation = require('./navigation');
fs.readdirSync('../src').forEach(filename => {
  if (!filename.endsWith('.html')) return;
  const html = fs.readFileSync('../src/' + filename, 'utf-8');
  const newHtml = addNavigation(html, filename);
  fs.writeFileSync('../build/' + filename, newHtml, 'utf-8');
});

然后是navigation.js,它就是针对html源代码做修改,返回新的html片段,已经插入了导航栏html片段。

// navigation.js
const config = [
  {name: '备忘录', url: 'memo.html'},
  // ...
];
const getNavigationHtml = (filename) => {
  return '<div>导航栏html片段</div>';
};

const addNavigation = (html, filename) => {
  let newHtml = html;
  const navigationHtml = getNavigationHtml(filename);
  const bodyIndex = newHtml.indexOf('<body>') + 6;
  newHtml = newHtml.substr(0, bodyIndex) + navigationHtml + newHtml.substr(bodyIndex);
  return newHtml;
};

module.exports = addNavigation;

为什么这么设计呢?因为addNavigation只是编译的一个环节,之后可以方便的增加addHeaderaddFooter等等。

缺点

  • 每次更新导航栏,需要重新编译所有项目,并重新发布所有页面的html文件。(但它是可接受的,全部重新编译、全部重新发布,完全可以自动化实现,且成本很低)

我个人就是选择了这种方案,参考: github.com/HullQin/too…

方案三:前端运行时插入(UMD、模块联邦)

通过script动态引入导航js,运行时插入html片段(即UMD方式,Webpack的模块联邦也属于这种方案)。

为什么必须通过script引入?

因为导航栏的一致性和可变性,开发时它一定是只存了一份代码的。因为本方案不在编译时统一插入,而是在运行时动态插入,所以就需要多个页面引入同一份js文件,动态插入一样的导航栏。

优点

解决了方案二的缺点,每次变更导航栏,只需要重新发布script即可,不需要重新发布其他工具的html。

缺点

  1. 加载速度较慢,可能存在导航栏闪动问题(因为script是异步加载的,展示页面内容时,可能还没下载好导航栏对应script)。
  2. SEO不好。

如果可以接受这些缺点,这确实是非常好的方案。

方案四:基于框架组件

如果页面整体是同一个项目,同一个框架,那么使用组件是最方便的。

这时候基本不需要决策了,直接无脑用组件吧。

方案五:基于微前端

微前端的初衷正是为了解决巨石应用,也可以让多个应用放到同一个SPA中,切换更流畅。

微前端方案中,通常分为「主应用」和「子应用」。可以把导航栏放在「主应用」中。

优点

  • 框架不受限制。
  • 可以让多页面应用(MPA)体验像单页面应用(SPA)一样(即切换页面时,导航栏不闪烁)。

缺点

  • 重。

如果你的项目本身不是基于微前端的,没有必要为了加导航栏而引入微前端方案。

你可以看看我的网站 tool.hullqin.cn,它没有采用微前端方案,本身是个多页面应用(非SPA)。但因为浏览器有缓存,所以体验非常丝滑,在多个页面之间切换非常快。

方案汇总

方案框架限制首屏加载速度SEO可维护性
服务端渲染(SSR或模板渲染),统一在html特定位置插入导航html片段较快很好导航html片段在后端项目,需维护好它
前端编译时,统一在html特定位置插入导航html片段最快很好导航html片段在前端项目,需维护好它
通过script动态引入导航js,运行时插入html片段一般同上
基于框架组件(React、Vue等)做导航栏必须统一框架一般同上
基于微前端做导航栏,导航属于主应用,工具页面属于子应用一般一般同上

我个人是选择了方案二,代码参考: github.com/HullQin/too…

效果如下: tool.hullqin.cn

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋象棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》《极致用户体验》本文正在参加「金石计划 . 瓜分6万现金大奖」