【前情提要】 文章写好了,标签配好了,列表页也有了。老周说:“明天让博客有点‘人样’,加图片、代码块、短代码。”小林打开 PaperMod 文档,准备大干一场。但他不知道,Hugo 的 shortcode、代码高亮、TOC,每一个都有细节。
一、老周的审美暴击
早上到工位,小林得意地打开博客,给老周展示昨天写的第一篇文章。
老周看了一眼,沉默了三秒。
“你知道你这篇文章像什么吗?”
“像什么?”
“像上世纪九十年代的 BBS。”老周说,“纯文字,没图,代码块连高亮都没有。你管这叫技术博客?”
小林委屈:“我大学写实验报告就这样……”
“大学是大学,公司是公司。”老周拉过椅子,“今天教你三件事:图片、代码高亮、TOC。搞完你这篇博客至少能看。”
二、图片:路径、格式、大小
“先说你昨天那张 Hugo Logo。”老周说,“你放哪了?”
“static/images/ 啊。”
“对。”老周说,“但你有没有发现一个问题:你每次换主题,图片路径不变,但显示效果可能变。为什么?”
“因为……主题的 CSS 不一样?”
“对。图片的‘大小’是主题的 CSS 控制的,你写 ![]() 只是插入图片,不控制大小。所以,图片本身的尺寸不要太大,不然主题再好看也救不了你。”
老周让小林检查图片大小:
ls -lh static/images/
输出:
-rw-r--r-- 1 devops devops 2.3M Sep 13 10:00 hugo-logo.png
“2.3M?”老周说,“一张 Logo 2.3M,你首页加载要等三秒。”
“那怎么办?”
“压缩。”老周说,“图片宽度 800 到 1200 像素就够了,再大没意义。你用 imagemagick 压缩。”
sudo apt install -y imagemagick
convert hugo-logo.png -resize 800x hugo-logo-small.png
“convert 是 ImageMagick 的命令,-resize 800x 表示宽度缩到 800,高度等比例。”老周说,“压缩完看看。”
ls -lh static/images/
输出:
-rw-r--r-- 1 devops devops 80K Sep 13 10:05 hugo-logo-small.png
-rw-r--r-- 1 devops devops 2.3M Sep 13 10:00 hugo-logo.png
“2.3M 变 80K。”小林惊了,“这差距也太大了吧。”
“所以,以后写文章,图片先压缩再上传。 你可以用 TinyPNG 在线压,也可以用 ImageMagick 本地压。反正别把相机原图直接丢进去。”
小林记下:
- 图片宽度 800~1200px 够用
- 用
convert -resize 800x压缩 - 图片放
static/images/,引用用/images/xxx.png
“还有一个坑。”老周说,“图片格式。 PNG 适合截图和透明背景,JPG 适合照片。你截图用 PNG,照片用 JPG。别搞反,不然要么模糊,要么巨大。”
“那 WebP 呢?”
“WebP 更小,但不是所有浏览器都支持。你现在别折腾,PNG 和 JPG 够用了。”
三、图片的进阶用法:Figure Shortcode
“普通的 ![]() 够用,但 PaperMod 支持一个更高级的写法,叫 shortcode。”老周说。
“shortcode?”
“你可以理解成 Hugo 的‘扩展标签’。Hugo 内置了一些 shortcode,主题也可以定义自己的 shortcode。PaperMod 内置了 figure。”
小林打开文章,找到图片那行,改成:
{{< figure src="/images/hugo-logo-small.png" title="Hugo Logo" caption="Hugo 的官方 Logo" >}}
保存,刷新。
图片出来了,下面多了一行小字:“Hugo 的官方 Logo”。
“看到了吧?”老周说,“figure 会自动加 figure 和 figcaption 标签,图片下面有说明文字。而且 PaperMod 给它加了样式,比裸 ![]() 好看。”
“那 figure 还有别的参数吗?”
“有。”老周说,“常用的几个:”
src:图片路径title:图片标题caption:图片说明alt:替代文本,SEO 和无障碍访问用link:点击图片跳转的链接width:图片宽度height:图片高度class:自定义 CSS 类
“你一般写 src、alt、caption 就够了。”老周说,“比如:”
{{< figure src="/images/hugo-logo-small.png" alt="Hugo Logo" caption="Hugo 是一个静态网站生成器" >}}
小林改了一下,刷新。
“那 {{< >}} 和 {{% %}} 有啥区别?”小林问。
“好问题。”老周说,“{{< >}} 表示 shortcode 的内容不经过 Markdown 渲染。{{% %}} 表示内容经过 Markdown 渲染。你以后写 shortcode,如果里面是纯 HTML,用 {{< >}};如果是 Markdown,用 {{% %}}。”
“那 figure 用哪个?”
“figure 用的是 {{< >}},因为它的参数是 Hugo 处理的,不需要 Markdown 再渲染。”
小林记下:{{< >}} 不渲染 Markdown,{{% %}} 渲染 Markdown。
四、代码块:从“能看”到“好看”
“图片搞定了,说代码块。”老周说,“你昨天的代码块,连高亮都没有。”
“有啊,我写了 ```bash。”
“写了是写了,但 PaperMod 默认的代码高亮是什么?”
小林刷新文章,看了一眼代码块。白色背景,黑色文字,确实没有语法高亮。
“因为 PaperMod 默认用的是 Hugo 内置的 Chroma 高亮,但你需要在 hugo.toml 里配置。”老周说。
在 hugo.toml 里加:
[markup]
[markup.highlight]
noClasses = false
lineNos = true
lineNumbersInTable = true
style = "monokai"
“一个一个说。”
noClasses = false:用 CSS 类而不是内联样式。这样主题可以覆盖。lineNos = true:显示行号。lineNumbersInTable = true:行号和代码用表格分开,复制代码不会把行号复制进去。style = "monokai":高亮配色方案。常用的有monokai、dracula、github、solarized-dark。
小林保存,刷新。
代码块有了行号,有了颜色。
“但我看行号挡住了复制按钮。”小林说。
“正常,PaperMod 的复制按钮在代码块右上角,行号在左边,不冲突。”老周说,“你试试复制。”
小林点了一下复制按钮,代码复制成功,没有行号。
“好用了。”小林说。
“再教你一个技巧。”老周说,“代码块可以指定高亮行。”
```bash {hl_lines=[2,4]}
hugo version
hugo server
ls -la
hugo new site myblog
```
“这样第 2 行和第 4 行会高亮,读者一眼看到重点。”
小林试了一下,果然。
“还有一个:行号从第几行开始。”
```bash {linenostart=10}
hugo version
hugo server
```
“这样行号从 10 开始,适合展示长文件的一部分。”
“记下:”
{hl_lines=[2,4]}:高亮第 2、4 行{linenostart=10}:行号从 10 开始{linenos=false}:这一块不显示行号
五、TOC:文章的“导航”
“第三件事,TOC。”老周说,“你文章长了,读者想跳到某一节,得有个目录。”
“PaperMod 不是有 ShowToc = true 吗?”
“对,你在 hugo.toml 里配了 ShowToc = true。但文章级别也可以单独控制。”
小林打开文章 front matter,加:
ShowToc: true
TocOpen: false
“ShowToc: true 显示目录,TocOpen: false 默认折叠。”老周说,“你保存,刷新。”
文章右侧出现了目录,列出了所有二级标题。
“点了能跳吗?”
“能。”老周说,“PaperMod 的 TOC 是锚点跳转,点一下滚到对应位置。”
“那目录的标题层级怎么控制?”
“默认是二级和三级标题。你可以在 hugo.toml 里配:”
[markup]
[markup.tableOfContents]
startLevel = 2
endLevel = 4
ordered = false
“startLevel = 2 从二级标题开始,endLevel = 4 到四级标题结束,ordered = false 不用数字编号。”
“那如果我不想显示某一篇的目录?”
“在 front matter 里写 ShowToc: false,覆盖全局配置。”
小林记下:
- 全局:
hugo.toml中ShowToc = true - 单篇:front matter 中
ShowToc: false覆盖 - TOC 层级:
startLevel和endLevel
六、短代码:Hugo 的“扩展标签”
“刚才讲了 figure,那是 Hugo 内置的 shortcode。”老周说,“Hugo 还有几个内置 shortcode,你以后会用。”
ref 和 relref
“你想在文章里链接到另一篇文章,不要写死 URL,用 ref:”
[我的第一篇文章]({{< ref "posts/my-first-post.md" >}})
“ref 会生成相对于 baseURL 的链接,relref 生成相对链接。你以后重构站点,URL 变了,ref 自动更新,写死 URL 就得手动改。”
youtube
“你想嵌入 YouTube 视频:”
{{< youtube dQw4w9WgXcQ >}}
“它会生成一个 iframe。”
highlight
“你想在 shortcode 里写代码:”
{{< highlight bash >}}
hugo version
hugo server
{{< /highlight >}}
“效果和 ```bash 一样,但 shortcode 更灵活。”
details
“你想折叠一段内容:”
{{< details "点击展开" >}}
这里是隐藏的内容。
{{< /details >}}
“读者点一下才展开。你以后写‘排错挑战’的答案,可以用这个折叠。”
小林眼睛一亮:“这个好,我练习答案就可以折叠。”
“对。”老周说,“PaperMod 支持 details,你试试。”
小林在文章末尾加了:
{{< details "参考答案" >}}
这是答案内容。
{{< /details >}}
刷新,出现一个“参考答案”的折叠块,点一下展开。
“成了。”小林满意地说。
七、排错挑战:图片 404
下午,小林想把一张新图片加到文章里。他下载了一张图,放到 static/images/,然后在文章里写:

保存,刷新。
图片挂了。
他检查文件:
ls -l static/images/
输出:
-rw-r--r-- 1 devops devops 80K Sep 13 10:05 hugo-logo-small.png
-rw-r--r-- 1 devops devops 120K Sep 13 14:00 architecture-diagram.png
“文件名是 architecture-diagram.png,全小写。”小林说,“我引用写的是 Architecture-Diagram.png,大写……”
他改回来:

刷新,图片出来了。
“又是大小写。”小林叹气。
“我昨天就说了。”老周路过,“Linux 大小写敏感,Mac 不敏感。你在 Mac 上写博客,图片能显示,传到 Ubuntu 就挂。 所以文件名全小写,引用也全小写。永远别用大写。”
“那如果我的图片名本来就有大写呢?”
“改。”老周说,“mv 重命名,或者下载的时候就改成小写。别偷懒。”
排错思路总结:
- 图片 404,先检查文件是否在
static/images/下。 - 检查文件名和引用路径是否完全一致(大小写敏感)。
- 检查引用路径是否以
/开头(绝对路径)。 - 检查
hugo server是否重新构建(改了static/不需要重启,但hugo.toml改了要重启)。 - 浏览器强制刷新(
Ctrl + Shift + R),排除缓存。
“记住,”老周说,“Hugo 的静态文件,路径就是 URL。 static/images/a.png 对应 /images/a.png。static/images/A.png 对应 /images/A.png。Linux 下这俩是两个文件,Mac 下是一个。所以,全小写,保平安。”
八、练习
- 练习一:压缩一张图片到宽度 800px,放到
static/images/下。用figureshortcode 插入文章,设置alt和caption。确认页面显示图片和说明文字。 - 练习二:在
hugo.toml中配置代码高亮style = "monokai"、lineNos = true、lineNumbersInTable = true。在文章中写一个代码块,用{hl_lines=[2]}高亮第 2 行,确认行号和高亮生效。 - 练习三:在文章 front matter 中设置
ShowToc: true、TocOpen: false。确认文章右侧显示目录,点击目录项能跳转。然后用detailsshortcode 包裹一段答案,确认可折叠。
参考答案
练习一:
sudo apt install -y imagemagick
convert 原图.png -resize 800x static/images/new-image.png
文章中添加:
{{< figure src="/images/new-image.png" alt="示例图片" caption="这是示例图片的说明" >}}
练习二:
hugo.toml:
[markup]
[markup.highlight]
noClasses = false
lineNos = true
lineNumbersInTable = true
style = "monokai"
文章代码块:
```bash {hl_lines=[2]}
hugo version
hugo server
```
练习三:
front matter:
ShowToc: true
TocOpen: false
正文中加:
{{< details "答案" >}}
这是折叠的答案内容。
{{< /details >}}
九、下集预告
图片、代码块、TOC 都搞定了,博客终于“有个人样”了。老周说:“明天让 Nginx 接管,把 hugo server 换成正经的 80 端口。”小林打开 Nginx 文档,准备配置。但他不知道,try_files、root 路径、权限,每一个都可能让他折腾一下午。
下一集:《让 Nginx 接管:从 localhost:1313 到正经端口》