侧边栏这活儿不干不知道一干全是细节。这个系列更新到第92期前六部分我们先后把Ubuntu上的Hugo环境搭好、主题跑起来、内容组件填满博客的基本盘算是稳了。但每次打开自己的博客总感觉少了点啥——访客进来之后除了顺着正文往下滚没有第二种探索路径。这不就是侧边栏缺席嘛。于是我把主题改造的重点放到侧边导航栏上这一篇就是完整的实操记录。先给个定心丸这篇文章不是从零教你写CSS而是把Hugo主题里侧边导航栏到底怎么改、菜单数据怎么配、移动端怎么适配、哪些坑我替你们踩过讲透。适合已经把Hugo基础环境搭好、接下来要精修博客门面的同学。按我实测的经验跟着这篇走一下午就能把侧边栏从能看做到能用。1. 侧边栏到底该放什么先把信息架构想明白动代码之前我先停下来问了问自己一个问题侧边栏的作用是什么答案其实很简单它是整个站点的常驻导航层。页面滚动时它不动无论访客看到哪篇文章视野里始终有这一列。这么珍贵的固定曝光位放什么不放什么优先级怎么排直接决定了博客好不好用而不只是好不好看。我见过不少博客的侧边栏是大杂烩友情链接二十个、标签云铺满一整屏、热门文章十大篇、还有一堆统计图标。访客进来第一眼是懵的不知道先点哪儿。信息架构的原则是分层侧边栏从功能上大体可以拆成三层想清楚这三层后面每个模块的去留就好决定了。1.1 三个层次的导航需求第一层是全局导航解决这个博客有什么的问题。首页、归档、分类、标签、关于我这类条目是主菜单必须放最上面且永远保持清晰可点。这一层是侧边栏存在的根本理由砍谁的优先级都不能砍它。第二层是发现工具解决我能不能快速找到想看的文章的问题。最近更新、分类聚合、关键词搜索都归在这一层。它们不要求列表完整但是要快、要准访客不用开新页就能判断这里值不值得点进去。第三层是辅助信息解决这个博客是谁、怎么联系的问题。博主头像卡片、订阅入口、社交链接、友情链接属于加分项。这些模块有当然好但绝对不该喧宾夺主更不该挤占前两层的空间。这三层放在一起侧边栏的信息优先级就出来了主菜单永远第一发现工具次之辅助信息垫底。我自己的站点最后定下的结构就是按照这个顺序排的后面不管是大改样式还是新增模块都先过一遍这个框架再动手省了好多回头返工的功夫。1.2 我的最终模块方案这是我在自己博客里实际采用的模块清单先给你一个整体视野模块优先级说明渲染方式主菜单P0首页、归档、分类、标签、关于Hugo菜单遍历输出搜索框P0站内内容检索入口独立partial最近文章P1最新5篇文章带日期Hugo内置method遍历分类列表P1按文章数排序的分类聚合数据模板 weight博主卡片P2头像一句话简介静态HTML模块定下来之后再去动布局文件和样式就有的放矢了。很多人一上来就纠结CSS颜色、字体、圆角大小其实都是次要的。先解决上面有什么再解决长什么样。这一节看着像废话但真的能帮你省掉后面反复改样式的时间——我最初就是没想清楚结果一个标签云模块来回调了三版最后干脆砍掉了。2. Hugo内置的菜单系统从配置文件到模板渲染的完整链路Hugo的菜单系统是侧边栏最核心的基础设施。很多人一开始会想侧边栏导航嘛直接在模板里写死几行a href不就行了不行因为主题要改、博客侧重得要调、页面位置要挪、激活状态要自动高亮——写死的HTML没法维护。正确的做法是使用Hugo的菜单API把导航数据从模板里抽出来做成配置驱动。这一章我们把数据流完整走一遍。2.1 在配置文件里声明菜单Hugo支持在站点的配置文件中声明菜单我这里用的是hugo.toml老版本叫config.toml后面坑里会细说。最常见的主菜单是这样定义的[menu.main] [[menu.main]] name 首页 url / weight 10 [[menu.main]] name 归档 url /archive/ weight 20 [[menu.main]] name 分类 url /categories/ weight 30 [[menu.main]] name 标签 url /tags/ weight 40 [[menu.main]] name 关于 url /about/ weight 50这里有几个字段值得说一下。name是菜单项显示文字url是链接地址weight是排序权重——Hugo按weight从小到大排序所以我习惯用10、20、30这样的间隔中间留出余量以后插入新菜单项不用改后面所有数字。identifier也是常用字段后面讲激活状态的时候它很关键。2.2 也可以把菜单放在文章front matter里菜单不一定要全部写在配置文件里。如果某个页面希望单独出现在导航栏可以直接在那一篇内容的front matter里声明--- title: 关于我 date: 2025-01-01 menu: main: name: 关于 weight: 50 ---这样做的优势是内容跟着页面走以后删掉这个页面菜单项也随之消失不会出现配置文件里还挂着一条死链的尴尬。缺点是菜单分散在各文件里全局调整的时候要翻很多文件。我的习惯是全局主菜单写死在配置文件个别特殊入口用front matter补充两边靠weight合并排序互不冲突。如果你跟我一样只维护一个个人博客这个组合基本够用了。2.3 模板里怎么把菜单渲染出来配置好菜单之后模板端通过.Site.Menus.main拿到所有菜单项。一个最直接的单层菜单渲染是这样的{{ range .Site.Menus.main }} a classsidebar-nav-item href{{ .URL }}{{ .Name }}/a {{ end }}如果要处理多级菜单就要遍历Children{{ range .Site.Menus.main }} a classsidebar-nav-item href{{ .URL }}{{ .Name }}/a {{ if .HasChildren }} ul classsidebar-nav-children {{ range .Children }} lia href{{ .URL }}{{ .Name }}/a/li {{ end }} /ul {{ end }} {{ end }}Hugo菜单项本身是一个结构体Children是它的子菜单数组HasChildren是判断方法。这些都属于很基础但是绕不开的东西建议真的打开编辑器敲一遍不要只是复制粘贴看完就完。菜单这块你亲手改一次比读十篇文档都管用。3. 侧边栏布局文件改造从主题的partials层切入Hugo主题的布局都在layouts目录下按用途分成了partials、_default、taxonomy等子目录。侧边导航栏属于可复用组件正常应该放在layouts/partials/sidebar.html这类文件里。很多主题会把侧边栏渲染逻辑拆得很细比如有header.html、widgets.html、footer.html之类的partial组合。改造的第一步是搞清楚自己用的主题到底在哪个文件渲染侧边栏。3.1 找到sidebar对应文件与override机制如果你用的是别人开发的主题主题代码一般在themes/主题名/layouts/下面。但我不建议直接改themes/里的文件因为以后主题一升级你改的东西全被覆盖。Hugo提供了override机制在站点根的layouts/目录里放同名文件会优先于主题内的文件生效。站点根目录/ ├── themes/ │ └── mytheme/ │ └── layouts/ │ └── partials/ │ └── sidebar.html ← 主题自带的原版 └── layouts/ └── partials/ └── sidebar.html ← 你自己的override版本我自己就是这么做的从主题里复制一份sidebar.html到layouts/partials/然后在副本上改。这样升级主题不慌改坏了也随时能回退。如果你实在找不到侧边栏对应的partial就在主题目录里grep -r sidebar layouts/搜一下关键词比一个一个文件翻快得多。3.2 手写一个带激活状态高亮的菜单渲染侧边栏菜单和普通页脚链接最大的区别就是要有当前页面高亮。访客浏览文章时侧边栏里归档要亮浏览分类页时分类要亮。实现这个效果需要用IsMenuCurrent或HasMenuCurrent方法。这里有个容易出错的地方range会把当前上下文切换成菜单项这时候要拿到当前正在渲染的页面对象必须用$。所以我的模板是这样写的nav classsidebar-menu {{ $currentPage : . }} {{ range .Site.Menus.main }} a classsidebar-menu-link{{ if or ($currentPage.IsMenuCurrent main .) ($currentPage.HasMenuCurrent main .) }} sidebar-menu-link-active{{ end }} href{{ .URL }} {{ .Name }} /a {{ end }} /nav关键点就两行先把.赋给$currentPage再用or组合IsMenuCurrent和HasMenuCurrent。前者精确匹配当前URL和菜单项后者用于上级或分类页被匹配的情况比如你在某篇文章里同时希望归档或分类处于激活态就可以用HasMenuCurrent配合identifier做包含关系判断。没有这个高亮导航栏就少了一半的可用性访客永远不知道自己在站点的哪个位置。3.3 把常用小部件一起补上菜单搞定了侧边栏还剩搜索框和最近文章。搜索框我用的是一个最简单可靠的方式直接把搜索动作指向站点内建的搜索落地页form classsidebar-search action/search/ methodget input typesearch nameq placeholder搜索文章... / /form/search/对应的落地页需要你另外做Hugo本身没有内建搜索功能它是纯静态站点。如果不想搭搜索页面可以退而求其次用站点地图或者干脆把搜索框这一节跳过不丢人。最近文章模块用Hugo内置的集合方法就能实现不用额外依赖h3最近文章/h3 ul classsidebar-recent {{ range first 5 (where .Site.RegularPages Type in (slice post posts)) }} li a href{{ .RelPermalink }}{{ .Title }}/a span classsidebar-recent-date{{ .Date.Format 2006-01-02 }}/span /li {{ end }} /ul注意我用的是RegularPages而不是Pages。RegularPages过滤掉了分类页、标签页这类归档列表页只保留真正的内容页面否则你的最近文章里会出现一串分类标签这种不是文章的条目这个坑我踩过必须提前给你提个醒。4. 响应式适配桌面固定侧栏与移动端抽屉的切换侧边栏的样式实现我推荐用CSS Grid来做整页布局简单、可控不用写复杂的浮动和清除浮动。桌面端是经典的侧栏正文两栏移动端则要把侧栏藏起来通过按钮滑出。两个形态分开处理各自都清爽。4.1 用Grid搭出页面骨架以我的博客为例页面最外层包了一个类名page的容器.page { display: grid; grid-template-columns: 280px 1fr; gap: 2rem; max-width: 1200px; margin: 0 auto; padding: 1rem; }左边280px是侧栏固定宽度右边1fr是正文自适应剩余空间。如果你的主题有更复杂的结构比如右侧还有相关文章栏可以再加一列但别贪多两栏已经够清爽了。我还给侧栏本身加了一个sticky定位让它在正文滚动时能吸在视口顶部长文浏览时始终能看到导航入口.sidebar { position: -webkit-sticky; position: sticky; top: 1rem; align-self: start; max-height: calc(100vh - 2rem); overflow-y: auto; }align-self: start很关键不然Grid和Sticky组合下侧栏会被拉满整个网格高度sticky就不生效了。overflow-y: auto则防止侧栏内容本身就比视口高的时候把屏幕卡死出现滚动不出去的烦人问题。4.2 移动端抽屉式导航窄屏上两栏布局放不下标准做法是把侧栏变成抽屉默认藏在屏幕左边点按钮再滑出来。我用了最朴素的方案一个汉堡按钮控制一个类名。media (max-width: 768px) { .page { grid-template-columns: 1fr; } .sidebar { position: fixed; top: 0; left: 0; height: 100vh; width: 260px; z-index: 1000; transform: translateX(-100%); transition: transform 0.25s ease-in-out; } .sidebar.is-open { transform: translateX(0); } }配合一小段JavaScript做类名切换const toggleButton document.querySelector(.sidebar-toggle); const sidebar document.querySelector(.sidebar); const overlay document.querySelector(.sidebar-overlay); toggleButton.addEventListener(click, function () { sidebar.classList.toggle(is-open); overlay.classList.toggle(is-visible); }); overlay.addEventListener(click, function () { sidebar.classList.remove(is-open); overlay.classList.remove(is-visible); });transition配合transform是在移动端性能上比较稳的方案千万不要用left: -260px到left: 0这样去直接变更位置属性动画会掉帧看着特别廉价。overlay遮罩记得给个半透明背景点遮罩关闭抽屉的操作习惯也得保留不然用户体验会大打折扣。4.3 不同屏幕下模块的取舍响应式不只是能用而是在不同设备上给最合适的体验。我实测下来手机端侧栏弹出空间有限标签云这种几十个标签的模块在260px宽的抽屉里会排到崩溃。所以我加了一条规则小屏下隐藏低优先级模块media (max-width: 768px) { .sidebar-widgets, .sidebar-tags { display: none; } }搜索框则保留因为移动端站内搜索的需求一点不比桌面端低。说白了display:none隐藏的只是锦上添花导航这种刚需永远要保留。做响应式之前先排优先级比调一堆断点参数更有用。断点数值也不要闭着眼用别人的先看看自己站点在哪些宽度下布局开始别扭再去设断点这样才是真正适合自己的方案。5. 实测踩坑记录四个最容易翻车的点这一章是全篇最有价值的部分因为都是我自己改侧边导航栏时真的碰到过的问题每一个都折腾了半小时以上。你提前看过至少能少走一半弯路。5.1 配置文件命名config.toml还是hugo.tomlHugo早期版本默认配置文件是config.toml后来官方把默认名改成了hugo.toml。如果你照着老教程的路径手动建了config.toml又在新版本里另外建了一份hugo.toml就会遇到为什么菜单没生效的初级疑问——因为站点只读其中一份你改的是另一份。建议统一用hugo.toml新装环境默认就是它。还有个隐藏问题如果两份文件都存在有些Hugo版本会提示配置冲突。我遇到过一次侧边栏明明配了菜单却不渲染查了半天才发现是两份配置文件在打架。解决方式很简单留hugo.toml删掉或重命名另一个清理干净再启动。5.2 菜单项URL失效与404菜单写死URL的坏处是如果目标页面后来改动了permalinks或者文章被删除菜单链接就会变成404。新版Hugo里我更推荐用PageRef字段指向内容路径而不是裸写url[[menu.main]] name 归档 pageRef /archive weight 20PageRef相当于告诉Hugo去内容库里找这个路径的页面由Hugo解析出真实的最终URL。如果页面不存在菜单项可能干脆不渲染或者构建时就给出警告至少不会把404留到线上给访客。这个改动很小但对长期维护博客的人来说特别值得。5.3 激活状态死活不亮菜单能正常显示但当前页面高亮始终不生效这是被问得最多的情况之一。原因几乎都是模板上下文问题range循环里上下文被切换成菜单项此时直接调用IsMenuCurrent拿到的当前页面根本不对。我一开始也是放着{{ if .IsMenuCurrent }}直接判断结果永远不亮。正确的姿势就是第3章那个代码先用$currentPage : .在当前上下文里存下当前页面对象再去调IsMenuCurrent方法。另外如果页面类型是分类聚合页要记得用HasMenuCurrent配合identifier做范围匹配不然分类下的文章详情页永远不会点亮分类菜单。5.4 中文菜单项乱码或渲染异常中文内容写进TOML最可能的坑是文件编码。Ubuntu下用不同编辑器保存文件时偶尔会带着BOM或错误的字符集。确保你的配置文件以UTF-8无BOM格式保存一般就不会乱码。VSCode、Vim这些主流工具默认都没问题反而是从网页复制粘贴配置的时候容易带进隐藏字符。另一个容易被忽略的是TOML字符串引号的规范。name 分类这种双引号字符串没问题但如果你写成了单引号字面量字符串某些Hugo版本的解析器对特殊字符的处理会更严格。我的建议菜单相关的字符串统一用双引号包起来不要为了少敲一个Shift键用单引号真出问题排查起来你都不知道从哪儿查起。Debug也要给一个标准姿势启动hugo server --logLevel info看构建日志模板里犹豫不决的地方用{{ printf %#v . }}把结构体内容打印出来直接看。侧边栏看着是个小功能但踩坑时照着这个思路排查基本都能在半小时内定位到问题根源。我在实际改侧边栏的过程里还有一个体会特别深别在样式细节上过度纠结。什么圆角度数、字色饱和度、间距是3px还是5px渲染出来看两眼觉得顺眼就过别为一个像素折腾到半夜。真正值得花时间的是菜单数据结构、激活状态和响应式边界这几个逻辑层的东西它们决定侧边栏的质量下限。样式不满意后面哪天真想重新设计框架稳了改起来也就是换个CSS的事。这个系列走到这里侧边栏这关算是过了下一篇我准备折腾博客的部署和自动化更新到时候再继续聊。