
这个系列写到第三篇前两篇我们把 Hugo 站点的基本目录、内容模型和 single 模板理顺了站点能跑起来文章能正常渲染内容也能正常输出了。但打开首页一看很多人会愣住——首页要么是一片空白要么是 Hugo 默认那句简单的无样式文本。原因其实不复杂在 Hugo 里首页本身就是一个列表页它走的不是 single 模板而是 list 模板体系。这个认知没转过来首页板块配置就总绕不过弯。这篇文章就把“首页板块配置”这件事彻底讲透从 list 模板的查找顺序、板块拆分思路到 Ubuntu 环境下的完整实操再到我踩过的几个坑一次性给齐。1. 首页板块配置的整体思路先理解首页为什么是 list 模板1.1 首页本质上就是一个特殊的列表页Hugo 里的页面类型分好几种普通文章页page、栏目页section、首页home、分类页taxonomy、标签术语页term。很多新手只盯着 single 模板因为写了一篇文章之后最先要解决的就是“文章页怎么排版”。但首页和栏目页走的是另一套逻辑它们不是展示“单个内容”而是展示“一批内容的聚合结果”。想通这一点非常关键。首页要干的事情无非是“把全站最新文章列出来、把重点分类列出来、把归档入口放出来”这些动作全都是列表操作。所以 Hugo 干脆把首页设计成一种特殊类型的列表页它有一个独立的 kind叫 home。它也参与模板查找而且查找方式和普通栏目页非常接近。打个比方single 模板是酒店的客房list 模板是酒店的前台和大堂。客房各有各的布置但大堂的作用是把所有客人都引到该去的地方。首页就是这个大堂它不需要自己写死哪篇文章放哪里它只需要告诉 Hugo把哪些内容聚合过来、按什么顺序排、每个条目渲染成什么样。这个“聚合规则”就是靠 list 模板里的 range、where、first 这些语句来实现的。1.2 模板查找顺序index.html、home.html、list.html 的优先级配置首页板块之前必须搞懂 Hugo 的模板查找顺序。我给一个常见优先级的简化版本优先级模板路径作用1layouts/index.html首页专用模板优先级最高2layouts/home.html首页模板的另一命名3layouts/_default/home.html默认目录下的首页模板4layouts/_default/list.html默认 list 模板会兜底所有列表页5主题目录下的同名模板主题自带模板优先级最低换句话说如果你在根目录layouts/下没有创建任何首页相关模板Hugo 最终会用layouts/_default/list.html或者主题里自带的 list 模板来渲染首页。这也是为什么一个刚hugo new site出来的新站点跑起来之后首页不是完全空白而是有一个最简单的页面——因为系统兜底用了 list 模板。这个查找顺序同时也是一个排障入口。很多“我改了模板但首页没变化”的情况都是因为同时存在多个候选模板而你改的那个优先级太低。比如你在layouts/_default/list.html里改了首页样式但站点里其实已经有layouts/home.html那你的改动就会完全被忽略。所以动手前先检查layouts/下到底有哪些文件再决定改哪个。1.3 板块化方案选型全写一个文件还是拆 partial把首页拆成多个板块在 Hugo 里大体有三种做法。第一种是把所有区块全写进index.html。适合首页非常固定、一年到头不怎么会动的站点。缺点是只要想调整顺序或增删区块就要翻一个长模板文件改起来容易误伤。第二种是把每个板块拆成独立的 partial 文件放在layouts/partials/home/下index.html只做骨骼拼接。这是我最推荐的做法。板块之间互不干扰某个板块出问题可以直接单看那个文件以后想在归档页或关于页复用同一个板块也可以直接{{ partial home/recent.html . }}调过来。第三种是配置驱动把板块列表写在hugo.yaml或config.toml的params里模板里用一个循环去遍历配置项渲染板块。适合多语言站点、多站点共用同一套主题的场景也适合站点的维护者不是技术人员、需要频繁调整首页板块顺序的情况。三种方案并不互斥。我实际项目里经常是“index 骨架 partial 板块 少量配置参数”混合使用。首页的固定区域用 partial需要动态启用的区域用配置驱动。这个后面实操部分会给出完整示例。2. 核心模板语法与板块的常见构成2.1 高频语法range、where、first、GroupByDate 怎么配合Hugo 模板使用的是 Go template 语法刚开始接触的人会觉得有点别扭尤其是{{ }}里嵌套各种管道符和括号。但首页板块真正高频用到的语句其实就那几个range负责遍历where负责过滤first负责取前几条GroupByDate负责按日期分组。先看一个最容易踩的坑range .Pages和range .Site.RegularPages的区别。.Pages返回的是当前上下文下的所有页面但在首页这个场景里它会把分类页、标签页、栏目页这些分支页面也算进来。如果你直接遍历.Pages首页列表里很可能混进去一个叫 “Posts” 的栏目链接或者 “Tags” 的聚合链接。而.Site.RegularPages返回的是全站所有普通文章页不含分支页面是首页“最新文章列表”这种板块最稳妥的数据源。再看where。它的典型用法是where .Site.RegularPages Section posts意思是把全站普通文章里属于posts栏目下的内容筛出来。如果你网站里既有博客文章、又有读书笔记、还有碎碎念你可以通过多个where分别取不同栏目的文章然后对应渲染成不同板块。first更简单first 5 $posts就是取前 5 条。注意它接的是一个变量所以一般先$posts : where ...把筛选结果存起来再用first截断。GroupByDate是归档板块的利器range .Site.RegularPages.GroupByDate 2006-01会把所有文章按年月分组每一组的.Key就是类似2025-06这样的字符串。配合.Pages再遍历就能做出一个按月份折叠的归档列表。2.2 最新文章板块最常用的首页板块怎么写最新文章区几乎是每个博客首页的标配。用 partial 拆出来大概是这样的结构文件放layouts/partials/home/recent.html{{ $posts : where .Site.RegularPages Section posts }} {{ $recent : first 6 $posts }} section classhome-section home-recent h2 classsection-title最新文章/h2 ul classpost-list {{ range $recent }} li classpost-item a classpost-title href{{ .Permalink }}{{ .Title }}/a time classpost-date datetime{{ .Date.Format 2006-01-02 }} {{ .Date.Format 2006年01月02日 }} /time p classpost-summary{{ .Summary }}/p /li {{ end }} /ul /section这里有几个细节值得单独说明。第一个是.Date.Format 2006年01月02日。Go template 的时间格式化模板是固定的2006-01-02 15:04:05这个日期就是 Go 语言的“参考时间”不是随便写的占位符。很多从 PHP 或 Python 转过来的朋友第一反应是写Y-m-d或者%Y-%m-%d结果页面上一片乱码。这是 Hugo 模板新手最容易犯的错没有之一。第二个是.Summary。Hugo 会自动从文章正文里截取摘要截取长度由配置文件里的summaryLength控制默认大概 70 个词。截取位置不一定符合你的预期后面问题排查部分我会专门讲怎么控制摘要。第三个是where .Site.RegularPages Section posts。如果你的博客文章不只放在posts一个栏目下这个过滤条件会把其他栏目的文章全部过滤掉。这时候可以直接用.Site.RegularPages不加过滤让首页展示全站最新文章或者根据你的栏目规划写多个板块分别展示。2.3 分类、标签、归档板块用列表页模板扩展首页信息密度除了最新文章首页通常还需要几个入口型板块帮助访客快速找到感兴趣的内容。分类板块的写法在 Hugo 里非常简洁{{ $categories : .Site.Taxonomies.categories.ByCount }} {{ $topCategories : first 12 $categories }} section classhome-section home-categories h2 classsection-title文章分类/h2 ul classcategory-list {{ range $topCategories }} li a href{{ .Page.Permalink }}{{ .Page.Title }} ({{ .Count }})/a /li {{ end }} /ul /section这里要注意的是.Page.Permalink。在分类板块中遍历得到的每一项是一个 taxonomy entry它自身有.Permalink但更规范的写法是拿到它对应的.Page对象再取.Page.Permalink。.Count则代表这个分类下有多少篇文章。标签云板块也是同样的套路只是把categories换成tags。如果你觉得标签太多可以用first 12限制数量或者配合.Alphabetical、.ByCount做排序。ByCount是按文章数量从多到少排序适合做“热门标签”展示。归档板块则用GroupByDate{{ range .Site.RegularPages.GroupByDate 2006-01 }} h3{{ .Key }}/h3 ul {{ range .Pages }} lia href{{ .Permalink }}{{ .Title }}/a/li {{ end }} /ul {{ end }}不过我要提醒一句分类、标签、归档、最新、推荐全部堆到首页首页会变得特别长首屏加载和信息密度都会失控。我自己的经验是首页最多保留三四个板块分类和标签只展示 Top N归档入口用一个“查看全部文章”的链接引导到独立归档页而不是把完整归档直接铺在首页。这个取舍后面还会再提。2.4 摘要与时间格式两个容易出细节问题的点摘要和时间是首页板块配置里最容易“看着差不多、实际差很多”的两个地方。关于摘要Hugo 的自动摘要确实省事但有两个隐患。第一个是自动摘要可能截在某个 HTML 标签中间导致页面样式错乱。第二个是摘要位置不是你想要的比如文章开头几行刚好是图片说明或者短代码截出来的摘要就很奇怪。最稳妥的控制方式是在文章 front matter 里手动指定摘要字段或者直接在正文里插入!--more--标记。Hugo 会把标记之前的内容作为摘要而且不会把自动截断的 HTML 残留带进来。在模板里的写法依然是.SummaryHugo 会自动区分是手动摘要还是自动摘要。关于时间除了上一节提到的2006格式化问题还有一个时区问题。Hugo 默认使用本地时间如果你在 Ubuntu 服务器上构建站点而服务器时区和你的本地时区不一致文章显示的日期可能比实际发布时间早一天或晚一天。解决办法是在站点配置里显式设置timeZone比如写timeZone Asia/Shanghai这样无论服务器在哪个时区渲染出的日期都会一致。这个坑在配置了自动构建之后尤其明显。3. Ubuntu 环境下实操从新建站点到多板块首页跑起来3.1 环境准备Hugo 版本、站点目录、示例内容先说版本问题。Ubuntu 的 apt 源里确实有 hugo但版本可能非常老老版本对很多新模板语法支持不完整。我建议优先用官方二进制或者 snap 安装。下面这张表是我在不同环境里的对比安装方式优点缺点apt install hugo一条命令版本可能偏旧extended 特性不一定有snap install hugo --classic安装方便自动更新snap 版本更新策略不一定可控GitHub Releases 下载二进制版本完全可控extended 版功能全手动更新稍微麻烦一点如果你要用的主题依赖 SCSS/SASS 编译那就必须装 extended 版 Hugo。普通版和 extended 版的区别就是是否内置了 SCSS 编译相关功能很多商业主题和热门开源主题都默认要求 extended。检查版本的命令是hugo version看到输出里有extended字样就说明没问题。环境准备好之后建一个测试站点hugo new site hugo-home-demo cd hugo-home-demo hugo new posts/first-post.md注意默认新建的文章是 draft 草稿状态直接hugo server启动时你不会在首页看到它。本地预览草稿要用hugo server -D。这也是新手经常“页面空白”的原因之一不是模板问题而是内容全被标记成了草稿。在 Ubuntu 下还有一个很容易被忽略的权限问题。如果你是从别的地方拷贝来的站点目录或者用sudo执行过hugo new目录里的文件属主可能是 root导致后续写入public/时报permission denied。稳妥做法是先把站点目录权限归到当前用户sudo chown -R $USER:$USER ~/hugo-home-demo3.2 首页骨架与 partial 拆分把“板块”拆出来现在开始正式配置首页。第一步是创建首页骨架layouts/index.html。这里要根据你的主题情况决定写法如果主题里有baseof.html那么首页模板只需要重写main区块如果没有 baseof那你需要自己写完整的 HTML 结构。判断方法很简单去看看你的主题layouts/_default/下有没有baseof.html或者站点根目录layouts/_default/下有没有。假设你的主题自带 baseof那么首页骨架可以写成{{ define main }} main classhome {{ partial home/recent.html . }} {{ partial home/categories.html . }} {{ partial home/tags.html . }} /main {{ end }}如果你没有 baseof就需要把html、head、body这些标签都写出来然后同样在主体部分引入 partial。第二步是创建layouts/partials/home/目录然后把最新文章、分类、标签这几个板块分别写入对应的 partial 文件。结构大概是这样layouts/ ├── index.html └── partials/ └── home/ ├── recent.html ├── categories.html └── tags.html为什么要单独建一个home/子目录因为一个站点的 partial 可能非常多如果全部平铺在partials/下很快就分不清哪个是给首页用的、哪个是给单页用的、哪个是给导航用的。按功能建子目录是让模板目录变整洁的好习惯。第三步是调整站点配置。在hugo.toml或hugo.yaml里设置必要的参数baseURL https://example.com/ title 我的 Hugo 博客 languageCode zh-cn timeZone Asia/Shanghai summaryLength 80 paginate 10 [params] description 记录技术、生活与思考这里timeZone就是前面说的日期时区问题summaryLength控制自动摘要长度paginate虽然首页板块不一定用到但归档页和列表页会用到。3.3 用站点配置驱动板块改配置就能调首页如果你想让首页板块的顺序、数量、展示条数都可以方便调整那就用配置驱动的方式。先在hugo.toml里增加一个板块列表[params] [[params.homeSections]] name 最新文章 type posts limit 5 showDate true [[params.homeSections]] name 随想 type notes limit 3 showDate false然后写一个通用板块 partial文件放layouts/partials/home/dynamic.html{{ if .Site.Params.homeSections }} {{ range .Site.Params.homeSections }} {{ $posts : where $.Site.RegularPages Section .type }} {{ $limit : default 5 .limit }} {{ $showDate : default true .showDate }} section classhome-section h2 classsection-title{{ .name }}/h2 ul classpost-list {{ range first $limit $posts }} li a href{{ .Permalink }}{{ .Title }}/a {{ if $showDate }} time datetime{{ .Date.Format 2006-01-02 }} {{ .Date.Format 2006年01月02日 }} /time {{ end }} /li {{ end }} /ul /section {{ end }} {{ end }}注意这里在range里面上下文已经从站点变成了配置项访问站点对象时必须用$加前缀所以写的是$.Site.RegularPages而不是.Site.RegularPages。这个细节很容易漏漏了就报错或者页面空白。然后在首页骨架里加一行{{ define main }} main classhome {{ partial home/recent.html . }} {{ partial home/dynamic.html . }} {{ partial home/categories.html . }} /main {{ end }}这样你以后想调整首页最新文章之外的板块只需要改配置文件的params.homeSections列表完全不用碰模板。对于多语言站点甚至可以在不同语言配置里写不同的板块列表同一个主题渲染出不同的首页结构。3.4 本地调试与构建hugo server 的几个实用参数配置完成后在 Ubuntu 终端里启动本地服务hugo server -D -p 8080-D表示把草稿也渲染出来-p 8080指定端口避免默认 1313 被占用。启动后终端会输出一个访问地址一般是http://localhost:8080。你每保存一次模板或内容页面会自动热重载。如果页面结构复杂、模板渲染慢可以用--templateMetrics参数查看每个模板的渲染耗时hugo server -D --templateMetrics这个参数会输出一张表格列出每个模板渲染了多少次、花了多长时间。首页板块多的时候可以用它定位到哪个 partial 是性能瓶颈。实际导出的静态站点也一样用hugo --templateMetrics可以针对生产构建做分析。最终生成静态文件用hugo默认输出到public/目录把整个public/上传到服务器或部署平台即可。构建时如果发现旧文件残留很奇怪可以加--gc参数做一次垃圾回收清理掉不再使用的缓存文件。4. 常见问题与排查技巧实录4.1 首页空白先查这三个位置首页空白是最常见的问题但排查路径其实很固定。先检查有没有内容content/目录下有没有文章文章是不是全是 draft 状态用hugo server -D启动后能不能看到接着检查模板路径layouts/index.html是放在了layouts/根目录而不是layouts/partials/下。这个错误我见过太多次模板文件放错了目录改了半天都不生效。最后检查终端日志Hugo 启动时如果模板有语法错误终端会直接报错并把出错文件和行号标出来。很多空白页面不是“没有模板”而是“模板坏了”。给你一个排障顺序列表hugo version确认版本find layouts -type f查看模板文件实际位置hugo server -D看终端输出有没有报错浏览器强制刷新CtrlShiftR排除缓存用curl http://localhost:1313/看返回的 HTML 是否正常4.2 .Pages 和 .RegularPages 混用导致栏目页混进列表症状很典型首页文章列表里出现 “Posts”“About”“Tags” 这种不像文章的链接。原因是模板里用了.Pages而不是.RegularPages。.Pages是“当前上下文下的所有页面”包括栏目页、分类页这些分支页面。首页作为一个特殊列表页它的.Pages范围其实是整个站点的所有页面集合所以会混入各种非文章页面。而.RegularPages只包含普通内容页分支页面一律排除。如果你实在不小心用了.Pages补救方式有两种。一种是换成.RegularPages另一种是在where里过滤 kind{{ range where .Site.RegularPages Kind page }} ... {{ end }}顺便记住 Hugo 的几种 kind 值home是首页section是栏目页page是普通文章页taxonomy是分类聚合页term是具体某个标签或分类的术语页。搞清楚这几个值过滤逻辑就不会写错。4.3 摘要截断和 HTML 标签残留怎么破如果你发现首页列表里某些摘要带着半个p标签或者排版突然乱了大概率是自动摘要截断在了 HTML 标签中间。Hugo 的自动摘要虽然会尽量保证标签闭合但在复杂正文里依然可能出现奇怪结果。最可靠的解决方案是使用手动摘要标记!--more--。在文章正文想截断的位置插入一行这里是摘要部分。 !--more-- 这里是正文剩余部分。Hugo 会把!--more--之前的内容作为摘要模板里依然用.Summary输出不需要改模板。这个方法的好处是完全可控摘要一定是你想展示的那段话。另外一个方案是在 front matter 里手动写summary字段--- title: 我的文章 summary: 这是一段手写的摘要内容。 date: 2025-06-01 ---如果模板里输出摘要后还想去掉所有格式可以加plainify过滤器{{ .Summary | plainify }}。但要注意这样会把摘要里的加粗、链接全部变成纯文本适合卡片式布局不适合富文本摘要。4.4 模板不生效与缓存问题“我改了模板页面就是不变”这个问题通常不是 Hugo 的锅而是下面几类原因。第一类是文件名或路径问题。比如你把index.html写成了indexs.html或者放进了partials/目录。Hugo 不会管你的文件名是否合理它只会按约定去查找。多一个字母少一个字母它就直接跳过用兜底模板渲染。第二类是优先级冲突。前面 1.2 节讲过Hugo 会按顺序查找首页模板。如果你站点里同时存在layouts/home.html和layouts/index.html但你改的是低优先级的那个自然看不出来变化。第三类是缓存。本地hugo server一般会热重载但你改hugo.toml这类配置文件时偶尔不会自动刷新。遇到这种情况手动重启一下hugo server。浏览器端也可能缓存旧的 CSS 和 JS按 CtrlShiftR 强制刷新或者开着开发者工具的 Disable cache 选项。第四类是 partialCached 使用不当。如果你给某个 partial 加了缓存但又没有传足够多的缓存 key内容更新后可能还渲染旧数据。这种情况下检查你的partialCached调用确保把该参与变化的变量都作为参数传进去。4.5 Ubuntu 相关的权限、版本和端口问题最后整理几个 Ubuntu 环境下的特别问题。权限问题前面提过最主要的症状是hugo构建时报permission denied或者hugo new创建文件后你没法编辑。用ls -l查看文件属主然后sudo chown -R $USER:$USER 站点目录解决。版本问题主要表现为某些模板语法不支持。老版本 Hugo 对.Site.RegularPages支持是没问题的但新版支持的hugo.yaml配置文件、resources处理、图片滤镜等特性在老版本上会直接报错。所以装完 Hugo 之后第一件事是hugo version确认版本别等部署到服务器上跑 production build 才发现问题。端口问题比较直接hugo server默认监听 1313如果这个端口被其他进程占用终端会提示地址已被使用。用下面命令找到占用进程lsof -i :1313然后按 PIDs 决定是 kill 掉旧进程还是换一个端口启动hugo server -p 8080。还有一个不算坑但容易被忽略的小细节Ubuntu 上如果你用nohup或 systemd 做自动部署环境变量里的 PATH 可能跟手动执行终端时不一样导致 cron 或 systemd 找不到hugo命令。解决办法是用绝对路径运行hugo比如/usr/local/bin/hugo或者在 systemd service 文件里显式指定EnvironmentPATH/usr/local/bin:/usr/bin:/bin。我自己在实际折腾首页板块时最大的体会是板块不是越多越好信息层级比信息数量重要。刚开始我恨不得把最新文章、分类、标签、归档、推荐全放首页结果首屏特别长访客反而不知道先看什么。后来砍到只剩“最新文章 重点分类 归档入口”三个板块打开速度和浏览体验都好了。另外一个小技巧把首页的模板改动用git管理起来每次改完先hugo --gc清理再本地预览确认没问题再部署能省掉很多“线上和本地不一样”的麻烦。如果你现在正卡在首页空白或者列表混入栏目页的坑里按上面 4.1 和 4.2 的顺序查一遍基本都能解决。