09 Hugo自动构建时间细节即workflow设置 凌晨在移动端的gh上更新了一篇文章,push以后网页并没有显示,意识到这可能跟github上的构建时间有关。因为我的文章日期是由我键入的标题前缀决定。本地hugo构建时间参考本机,gh的提交和上传时间在服务器端使用UTC存储,也就是说,gh上hugo构建时的服务器时区与键入文章日期的时区并不一致,导致文章被识别成了未来文章,不予显示。如果想解除不显示未来文章的限制,可以在config配置里添加:buildFuture = true 另一个方案则是修改workflow,使用本地时区:- name: Build Hugo run: | export TZ='Asia/Shanghai' hugo --minify 未来文章也可以配合添加wf的定时发布自动更新推送。on: push: branches: [main] schedule: - cron: '0 0 * * *' # 时差可以修改第二位数字进行更改 …待续2026-04-30 小岛建设
08 多层文件夹引用bug 改bug时翻文件夹翻到麻木,layouts文件全部堆在default和partial底下,继上次整理css文件后再一次忍无可忍重新分类整理。tree layouts /F ---整理前--- \LAYOUTS │ ... ├─all-articles ├─partials │ all-articles-cards.html │ articles-range.html │ comments.html │ commentsForbl.html │ copyright.html │ get-title.html │ header.html │ indexright.html │ posts-words.html │ sectionnav.html │ sidebar.html │ └─_default │ 03_01.html │ alchemy.html │ baseof.html │ bloglinks.html │ guestbook.html │ search.html │ section.html │ sidebarpage.html │ single.html │ ├─06mumble │ 06_00.html │ └─all-articles all-articles-cards.html all-articles.html ---整理后--- \LAYOUTS │ ... ├─partials │ ├─article │ │ all-articles-cards.html │ │ articles-range.html │ │ get-title.html │ │ │ ├─baseof │ │ copyright.html │ │ header.html │ │ posts-words.html │ │ sidebar.html │ │ │ └─page │ comments.html │ commentsForbl.html │ indexright.html │ sectionnav.html │ └─_default │ baseof.html │ section.html │ single.html │ ├─page │ alchemy.html │ bloglinks.html │ guestbook.html │ search.html │ sidebarpage.html │ └─section ├─03 │ 03_01.html │ ├─06 │ 06_00.html │ └─07 all-articles-cards.html all-articles.html layouts下除了通用的三个模版,其他模版都归入不同文件夹下,在页面的引用里额外添加type类型输入文件夹命名,继承一样在cascade下面添加。partial部分则写用脚本一键修改所有引用,例如:{{ partial "get-title.html" . }} ↓ {{ partial "article/get-title.html" . }} 因为懒+有一些写作界面强迫症,所以在搭建博客后新建的所有md文件里都没有用frontmatter写title,所以用了额外的文件名读取生成一个partial页,就是在这里出现了一个bug,search.html里的部分在修改引用部分前是:{{ range .Site.RegularPages }} <div data-uri="{{ .RelPermalink }}" data-title='{{partial "get-title" .}}' data-date="{{ .Date.Format " 2006-01-02" }}" data-content='{{ .Plain }}'> </div> {{ end }} 仅仅添加上级文件夹名后开始报错。process: "..layouts\_default\baseof.html:14:1": apply base template failed unterminated quoted string 但把引用部分提前后再引入,则不会报错。{{ range .Site.RegularPages }}{{$title:=partial "article/get-title" .}} <div data-uri="{{ .RelPermalink }}" data-title='{{$title}}' data-date="{{ .Date.Format " 2006-01-02" }}" data-content='{{ .Plain }}'> </div> {{ end }} 跑起来了,但不知道怎么跑的,也不知道为什么刚刚没跑,陷入迷茫……2026-04-30 小岛建设
07 Hugo分页函数与摘要显示设置 .Paginate 是 Hugo 内置的分页函数,可以将大量内容分成多页显示,避免单页面加载太多文章。{{ range .Paginator.Pages }} <h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2> <p>{{ .Summary }}</p> {{ end }} 按section主题分页,再添加摘录与展开缩放功能,实现逻辑大概如下:{{ $articles := .RegularPagesRecursive.ByDate.Reverse }} {{ $paginatedArticles := .Paginate $articles 10 }} {{ range $index, $article := $paginatedArticles.Pages }} <article class="article-card"> {{/* 文章标题 */}} <h2><a>{{ .Title }}</a></h2> <input type="checkbox" id="expand-{{ $index }}" class="expand-checkbox"> {{/* 文章详情(摘录) */}} <label for="expand-{{ $index }}" class="recent-abstract"> {{ $article.Content }} // 点击文章内容可展开/折叠 </label> </article> {{ end }} 其中摘要class的css关键在于如下:.recent-abstract { display: -webkit-box; -webkit-line-clamp: 7; /* 显示7行 */ -webkit-box-orient: vertical; } 但当内容中有特殊格式时,比如:code/pre,摘要行数可能会被上下撑开,显示不止指定行数。处理摘要中code/pre的代码块时,可以采用css限制去修改它的具体样式:.recent-abstract pre, .recent-abstract code { display: -webkit-box; /* -webkit-line-clamp: 2; */ -webkit-box-orient: vertical; overflow-y: auto;// 禁止滚动就用hidden max-height: 33px; } 展开摘要栏时,也要还原css展开样式:.expand-checkbox:checked~.recent-abstract pre, .expand-checkbox:checked~.recent-abstract code { -webkit-line-clamp: unset; max-height: none; } 有inline-block的特殊样式时,也会改变-webkit-的截断逻辑,可以在摘要样式内修改display:.recent-abstract .special-type { display: inline; } 也可以靠hugo的内置语法把内容转化成纯文本再截断,但会导致特殊格式丢失:{{ $article.Plain | truncate 500 }} 但实际情况还是会触发一个奇怪的bug,有时pre/code代码块会被无视,不计入行数的运算,于是又会导致文章摘要卡片变长,于是添加了一个额外的预防措施,先定义摘要板块的最大高度,再在展开后取消最大高度:<!-- 定义最大高度 --> .recent-abstract { max-height: 300px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 7; /* 显示7行 */ -webkit-box-orient: vertical; } <!-- 点击后移除限制 --> .expand-checkbox:checked~.recent-abstract { max-height: none; /* overflow: visible; */ -webkit-line-clamp: unset; /* 移除行数限制 */ /* 保留其他所有样式不变 */ } 2026-04-23 小岛建设
06 导航高亮及面包屑相关 侧边栏导航高亮设置:{{ $currentPage := . }} {{ range sort .Site.Sections "Section" }}{{if .Params.navtitle}} <div class="sidebar-nav"> <input type="checkbox" id="{{.Params.navtitle}}-toggle" class="toggle sidebartog"> <label for="{{.Params.navtitle}}-toggle" class="toggle-label"> <a href="{{.RelPermalink}}" class="{{if eq $currentPage.FirstSection.RelPermalink .RelPermalink}}active {{ end }}"> {{ .Params.navtitle }}</a> </label> <div class="togg {{if .Params.display}}{{else}}toggle-content{{end}}"> {{ $section := .Site.GetPage "section" .Section }} {{ range sort $section.Sections "Path" }} {{if .Params.display}} {{else}}<a href="{{ .RelPermalink }}" class="{{ if eq $currentPage.CurrentSection.RelPermalink .RelPermalink}}active{{ end }}"> {{ .Title }} </a> {{end}} {{ end }} </div> </div> {{ end }} {{ end }} 使用{{$currentPage}}定义当前页面。.RelPermalink指当前关联链接,没有前缀时取决于.,有前缀时取决于前缀,在range内部.代表当前遍历到的页面。面包屑代码:<nav class="breadcrumb"> <a href="{{ .Site.BaseURL }}">{{.Site.Title}}</a> //首页 {{ if and .File (not .IsHome) }} //避免再度出现首页 > <a href="{{ .FirstSection.RelPermalink }}">{{ .FirstSection.Title }}</a> {{ end }} {{ if ne .FirstSection .CurrentSection }} > <a href="{{ .CurrentSection.RelPermalink }}">{{ .CurrentSection.Title }}</a> {{ end }} {{ if eq .Kind "page"}} //识别文章页 {{if .Title}} <span>> {{.Title}} {{else}} <span>> {{ .File.ContentBaseName | replaceRE `^\d{4}-\d{2}-\d{2}_` "" | replaceRE "_" " " | title }} </span> {{end}} {{end}} </nav> 读了hugo官方文档后发现hugo应该已经支持没有默认title时文件名作为.Title,但还没有研究过要具体如何配置,所以暂且保留了旧方案。2026-04-23 小岛建设
05 Code横向撑爆父容器的未解之谜 开始在这个专题下塞入代码块后,md格式下的代码块会随机把父容器撑爆,起初一劳永逸的解决方案是添加换行:pre, code { margin: 0; white-space: pre-wrap; word-break: break-word; } 但这样毫无疑问降低了代码的可读性,最好的方案还是使代码块的宽度继承父容器宽度,使长代码可以通过滚轮阅读:pre, code { max-width: 100%; margin: 0; overflow-x: auto; } 但这个css样式无论如何都不能生效,依然会撑爆父容器。目前仍不知道原因是否是因为在桌面端自适应屏幕里我的sidebar部分用了absolute布局固定,内容区用padding强行固定到右侧(但隐隐又感觉不是)。父容器的box-sizing/max-width都设置了,起初怀疑是否和部分父容器的inline-block有关,修改成block并添加了max-width仍然未有解决问题。最后解决方案是在直接父容器里添加上同一行overflow:.article-card { max-width: 100%; overflow-x: auto; //添加此行 border: 1px solid #ddd; margin: 0 auto; padding: 5px 20px 10px; box-sizing: border-box; background: #fff; } 于是问题解决了,但不知道实现路径是什么,因为在这个父容器内实际上并没有显示滚动,滚动的仍然只有代码块部分,但如果不添加此行,就仍然会被撑爆。由于解决方案超出想象,所以还是记录一下。在今天的调试中终于忍无可忍把整坨style.css分开成几个css方便维护了。使用一个main.css导入所有其他css,同样要注意顺序避免相互覆盖:@import '/css/layouts/style.css'; @import '/css/layouts/...'; ... @import '/css/media.css'; 在baseof.html里引用main.css单文件: <link rel="stylesheet" href="/css/main.css"> 2026-04-22 小岛建设
04 Hugo内置语法上下篇功能实现 这也算是一个从notion跳出来选择自搭博客的关键原因了,点开阅读后要手动返回之前页面,然后又要重新定位位置跳转新文章,也许notion有插件或功能可以实现这个功能,但hugo自带还是太方便了,如果是所有文章上下翻页:{{ with .Prev}} //{{ with .Next}} <a href=".RelPermalink"> {{end}} hugo的.Prev和.Next并不对应字面意义,.Next指向排序列表中索引更小的,.Prev指向索引更大的。特定Section内实现排序也可以用.PrevInSectioin和.NextInSection实现。但如果目录是多层文件夹嵌套结构:->content/A/a-a.md ->content/A/a-b.md ->content/A/a-c.md 并希望实现专题下所有文章的前后篇翻阅,到尽头指向另一专题,就无法通过已有模块实现了。起初用了一套非常冗杂的代码实现,用到了hugo的稳定排序(stable sort)和索引赋值:{{ $allPages := where .Site.RegularPages "Section" .Section }} {{ $allPages = sort $allPages "File.Path" }} {{ $allPages := sort $allPages "File.BaseFileName" }} {{ $currentIndex := -1 }} {{ range $index, $page := $allPages }} {{ if eq $page $ }} {{ $currentIndex = $index }} {{ end }} {{ end }} {{ $parentSection := .CurrentSection.Parent }} //获取父栏目 {{ $grandParentSections := sort $parentSection.Parent.Sections "File.Path" }} //获取所有同级父栏目 {{ $parentIndex := -1 }} //当前父栏目的索引 {{ range $index, $section := $grandParentSections }} {{ if eq $section.File.Path $parentSection.File.Path }} {{ $parentIndex = $index }} {{ end }} {{ if lt $currentIndex (sub (len $allPages) 1) }} {{ with index $allPages (add $currentIndex 1) }} <a href="{{ .RelPermalink }}"> 上一篇</a> {{ end }} {{ if gt $currentIndex 0 }} {{ with index $allPages (sub $currentIndex 1) }} <a href="{{ .RelPermalink }}"> 下一篇</a> {{ end }} 但一旦设计到一些特殊页面,比如:->content/introduce.md 生成就会报错,于是就要添加更多判断,既不美观,又不直观。针对这种情况hugo其实也已经给出了其他自订制方案,比如使用.FirstSection -> content下所有的一级文件夹。方案一-ByWeight:{{ $pages:=.FirstSection.RegularPagesRecursive.ByWeight }} {{with $pages.Next}} <a href=".RelPermalink">上一篇</a> {{end}} {{with $pages.Prev}} <a href=".RelPermalink">下一篇</a> {{end}} 方案二-sort:{{$pages:=sort .Site.Section "File.BaseFilename"}} {{with $pages.Prev}} //上一篇 {{with $pages.Next}} //下一篇 第一篇和最后一篇的专题间切换,用if判断添加到方案一/二的下面:{{ $sections := .Site.Sections }} {{ $sections = sort $sections "File.Path" }} {{ $prevSection := $sections.Next .FirstSection }} //下一专题 {{ $nextSection := $sections.Prev .FirstSection }} {{ with $PrevSection }} <a href="{{ .RelPermalink }}">上一专题</a> {{ end }} 因为是静态页面,所以选择了便于专题内阅读的翻页处理,如果希望直观地按总览排序:{{ $pages := sort .Site.RegularPages "Date" "asc"}} 其实.Prev/.Next就可以直接实现这个方案了,但sort可以配合front matter定义更多排序字段,比如:{{ $pages := sort .Site.RegularPages "Params.order" "desc" }} 再结合稳定排序的特性,也可以自定义多样性排序。2026-04-19 小岛建设
03 Git推送和cloudflare托管 通过SSH推送到git仓库可以省去很多麻烦,借助cloudflare的网络配置可以在未备案的情况下更流畅地访问网址,除了购买域名外,静态博客的托管上线靠gh和cf的免费方案已经完全够用(让我们说谢谢赛博菩萨)。第一步 初始化git仓库进入博客根目录,输入:git init 在当前文件夹下创建一个 .git 文件夹,并将其变成一个 Git 仓库。为了提交成功,如果没有设置过git用户信息,可以进行以下设置:git config --global user.name "UserName" git config --global user.email "email@example.com" 第二步 创建仓库并链接本地在github上新建一个repository仓库,将本地git仓库链接并推送。git remote add origin https://github.com/UserName/RepositoryName.git git add . //添加全部文件 git commit -m "首次推送" git push -u origin master //确认分支并完成推送 第三步 配置并使用SSH推送查看公钥:cat ~/.ssh/id_ed25519.pub 完整复制输出的全部内容并添加到GitHub:Settings - SSH and GPG keys - New SSH key -``` 完成相关填写。 本地测试SSH连接:ssh -T git@github.com 配置成功将显示正确回复。补充:其他git推送常用命令记录查看当前分支:git branch 查看当前状态git status 创建并切换至新分支:git checkout -b new-branch 切换至已有分支:git checkout another-branch 清空当前分支内容:git rm -rf . 将某文件夹内容添加到当前分支:cp -r public/* . 检查远程仓库URL:git remote -v 如果需要更改仓库URL:git remote set-url origin https://github.com/UserName/RepositoryName.git 解除远程仓库关联:# 查看当前远程仓库 git remote -v # 删除远程仓库关联 git remote remove origin # 或者 git remote rm origin 清除所有git历史重新初始化:# 1. 删除旧的 Git 关联 rm -rf .git # 2. 重新初始化新的 Git 仓库 git init # 3. 添加文件并创建新的第一次提交 git add . git commit -m "初始化新仓库" 全局禁止换行符转成 Windows 格式warning:git config --global core.autocrlf false 第四步 cf配置在cloudflare上买过域名后可以自动添加配置dns记录,关联github仓库也非常丝滑。生成public的步骤可以在本地/gh/cf任一位置实现。在cf的pages页面进入项目 - Custom domains选项卡 - Set up a custom domain -```输入域名。当然,cf也有一定的不便,比如注册的域名为了cf的一体化策略而无法将DNS解析托管到其他平台,但对静态博客托管影响较小,提高大陆访问便捷度已经很足够。2026-04-16 小岛建设
02 Hugo MD文件收纳方案 hugo对md文件统一管理的常见方案之一是在archetype文件夹内添加模板样式,然后每次通过cmd/powershell的hugo new指令生成对应模版样式。但如此每次都要求输入:hugo new --kind archetype-01 01file/01text.md 生成的md文件继承archetype中模版的格式。但我希望实现:将notion中的md文件一键分类入对应文件夹后,不必再使用终端新建,只通过IDE在对应文件夹内新建md文件就可以自动继承父模版,不必在单个博客md文件内生成多余的front matter内容。我的需求是主分类文件夹下包含子文件夹分类,子文件夹有固定tag,避免手动添加或多次键入date和title。第一步 通过powershell的脚本完成一键分类特殊要求是显式指定UTF-8编码处理避免生成乱码:$basePath = ".\content\03archetype" # 中文标签 -> 英文子文件夹映射 $tagMap = @{ "book" = "01" "movie" = "02" } Get-ChildItem $basePath -Filter *.md | ForEach-Object { # 只读取前4行并指定编码 UTF8 $lines = Get-Content $_.FullName -TotalCount 4 -Encoding UTF8 $bodyLines = Get-Content $_.FullName -Encoding UTF8 | Select-Object -Skip 4 $title = "" $tag = "" $date = $null # 用 null 来表示没有找到日期 foreach ($line in $lines) { if ($line -match '^#\s*(.+)$') { $title = $matches[1] } elseif ($line -match '^Tags:\s*(.+)$') { $tag = $matches[1] } elseif ($line -match '^date:\s*(.+)$') { try { $dateObj = Get-Date $matches[1] $date = $dateObj.ToString("yyyy-MM-dd") } catch { $date = (Get-Date).ToString("yyyy-MM-dd") } } } if ($title -eq "") { return } # 如果没有在 Front Matter 中找到日期,跳过日期的修改 if ($date -eq $null) { # 如果没有 `date` 字段,则不修改日期,保持原样 $date = "" # 可以选择空字符串或其他标识,表示不修改 `date` } $newHeader = @( '---' "title: `"$title`"" "tags: [`"$tag`"]" "date: $date" "draft: false" '---' '' ) $newContent = $newHeader + $bodyLines # 写回文件时显式 UTF-8 编码 Set-Content $_.FullName $newContent -Encoding UTF8 # 根据 Tags 移动文件到英文子文件夹 if ($tagMap.ContainsKey($tag)) { $targetDir = Join-Path $basePath $tagMap[$tag] if (!(Test-Path $targetDir)) { New-Item -ItemType Directory -Path $targetDir | Out-Null } Move-Item $_.FullName (Join-Path $targetDir $_.Name) } } 同时可以顺便用脚本将分好类后的多余文本删除/重命名文件标题。第二步 在每个文件夹下新建_index.mdHugo的生成逻辑里,文件夹只有在有_index.md的文件时,才可以解析显示页面,不报错。页面md文件的内容是front matter,根据个性化需求填写。例如:\content\05architect\_index.md --- title: "Ⅴ测试篇" navtitle: "测试篇 ▪ 信息留档" draft: false --- \content\05architect\05_01\_index.md --- title: "小岛建设" cascade: tags: ["小岛建设"] draft: false --- 其中cascade下的内容就是关键的继承部分,完成这个配置后,新建博文md文件就不必再手动填写tags等内容了,只需要在对应tag文件夹下新建md,键入文本,即可生成博文内容。第三步 避免重复键入日期与title因为不愿在md里键入额外的frontmatter,所以每次新建md文件会将时间添加入文件名,一来是键入直接快速,二来也方便排序管理,hugo很贴心地提供了文件名下对应格式的日期识别功能,具体实现只用在配置toml中写入:[frontmatter] date = [":filename",":default" ] 新建md时日期格式以"YYYY-MM-DD"格式输入即可识别。而title部分研究发现toml内不支持动态读取(也可能是我没有调试成功),所以我采用了很呆的方法:为了文件管理便捷性放弃配置title。转而在需求显示标题的layouts中用go template进行文件名节选,实现网页浏览可读性。 {{ if .Title }} {{ .Title }} {{ else }} {{ $filename := .File.ContentBaseName }} {{ $title := replaceRE `^\d{4}-\d{2}-\d{2}_` "" $filename }} {{ $title | replaceRE "-" " " | title }} {{ end }} 例如本篇博文的文件标题为:<2026-04-14_02_hugo MD文件收纳方案.md>,经过处理后在前端显示为<02_hugo MD文件收纳方案>。2026-04-14 小岛建设
01 Hugo框架选择 搭建博客的第一个卡点在写好静态前端页面后,想自动监听生成文章的上下篇功能。起初准备用node.js写脚本实现,但这涉及到怎样自动运行脚本、如何同步热重载等等问题。除了上下页外,还包括想实现导航栏的同步、分区等等。若依靠js实现动态加载功能,又会顾忌加载速度/js禁用等问题(忽略SEO的情况);还有其他类似于采用在线编辑器等方案实现远程运行脚本,但这仍然依赖在线环境,非常不便。同时后续还有将从notion内导出的md文件解析成html格式的需求等等,最终决定根据这些需求参考有相应功能的博客框架,了解可实施方案后采用了hugo。第一步:安装hugo(windows系统采用了choco,需要管理员权限安装)choco install hugo -confirm //根据需求也可以安装extended版本 hugo version //验证版本 第二步:创建项目并进入文件夹d: //切换到目标磁盘 hugo new site Melusinn cd Melusinn 第三步:研究主题在hugo的theme页内并没有找到心怡的选择,于是把下载hugo前自己搭好的html、css丢进了layouts和status里。然后开始正式着手研究hugo框架。第四步:本地预览hugo server 浏览器打开本地http://localhost:1313展示网站效果。上传前需求hugo生成可用的public文件,而不是hugo server,否则默认baseURL http://localhost:1313/,而非toml中配置的URL。hugo框架内有一些基本规则,contents内放md文件,如果要以文件夹形式分类,每个文件夹下都需要放一个_index.md表示本个文件夹有页面显示(否则无法显示/html似乎同样可实现)。layouts下新建index.html渲染首页,default文件夹内可以建baseof.html(公用部分)/single.html(md默认转换格式)/section.html或list.html(contents下子文件夹页面样式)。hugo server时本个ps对话框被热重载占用,ctrl+c结束任务进程。2026-04-13 小岛建设