首页 / 资讯中心 / 文章详情

Hugo博客侧边导航栏定制:从模板到配置的完整实战

Hugo博客侧边导航栏定制:从模板到配置的完整实战 ★ FEATURED ARTICLE
从零折腾私人博客最容易被忽略、又最能决定页面气质的就是侧边导航栏。前面几篇我们把Hugo装好、站点跑起来、主题换上了文章也一篇篇写进去这时候博客还只是单一的内容流所有文章从上到下排队读者想看某篇旧文只能一直往下翻。侧边导航栏要解决的核心问题就是把“内容目录”和“站点信息”在每个页面上固定住让访客无论从哪篇文章进来都能快速找到入口。这一篇是这个系列里比较偏实战的一篇我尽量把模板文件、配置文件、踩坑点都铺开讲。适合正在跟这个系列折腾的朋友也适合那些用Hugo主题但一直不知道怎么动侧边栏的朋友。读完之后你应该能自己做出一套符合自己习惯的侧边导航栏而不是继续用主题默认的那一堆模块。1. 先想清楚侧边导航栏要放什么、解决什么问题1.1 从站点的角度看侧边栏是个导航中枢很多人一上来就搜“Hugo侧边栏模板代码”然后往页面里堆一堆模块头像、搜索、分类、标签、最近文章、友链、订阅、归档恨不得全塞进去。最后结果通常是侧边栏比正文还长读者反而不知道该点哪里。我的建议是先停下来想一个问题你这个博客读者最常做的事情是哪几件是翻你最近写了什么还是找某个分类下的文章还是想了解你这个人还是想通过标签发现一些零散的主题不同需求对应不同的模块优先级这个顺序直接决定了侧边栏的布局层次。对于私人博客我的判断是分类导航 最近文章 站点信息 标签 其他。分类是内容组织的主线它能直观反映博客的写作方向最近文章是动态入口告诉读者你还在更新站点信息和头像解决信任问题标签则留给那些想随机探索的读者。这个顺序不是绝对的但它值得作为你的默认参考。1.2 私人博客与内容型博客的差异私人博客和那种面向SEO、流量的内容站有一个本质区别内容量和访问目的不同。内容站侧边栏常见的“热门文章榜”“广告位”“排行榜”在私人博客里基本没有意义因为你没有海量内容支撑这些模块也没有广告需求。私人博客的读者通常是熟人、同行或者通过搜索引擎找到某篇具体文章的人他们更需要在短时间内搞清楚这里是谁写的、有哪些主题、最近在关注什么。所以私人博客的侧边栏应当追求“克制”而不是“丰富”。宁可只保留三四个模块也不要试图全面覆盖。我见过很多干净清爽的博客侧边栏就放了个人头像、一句话简介、分类列表和RSS入口整个页面反而很有味道。模块少每个模块就能给足呼吸空间导航的命中率也更高。1.3 内容整理与导航是同步的还有一个隐藏需求我必须在动模板之前就点出来侧边导航栏的实质是内容组织不是单纯的布局问题。Hugo的分类、标签都是从文章的前置参数front matter里自动读取的。如果你以前写文章时没有认真填categories和tags那侧边栏就永远做不漂亮因为分类列表只显示已存在的分类标签云也是按标签的实际数量生成的。模板写得再完美没有数据输入也是白搭。所以这里建议你先花一点时间把已有文章的前置参数整理一遍。每篇文章至少有一个分类、两三个标签统一分类的命名风格。这一步做完侧边栏才真正有内容可以渲染。这也是我把这一节放在最前面的原因——它在实操顺序上应该排第一。2. Hugo侧边栏的实现路径模板、配置与数据2.1 理解partial与模板查找顺序Hugo里侧边栏通常以partial的形式存在。partial可以理解为一段可复用的HTML片段站点的首页、列表页、文章页都会把它引入这样你只需要维护一份代码所有页面都会同步更新。理解它的文件查找顺序很重要。Hugo会优先读取站点根目录下的layouts目录如果找不到再去主题的layouts目录中找。也就是说你可以完全不修改主题源码只在站点项目里新建一个同名文件就能把主题默认的侧边栏替换掉。目录结构大概长这样myblog/ ├── config/ │ └── _default/ │ ├── hugo.toml │ └── params.toml ├── content/ ├── layouts/ │ └── partials/ │ └── sidebar.html ├── data/ │ └── sidebar.toml └── themes/ └── ztheme/ └── layouts/ └── partials/ └── sidebar.html注意一个关键点你新建的partial文件名字必须和主题模板中引用的一致。如果主题在模板里写的是{{ partial sidebar.html . }}那你的文件路径就必须是layouts/partials/sidebar.html不能改成left-sidebar.html之类的新名字否则主题根本调不到你的文件。这一点我在实际中踩过后面常见问题里还会展开。2.2 用配置驱动侧边栏而不是写死在模板里侧边栏的模块开关和数量我更推荐通过配置文件来控制而不是在HTML里写死。以hugo.toml为例我会在[params]下面建一个sanbiansidebar的子配置块[params.sidebar] avatar /img/avatar.jpg description 写代码、读书和日常思考的地方 showRecent true recentCount 5 showCategories true showTags true然后在模板里用{{ .Site.Params.sidebar.showRecent }}这样的变量去判断是否渲染某个模块。这样做的好处是将来你想调整侧边栏打开配置文件改一行就行不用去翻模板代码。而且如果你以后换主题这些参数还能直接复用减少重复劳动。如果你的侧边栏里要放比较复杂的结构比如一组社交链接、一组友链每个链接有名字和地址那么在data/sidebar.toml里维护会更清晰[[social]] name GitHub url https://github.com/yourname [[social]] name Email url mailto:youexample.com模板中通过{{ range .Site.Data.sidebar.social }}来遍历。这种方式适合“结构化数据”简单开关则直接走[params]就好。两者可以混用我的经验是开关和数量参数放[params]列表型数据放data目录。2.3 分类、标签自动生成的核心逻辑Hugo的分类和标签机制官方术语叫taxonomy。你不需要手动维护一个分类列表只需要在每篇文章的front matter里写清楚--- title: 这是一篇示例文章 date: 2025-01-01 categories: [随笔] tags: [Hugo, 博客] ---Hugo构建站点时会自动扫描所有文章的这些字段然后生成对应的分类页和标签页同时也能在侧边栏里被遍历到。这是Hugo侧边栏最核心的数据来源。在模板里遍历分类的代码通常长这样{{ if .Site.Taxonomies.categories }} ul classsidebar-categories {{ range .Site.Taxonomies.categories }} li a href{{ .Page.RelPermalink }}{{ .Page.Title }}/a span classcount{{ .Count }}/span /li {{ end }} /ul {{ end }}这里的{{ .Count }}会输出该分类下的文章数量非常方便。需要注意.Page是Hugo新版API的写法老版本主题里可能有直接用.Name或.Permalink的写法新写代码时建议统一用.Page系列字段。2.4 用菜单驱动顶部导航链接除了分类和标签侧边栏往往还需要一些固定的导航链接比如“首页”“归档”“关于”。这些环节Hugo也有官方机制就是菜单。在hugo.toml中定义[[menus.sidebar]] name 首页 pageRef / weight 1 [[menus.sidebar]] name 关于 pageRef /about weight 2 [[menus.sidebar]] name 归档 pageRef /archives weight 3模板中这样渲染{{ range .Site.Menus.sidebar }} a href{{ .URL }} classsidebar-nav-item{{ .Name }}/a {{ end }}weight字段控制排序数字小的排前面。用菜单的好处是高亮当前页面的逻辑Hugo已经帮你想好了配合IsMenuCurrent方法就能做当前菜单高亮比你手动去比较URL靠谱得多。3. 实操从零搭一个完整的侧边导航栏3.1 定义最终效果和模块清单我这篇的目标是做一个偏传统个人博客的侧边栏站点logo加简介在顶部接着是主导航菜单然后是最近文章、分类列表、标签云。整个侧边栏宽度控制在280到320像素之间在桌面端固定在左侧移动端折叠成抽屉。动手前先梳理模块清单头像和一句话简介主导航菜单最近文章列表分类列表标签云RSS订阅和社交链接这个清单看着不长已经涵盖了一个私人博客的日常需求。3.2 准备配置文件假设你用的是新版Hugo配置文件在config/_default/hugo.toml。需要加入的内容如下baseURL https://your-domain.example/blog/ languageCode zh-cn title 你的博客名 [params.sidebar] avatar /img/avatar.jpg description 写代码、读书和日常思考的地方 showRecent true recentCount 5 showCategories true showTags true [[menus.sidebar]] name 首页 pageRef / weight 1 [[menus.sidebar]] name 归档 pageRef /archives weight 2 [[menus.sidebar]] name 关于 pageRef /about weight 3baseURL里的域名替换成你自己的如果博客部署在域名根目录就直接写https://your-domain.example/。这里我特意演示了子目录部署的场景因为很多人的博客并不放在根目录后面会提到由此带来的URL问题。3.3 编写sidebar.html主体模板在layouts/partials/下新建sidebar.html完整内容如下aside idsidebar classsidebar div classsidebar-header a href{{ .Site.Home.RelPermalink }} classsite-logo {{ with .Site.Params.sidebar.avatar }} img src{{ . | absURL }} altavatar {{ end }} span classsite-title{{ .Site.Title }}/span /a {{ with .Site.Params.sidebar.description }} p classsite-desc{{ . }}/p {{ end }} /div nav classsidebar-nav {{ range .Site.Menus.sidebar }} a href{{ .URL }} classsidebar-nav-item{{ .Name }}/a {{ end }} /nav {{ if .Site.Params.sidebar.showRecent }} section classsidebar-module sidebar-recent h3最近文章/h3 ul {{ $count : .Site.Params.sidebar.recentCount | default 5 }} {{ range first $count (where .Site.RegularPages Section posts) }} lia href{{ .RelPermalink }}{{ .Title }}/a/li {{ end }} /ul /section {{ end }} {{ if and .Site.Params.sidebar.showCategories .Site.Taxonomies.categories }} section classsidebar-module sidebar-categories h3分类/h3 ul {{ range .Site.Taxonomies.categories }} li a href{{ .Page.RelPermalink }}{{ .Page.Title }}/a span classcount{{ .Count }}/span /li {{ end }} /ul /section {{ end }} {{ if and .Site.Params.sidebar.showTags .Site.Taxonomies.tags }} section classsidebar-module sidebar-tags h3标签/h3 {{ range .Site.Taxonomies.tags }} a href{{ .Page.RelPermalink }} classtag-link{{ .Page.Title }}/a {{ end }} /section {{ end }} /aside这段代码有几个细节值得说明。{{ .Site.Home.RelPermalink }}是站点首页的链接用这个方法可以避免硬编码URL不管博客放在根目录还是子目录都不会出错。头像后面的{{ . | absURL }}会把配置里的相对路径拼成绝对URL。如果你在hugo.toml里写的是/img/avatar.jpg那么部署后它会自动变成https://your-domain.example/img/avatar.jpg。这个处理能避免在子目录部署时图片路径出问题。最近文章列表里的where .Site.RegularPages Section posts是指只统计content/posts/目录下的普通文章。如果你的文章目录不叫posts这里要改成你自己的目录名。3.4 把模板挂载到主布局光有partial文件还不够得让它被页面真正引用。这就要看主题的模板结构了。大多数Hugo主题会在baseof.html或者index.html里用{{ partial sidebar.html . }}引入侧边栏。你可以先在项目里查一下主题的引用情况grep -rn sidebar themes/ztheme/layouts/如果输出的结果里有partial sidebar.html恭喜你直接刷新页面就能看到自定义侧边栏因为项目layouts/partials/sidebar.html会覆盖主题里的同名文件。如果主题里根本没有sidebar这个词那说明这个主题的布局中没有独立的侧边栏文件。你需要把主题里的index.html、list.html、single.html复制到项目的layouts/目录下然后手动在合适的aside位置插入一行{{ partial sidebar.html . }}复制模板这个操作要谨慎因为它会把主题原有布局从项目层接管过来后续主题升级时不会再更新这些文件所有改动都得自己维护。能不动就不动优先走覆盖partial的路线。3.5 本地预览和发布配置和模板都准备好之后在项目根目录启动本地服务hugo server -D -p 1313-D是让草稿文章也显示出来方便开发时预览。浏览器打开http://localhost:1313你就能看到侧边栏的效果。确认无误后再执行正式构建hugo --gc --minify构建产物会生成在public/目录里把它部署到服务器或者托管平台即可。--gc是清理旧缓存--minify压缩HTML和CSS由于侧边栏是静态生成的这些操作对SEO来说只有好处没有坏处。我在实际操作中一般会开两个终端一个跑hugo server另一个随时改配置和模板。Hugo的局部更新速度很快保存后页面基本秒级刷新非常方便。4. 侧边栏的进阶打磨响应式、高亮与性能4.1 移动端折叠方案侧边栏在桌面端很舒服到了手机上就得折叠起来否则会占掉一半屏幕正文几乎没法看。我一个比较推荐的方案是纯CSS加HTML的checkbox占位不需要写JavaScript。原理是用一个隐藏的checkbox配合label元素切换侧边栏的显示状态。结构如下input typecheckbox idsidebarToggle classhamburger-check label forsidebarToggle classhamburger-btn菜单/label aside idsidebar classsidebar ... /aside配套CSS大致是.hamburger-check { display: none; } .hamburger-btn { display: none; cursor: pointer; } .sidebar { display: block; } media (max-width: 768px) { .hamburger-btn { display: inline-block; } .sidebar { display: none; } .hamburger-check:checked .sidebar { display: block; } }注意这里的CSS兄弟选择器要求checkbox和aside是相邻兄弟节点中间不能插入其他元素。如果你还有其他布局元素在它们之间需要用~通用兄弟选择器并且保证它们同处一个父容器里。这个方案的好处是纯静态实现生成出来的页面不需要额外加载JS库非常贴合Hugo静态站的气质。4.2 当前主导航高亮菜单高亮是导航体验里很重要的细节读者知道自己正在哪个栏目。Hugo的菜单项提供IsMenuCurrent方法可以在渲染菜单时判断当前页面是否是这个菜单项对应的页面{{ range .Site.Menus.sidebar }} a href{{ .URL }} classsidebar-nav-item{{ if $currentPage.IsMenuCurrent sidebar . }} active{{ end }} {{ .Name }}/a {{ end }}其中$currentPage在partial开头定义一下{{ $currentPage : . }}。分类高亮会复杂一些。如果你在某个分类列表页想让侧边栏里对应的分类条目高亮可以对比当前页面的Data.Term和分类名{{ $currentTerm : }} {{ if eq .Kind term }} {{ $currentTerm .Data.Term }} {{ end }} {{ range .Site.Taxonomies.categories }} li class{{ if eq (lower .Page.Title) (lower $currentTerm) }}active{{ end }} ... /li {{ end }}这段逻辑的意思是如果当前页面是一个分类页就把这个分类名保存到$currentTerm然后在遍历分类时匹配名称匹配上了就加active类。4.3 分类标签按数量排序Hugo默认的分类列表排序是按名称字母序有时候你想让文章数更多的分类排在前面。默认的range做不到但可以在模板里构造一个切片再排序。思路是这样的遍历所有分类把标题、链接、文章数做成一个字典追加到切片里然后按文章数降序排列。{{ $sorted : slice }} {{ range .Site.Taxonomies.categories }} {{ $sorted $sorted | append (dict title .Page.Title url .Page.RelPermalink count .Count) }} {{ end }} {{ $sorted sort $sorted count desc }} {{ range $sorted }} li a href{{ .url }}{{ .title }}/a span classcount{{ .count }}/span /li {{ end }}这里有个容易踩的坑Go模板的变量作用域比多数语言严格。你在range内部用$sorted $sorted | append ...是在原地更新变量但如果没有先在外面声明{{ $sorted : slice }}内部根本访问不到$sorted。很多人一上来就写{{ $sorted : slice }}{{ range ... }}{{ $sorted ... }}结果报未定义变量其实就是作用域没搞对。4.4 partial缓存与构建性能侧边栏在绝大多数页面上内容相同它又是每个页面都要渲染的部件所以构建时存在重复渲染的问题。Hugo提供了partialCached方法可以把partial的渲染结果缓存起来{{ partialCached sidebar.html . }}这里有一个细节缓存默认是不区分上下文的但我们的侧边栏在不同页面上其实有差异比如菜单高亮、分类高亮。如果直接用上面的写法第一次渲染的结果会被复用导致其他页面的高亮丢失。解决办法是给缓存加一个变化key让它在页面类型或标题变化时重新渲染{{ partialCached sidebar.html . .Kind }}通过.Kind区分首页、列表页、文章页就够了。缓存之后大型站点构建速度的提升还是很明显的对私人博客来说影响不大但这是一条值得养成的好习惯。5. 常见问题与排查技巧实录5.1 自定义partial不生效这个问题最常见通常有三个原因。第一文件名或路径大小写不匹配。Linux系统对大小写敏感Sidebar.html和sidebar.html是两个文件。请确认你的文件路径是layouts/partials/sidebar.html并且主题模板中拼写的partial名称与之一致。第二你把文件放错了位置。项目layouts/partials/和主题内layouts/partials/两者都有效但项目的会覆盖主题的。如果你把文件放在static/里Hugo会把它当静态资源复制出去而不会参与模板渲染。第三Hugo缓存了旧模板。改了模板不生效时可以先试命令hugo server --gc这会把旧的缓存文件清掉再启动。如果还不生效删除resources/_gen目录再重新构建基本能排除缓存问题。5.2 分类和标签区域空白侧边栏模板写了hugo server也跑了分类那一块就是空白。排查顺序如下。先看文章front matter里有没有写categories和tags。我见过很多所谓“文章”直接用Markdown文件文件名当标题连front matter都没写那Hugo当然不知道这篇该归类到哪里。再看模板里的条件判断是否合理。如果你用了if .Site.Taxonomies.categories但站点配置中手工配置的taxonomies条目名写错了也会导致这个值为空。可以临时在模板里加一行{{ printf %#v .Site.Taxonomies }}输出到页面上看结构哪个分类有数据、哪个没有一目了然。排查完记得删掉这行。再看where条件里的section名字。你的文章如果放在content/posts/那么where .Site.RegularPages Section posts可以取到如果目录叫content/post/那section是post。目录名不同数据就不同这是新手最容易搞混的地方。5.3 侧边栏链接出现404或路径不对Hugo站点部署在域名根目录时写/about没问题部署在子目录时比如https://your-domain.example/blog/固定写法/about会跳转到域名的根目录下的about导致404。最稳妥的方式是不要手动拼URL全部采用Hugo的模板函数文章链接用{{ .RelPermalink }}分类链接用{{ .Page.RelPermalink }}站点首页用{{ .Site.Home.RelPermalink }}如果非要在配置里写一个链接比如社交链接用{{ . | absURL }}把它转成绝对地址。这样不管站点的部署位置如何变化链接都不会断。5.4 侧边栏模块太多反而找不到重点这是一个取舍问题。分类列表如果显示全部比如写了20个分类就会占掉很大一屏。我的处理方式是侧边栏只显示前10个分类剩余分类放到独立的“分类”页面去查看。模板里用first过滤{{ range first 10 .Site.Taxonomies.categories }} ... {{ end }}这个10可以放到配置文件里将来调整时不用改模板。同样的思路也适用于最近文章列表。侧边栏不是索引页它的任务是“指路”不是“陈列”。把最常用的入口放进去其余内容留在归档页里主次关系才清晰。5.5 移动端菜单打不开或样式错乱用checkbox方案时最常见的问题是选择器写错或DOM结构不对。要求相邻兄弟节点~要求同一父节点内的后续兄弟节点。如果input和aside之间隔着其他标签比如无意中多了一个div那选择器就失效了。另一个问题checkbox本身在切换后会保持状态刷新页面时如果浏览器恢复了表单状态可能出现刷新后菜单打开的情况。遇到这个场景我一般会改用几行原生JS在页面加载时重置状态document.getElementById(sidebarToggle).checked false;对于私人博客来说这个做法足够可靠。5.6 构建报错模板变量找不到或类型不对Go模板的报错信息在初看时可能比较绕。常见的两类cant evaluate field RelPermalink in type interface {}说明range遍历到的元素不是预期中的Page对象很可能where条件没匹配到任何页面Hugo就返回了一个空接口。检查一下section名称是否正确或者先不写where直接遍历看看。execute of template failed这类错误后面通常会跟行号和列号比如template: partials/sidebar.html:12:34直接打开文件看第12行第34列附近多半是少写了一个{{或者引号没闭合。需要说明的是Hugo近几个版本对模板报错的提示已经相当友好了会直接指出所在文件和具体行不用像以前一样全靠猜。5.7 调试侧边栏的实用技巧我在调侧边栏时最喜欢用的就是Hugo模板内的调试输出。临时在模板中插入{{ template _debug/inspect .Site.Taxonomies.categories }}这一行会把分类数据以JSON格式打印到页面上结构清清楚楚。排查完记得删掉。也可以配合浏览器的开发者工具查看侧边栏元素的渲染结构确认HTML有没有正常输出。另外hugo server --verbose可以输出每个模板文件的解析路径能直观看出当前页面到底用的是哪个目录下的partial文件。这在排查“为什么我改的文件没生效”时非常有用。到了这一步博客的骨架基本立住了。我个人的体会是侧边导航栏永远跟着内容走而不是跟着模板走。先想清楚自己文章主要分几类、读者最可能找什么再去决定模块布局不要为了填满侧边栏而堆模块。如果发现自己的分类有七八个宁可侧边栏只显示前五个剩下的留给归档页模块一多导航的意义就变弱了。最后分享一个我一直在用的小习惯每次调整完侧边栏我都会在浏览器里分别用桌面端和移动端宽度各看一遍。这花不了两分钟却能避免大部分“桌面端好看手机端乱套”的问题。下一篇准备聊聊RSS订阅和基础的SEO meta标签那些是博客搭建完之后的另一件重要事。
阅读完成 · 觉得有帮助?
咨询建站