1. 项目概述:一个独立技术博客的底层逻辑与生存实态
“Leepy’s Blogs”——这个名字乍看像个人ID加通用名词的简单组合,但放在当下内容平台高度同质化、算法推荐挤压长尾价值的环境中,它反而构成了一种清醒的宣言:这不是一个追逐流量的账号,而是一套自洽运转的
个人知识操作系统
。我从2016年开始搭建自己的第一版静态博客,到如今维护三个不同技术方向的子站(前端工程化、嵌入式Linux调试、硬件原型设计),深刻体会到,“Leepy’s Blogs”这类命名背后,本质是
对信息主权、表达节奏与知识沉淀路径的主动选择
。它不依赖任何平台的推荐机制,不为点击率优化标题党,也不用数据看板绑架写作动机;它的核心指标只有一个:当我在三个月后回看某篇关于GCC链接脚本的笔记时,能否在30秒内精准定位到
SECTIONS
段中
.init_array
的加载顺序问题。关键词如
静态站点生成器、Git驱动发布、语义化版本归档、离线可读性、跨设备同步一致性
,不是技术选型的装饰词,而是每天真实约束我写作方式的物理法则。适合谁?适合那些已经经历过“在公众号发10篇干货却只有3个朋友点开”的挫败感,开始认真思考“我写的东西,五年后还能不能被我自己快速复用”的人;也适合刚入门的开发者,想绕过平台黑箱,直接触摸内容从Markdown文本变成可访问网页的完整链路。这不是教你如何涨粉,而是带你亲手拧紧每一颗螺丝,把博客变成你数字工作台里最可靠的一块钢板。
2. 整体架构设计:为什么放弃CMS与托管平台?
2.1 核心矛盾:效率幻觉 vs. 长期可控性
很多人第一次建博客,直觉是选WordPress或Hexo主题市场里的“一键部署”方案。我试过三次:2017年用WordPress托管版,半年后因插件冲突导致全站白屏,备份恢复耗掉整个周末;2019年用Ghost云服务,某次自动升级后RSS订阅地址失效,下游5个聚合器全部断连,排查三天才发现是URL重写规则被覆盖;2021年用Notion导出静态页,结果发现代码块高亮样式在移动端完全错乱,且无法添加自定义CSS。这些不是偶然故障,而是
架构基因决定的必然代价
。CMS(内容管理系统)的本质是用数据库抽象层换取编辑便利性,但抽象层越厚,你离HTML源码就越远。当你需要精确控制某个
<pre>
标签的
tab-size
属性,或让MathJax公式在离线状态下仍能渲染,CMS的后台编辑器只会给你两个选项:用预设按钮(可能不支持),或切到“原始HTML模式”(此时你已失去所有可视化校验)。而“Leepy’s Blogs”选择的路径截然相反:
把内容当作不可变的数据源,把呈现逻辑完全外置为可版本控制的代码
。这看似增加了初始学习成本,但换来的是确定性——我知道
content/posts/2024-03-15-gcc-linker-scripts.md
这个文件,无论在哪台机器上用哪个工具编译,生成的HTML结构都严格一致。这种确定性,在你需要做A/B测试排版效果、对比不同年份文章的SEO结构变化、或向同事分享某篇调试笔记的原始环境时,会成为唯一可靠的锚点。
2.2 技术栈选型:Hugo为何成为最终答案?
在Jekyll、Hugo、Zola、Astro之间,我花了两个月实测对比。关键决策点不是“哪个更快”,而是“哪个最不容易让我在三年后看不懂自己当年写的配置”。Jekyll的Liquid模板语法简洁,但其插件生态严重依赖Ruby版本,去年一次系统升级后,本地
bundle exec jekyll serve
直接报
webrick
缺失,折腾半天才想起要手动安装旧版gem;Zola的纯Rust实现确实快,但其文档中大量使用“
section
对象隐式继承
_index.md
元数据”这类概念,新手容易误以为所有页面都有相同字段,实际调试时发现子目录下的
_index.md
若未显式声明
weight
,排序逻辑会静默失效。Hugo胜出的核心在于
错误反馈的诚实性
。举个具体例子:当我把一篇新文章的
date
字段写成
2024/03/15
(斜杠分隔)而非
2024-03-15
(ISO标准),Hugo在
hugo server
启动时会明确报错:“
ERROR: failed to render pages: render of "page" failed: execute of template failed: template: _default/single.html:12:3: executing "_default/single.html" at <.Date.Format>: invalid value; expected time.Time
”。这个错误信息直接指向模板第12行、指出期望类型是
time.Time
,而不是笼统的“解析失败”。这种设计哲学意味着,Hugo不会替你猜测意图,它强迫你面对数据格式的物理现实。配合Git的
pre-commit
钩子,我写了个简单脚本:每次
git add
前自动检查所有
.md
文件的
date
字段是否符合
^\d{4}-\d{2}-\d{2}$
正则,不符合则拒绝提交。这套组合拳下来,内容数据的洁净度从“靠人肉校验”提升到“由机器强制保障”,这才是“Leepy’s Blogs”能稳定运行六年的底层基石。
2.3 发布流程重构:从“上传”到“原子化部署”
传统理解的“发布博客”,常等同于“把文件传到服务器”。但在“Leepy’s Blogs”体系里,
发布是一个不可分割的原子操作
。我的CI/CD流水线(基于GitHub Actions)只做三件事:1)拉取最新
main
分支;2)执行
hugo --minify --cleanDestinationDir
生成
public/
目录;3)将
public/
目录下所有文件,以
rsync -avz --delete
方式同步至VPS的
/var/www/leepyblogs/
。这里的关键细节是
--delete
参数——它确保远程服务器上任何不在当前
public/
目录中的文件(比如上一版遗留的
/old-2022-archive/
)会被彻底清除。这听起来激进,但恰恰消除了最隐蔽的故障源:缓存污染。曾有一次,我修改了全局CSS,但忘了清理CDN缓存,用户看到的页面是新版HTML+旧版CSS,导致导航栏错位。现在,每次部署都是“全量覆盖”,不存在“部分更新”的中间态。更进一步,我给VPS的Nginx配置了
try_files $uri $uri/ /index.html;
,这意味着即使用户直接访问
/posts/2024-03-15-gcc-linker-scripts/
这个路径,Nginx也会先尝试找对应目录,找不到就回退到
/index.html
,由前端路由接管。这种设计让博客天然支持SPA(单页应用)式的平滑跳转,同时保留了静态站点的极致可靠性。当Cloudflare出现区域性故障时,我的VPS依然能独立响应所有请求,因为所有资源都在本地磁盘上,没有外部依赖。
3. 核心功能实现:从零构建可维护的知识库
3.1 内容组织规范:用文件系统代替数据库分类
在WordPress里,给文章打标签、分栏目是后台操作;在“Leepy’s Blogs”里,这是通过
严格的目录结构和文件命名约定
完成的。我的
content/
目录长这样:
content/
├── _index.md # 站点首页的元数据
├── posts/ # 所有技术文章
│ ├── 2024-03-15-gcc-linker-scripts.md
│ ├── 2024-02-28-esp32-jtag-debugging.md
│ └── 2024-01-10-react-vite-ssr.md
├── notes/ # 碎片化笔记(不公开)
│ └── embedded-linux-kernel-configs.md
└── projects/ # 项目记录(带代码仓库链接)
└── riscv-soc-verilog.md
关键点在于:
日期前缀不是为了排序,而是为了建立时间戳的不可篡改性
。
2024-03-15
这个字符串硬编码在文件名里,意味着这篇文章的创建时间被Git历史永久锁定。如果某天我发现这篇关于GCC链接脚本的文章需要大幅重写,我会新建一个
2024-03-15-gcc-linker-scripts-v2.md
,而不是修改原文件。这样做的好处是,旧版内容依然可通过
/posts/2024-03-15-gcc-linker-scripts/
访问,新版则走新路径,形成天然的版本对照。Hugo的
Section
功能会自动将
posts/
目录下的所有文章归类为
section = "posts"
,我只需在列表模板中写
{{ range where .Site.RegularPages "Section" "posts" }}
即可遍历。这种设计让分类逻辑完全脱离运行时计算,全部在文件系统层面完成,查询速度是O(1)级别的。更妙的是,当我想统计2024年Q1写了多少篇嵌入式相关文章,只需一条Shell命令:
find content/posts -name "*esp32*" -o -name "*jtag*" | grep "2024-0[1-3]" | wc -l
。数据即文件,文件即数据,没有抽象层损耗。
3.2 模板系统深度定制:超越主题市场的控制力
Hugo的主题市场(Themes)里有上千个免费模板,但它们解决的是“如何看起来像一个博客”,而非“如何精准表达我的知识结构”。以“Leepy’s Blogs”首页为例,我需要一个
三栏动态布局
:左侧显示最近3篇技术文章,中间是精选的“硬核调试技巧”合集(手动维护的
data/debug-tips.yaml
),右侧是实时更新的“正在阅读”书单(从Goodreads API抓取)。主流主题通常只提供
{{ .Site.Pages }}
的扁平化列表,无法满足这种混合数据源的需求。我的解法是:在
layouts/index.html
中,用Hugo的
where
函数组合过滤:
<!-- 左侧:最近3篇posts -->
{{ $recentPosts := where .Site.RegularPages "Section" "posts" | first 3 }}
{{ range $recentPosts }}
<article class="post-card">
<h3><a href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
<time>{{ .Date.Format "Jan 02, 2006" }}</time>
</article>
{{ end }}
<!-- 中间:硬核调试技巧(来自data文件) -->
{{ $debugTips := .Site.Data.debug-tips }}
{{ range $debugTips.tips }}
<div class="tip-item">
<h4>{{ .title }}</h4>
<p>{{ .summary }}</p>
</div>
{{ end }}
<!-- 右侧:正在阅读(API调用结果缓存) -->
{{ $readingList := .Site.Data.reading.current }}
{{ range $readingList }}
<div class="book-item">
<img src="{{ .cover }}" alt="{{ .title }}">
<h4>{{ .title }}</h4>
</div>
{{ end }}
这里的关键洞察是:
Hugo的Data模板(
data/
目录)和Page变量(
.Site.Pages
)可以无缝混用
。我不需要写JavaScript去异步加载书单,而是把API调用结果(每日凌晨cron job执行)存为
data/reading/current.json
,Hugo在构建时直接读取JSON并注入模板。这种“静态化动态数据”的思路,让首页既保持了毫秒级加载速度,又具备了动态内容的灵活性。更重要的是,所有这些逻辑都写在
layouts/
目录下,受Git版本控制。当我2025年想把书单换成豆瓣API,只需修改
data/
目录的抓取脚本和
layouts/index.html
中对应的
range
循环,无需触碰任何主题文件,避免了“升级主题即丢失定制”的经典陷阱。
3.3 代码块与技术图示:让博客成为可执行的文档
技术博客最大的痛点,不是写不出来,而是写出来后别人
无法复现
。“Leepy’s Blogs”为此建立了三层保障:首先是代码块的
语言标识与行号绑定
。Hugo默认的
highlight
短代码支持
linenos=table
,但我在
config.toml
中强制开启:
[markup]
[markup.highlight]
codeFences = true
guessSyntax = false
lineNos = true
lineNumbersInTable = true
noClasses = false
这样,当我在Markdown中写:
# 编译内核模块
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载模块
sudo insmod hello.ko
# 查看日志
dmesg | tail -5
Hugo会生成带行号的HTML表格,且第13、15行(对应
hl_lines=[3,5]
)会高亮。行号从10开始(
linenostart=10
),确保与真实终端输出一致。第二层是
交互式图示
。对于复杂的内存布局图,我放弃用Visio画静态PNG,改用Mermaid语法(注意:此处Mermaid是作为Hugo的Markdown扩展,非独立图表工具):
graph LR
A[用户空间] -->|mmap| B[内核空间]
B --> C[物理内存页]
C --> D[DDR控制器]
D --> E[DRAM芯片]
Hugo通过
markdownify
函数将Mermaid代码转为HTML
<div class="mermaid">
,再由前端JS库渲染。关键是,这段Mermaid代码本身是纯文本,可被Git diff追踪,修改布局只需改几行字符。第三层是
可点击的命令行
。在介绍
strace
用法时,我不会只写“运行
strace -e trace=openat,read write
”,而是用Hugo的
%
分隔符创建可复制代码块:
strace -e trace=openat,read,write -o /tmp/trace.log ./myapp
用户点击右上角复制按钮,粘贴到终端就能直接执行。这背后是Hugo的
%
短代码解析器在起作用,它把
{type="copy"}
识别为特殊指令,注入对应的JavaScript事件监听器。这三层设计叠加,让“Leepy’s Blogs”里的每篇技术文章,本质上都是一份
可验证、可执行、可审计的操作手册
,而非仅供阅读的说明文档。
4. 实操部署与运维:让博客像家电一样省心
4.1 VPS环境精简配置:只留最必要的服务
我选用的是1核2GB内存的廉价VPS(年付约$20),系统为Ubuntu 22.04 LTS。部署前的第一步,是
卸载所有非必要服务
。默认安装的
snapd
、
whoopsie
(Ubuntu错误报告)、
apport
(崩溃报告)全部禁用:
sudo systemctl stop snapd whoopsie apport
sudo systemctl disable snapd whoopsie apport
sudo apt purge snapd whoopsie apport -y
接着,只安装三个核心组件:Nginx(Web服务器)、Fail2ban(防暴力破解)、UFW(防火墙)。Nginx配置极度精简,
/etc/nginx/sites-available/leepyblogs
仅包含:
server {
listen 80;
server_name leepyblogs.com;
root /var/www/leepyblogs;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
这里没有PHP-FPM、没有MySQL、没有Redis——因为“Leepy’s Blogs”根本不需要它们。所有动态需求(如搜索)都通过客户端JavaScript实现,服务端只负责交付静态文件。这种极简主义带来的直接好处是:
系统补丁更新频率从每周降为每月
。Ubuntu的安全更新主要针对
systemd
、
openssl
、
nginx
等核心包,而我的VPS上只有这三类包需要关注。我设置了一个简单的cron job,每月1号凌晨2点自动执行:
# /etc/cron.d/security-update
0 2 1 * * root apt update && apt upgrade -y --only-upgrade && systemctl restart nginx
升级后,Nginx会自动重载配置(
systemctl restart nginx
触发
reload
而非
restart
),网站零中断。过去六年,因系统更新导致博客不可用的时长累计为0分钟。这种稳定性,不是靠昂贵的云服务SLA承诺,而是靠主动剥离复杂性换来的物理确定性。
4.2 备份策略:三重保险覆盖所有风险点
“Leepy’s Blogs”的备份不是“以防万一”,而是 日常操作的一部分 。我采用“3-2-1”原则的变体:3份副本、2种介质、1份异地。具体执行如下:
-
本地副本(Git仓库) :所有源码(
content/、layouts/、static/、config.toml)均在本地MacBook的Git仓库中。每次写作后,执行git add . && git commit -m "add: esp32 jtag debugging guide",然后git push origin main。Git的分布式特性保证,即使VPS硬盘损坏,只要本地Git仓库完好,git clone即可100%还原全部源码。 -
构建产物副本(VPS本地) :在VPS的
/var/www/leepyblogs/目录旁,我创建了/var/www/leepyblogs-backup/,并通过rsync定时同步:
# /etc/cron.d/backup-build
0 3 * * * root rsync -avz /var/www/leepyblogs/ /var/www/leepyblogs-backup/$(date +\%Y-\%m-\%d)
每天凌晨3点,将当前
/var/www/leepyblogs/
完整拷贝到带日期的子目录中。这样,如果某次部署出错(如CSS文件路径写错导致全站样式丢失),我可以在30秒内执行
rsync -avz /var/www/leepyblogs-backup/2024-03-14/ /var/www/leepyblogs/
回滚到昨日状态。
-
异地副本(GitHub私有仓库)
:这是最关键的保险。我创建了一个私有GitHub仓库
leepyblogs-deploy,其中只存放public/目录的内容(即Hugo生成的静态文件)。CI/CD流水线在每次成功部署后,自动执行:
- name: Push to GitHub Backup
run: |
cd public
git init
git remote add origin https://token:${{ secrets.GITHUB_TOKEN }}@github.com/leepy/leepyblogs-deploy.git
git checkout -b main
git add .
git commit -m "deploy: $(date)"
git push -u origin main --force
注意
--force
参数:它确保GitHub仓库永远只有一份
main
分支,内容与VPS上的
public/
完全一致。这个仓库不包含任何源码,只存最终产物,因此即使GitHub被黑,攻击者也无法获取我的Hugo配置或未发布的草稿。三重备份覆盖了所有单点故障:本地电脑丢(有VPS备份)、VPS硬盘坏(有GitHub备份)、GitHub宕机(有本地Git源码)。备份不是动作,而是状态——我的博客永远处于“随时可重建”的状态。
4.3 监控与告警:用最原始的方式守住底线
高级监控平台(如Prometheus+Grafana)对个人博客是过度设计。我采用“够用就好”的极简方案:
HTTP状态码轮询 + 邮件告警
。在本地MacBook上,我写了一个Python脚本
monitor.py
:
import requests
import smtplib
from email.mime.text import MIMEText
from datetime import datetime
def check_site():
try:
r = requests.get("http://leepyblogs.com", timeout=10)
if r.status_code != 200:
send_alert(f"HTTP {r.status_code} for leepyblogs.com")
except Exception as e:
send_alert(f"Request failed: {str(e)}")
def send_alert(message):
msg = MIMEText(f"Alert at {datetime.now()}: {message}")
msg['Subject'] = 'LeepyBlogs Down'
msg['From'] = 'monitor@leepyblogs.com'
msg['To'] = 'leepy@protonmail.com'
with smtplib.SMTP('localhost', 1025) as server:
server.send_message(msg)
if __name__ == "__main__":
check_site()
这个脚本通过Mac自带的
launchd
每5分钟执行一次。关键点在于SMTP服务器:我用
msmtp
配置了一个本地邮件代理,将所有发往
localhost:1025
的邮件,通过ProtonMail的SMTP网关(
smtp://user:pass@smtp.protonmail.ch:587
)加密发送。这样,当博客返回502(Bad Gateway)或超时,我的ProtonMail邮箱会在2分钟内收到告警。过去两年,这个脚本共触发过7次告警,其中5次是VPS供应商的网络波动(持续<10分钟),2次是我自己误操作
systemctl stop nginx
。每次告警,我打开手机SSH App,输入
sudo systemctl start nginx
,问题立即解决。没有Dashboard,没有复杂仪表盘,只有最原始的“请求-响应”闭环。这种监控哲学,与博客本身的极简主义一脉相承:
用最不可靠的组件(邮件),构建最可靠的告警通道
,因为即使整个VPS宕机,只要我的手机有网络,就能收到通知并修复。
5. 常见问题与实战避坑指南:血泪换来的经验清单
5.1 Hugo构建失败:90%的问题出在路径与大小写
Hugo对文件路径和大小写极其敏感,这是新手踩坑最多的雷区。典型场景:在macOS上创建文件
content/posts/My-First-Post.md
,本地
hugo server
能正常运行,但推送到Linux VPS后,
hugo
命令报错
Error: unable to locate template for shortcode "figure"
。原因?macOS文件系统默认不区分大小写(
My-First-Post.md
和
my-first-post.md
被视为同一文件),而Linux ext4文件系统严格区分。Hugo的模板查找路径是
layouts/shortcodes/figure.html
,但如果你在Markdown中误写成
{{</ figure >}}
(小写f),macOS会自动匹配到
Figure.html
,Linux则完全找不到。解决方案是:
在开发机上启用大小写敏感的APFS卷
。在macOS终端执行:
# 创建新卷(需重启)
sudo diskutil apfs addVolume disk1 "APFS" "LeepyBlogs-CaseSensitive" -role S
# 将项目移到新卷下
mv ~/Documents/leepyblogs /Volumes/LeepyBlogs-CaseSensitive/
这样,本地开发环境就与生产环境(Linux VPS)完全一致,所有路径错误在提交前就被捕获。另一个高频问题是
config.toml
中的
baseURL
配置。很多教程说填
https://leepyblogs.com
,但如果你的VPS用Nginx反向代理到本地
http://localhost:1313
,Hugo生成的HTML中所有
<link>
和
<script>
的src都会带上
https://
前缀,导致混合内容警告。正确做法是:
baseURL = "/"
,让所有资源路径变为相对路径(
/css/main.css
),由Nginx的
root
指令决定实际位置。这个细节,我花了三天调试Chrome的Network面板才定位到。
5.2 图片管理混乱:用Git LFS解决二进制文件膨胀
技术博客离不开截图和示意图,但直接把PNG/JPG塞进Git仓库会导致仓库体积爆炸。我曾有一个
content/posts/2023-12-01-oscilloscope-guide/
目录,里面放了20张高清示波器截图,单张2MB,
git push
一次耗时8分钟。后来改用Git LFS(Large File Storage):
# 安装Git LFS
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
sudo apt-get install git-lfs
git lfs install
# 跟踪图片文件
git lfs track "*.png"
git lfs track "*.jpg"
git lfs track "*.svg"
# 提交LFS配置
git add .gitattributes
git commit -m "track images with LFS"
此后,所有图片文件在Git中只存指针(pointer),实际二进制数据存储在GitHub的LFS服务器上。
git clone
时默认只下载指针,需
git lfs pull
才下载原图。这对CI/CD流水线很友好:GitHub Actions的
actions/checkout@v3
默认不拉取LFS文件,我只需在workflow中加一步:
- name: Checkout with LFS
uses: actions/checkout@v3
with:
lfs: true
这样,Hugo构建时能拿到真实图片,而我的本地Git仓库体积从1.2GB降到45MB。LFS不是银弹,但它完美解决了“既要保留图片版本历史,又不能拖垮Git性能”的矛盾。提醒一句:LFS有月度带宽限制(GitHub免费版1GB),我的博客月均图片流量约80MB,完全在安全范围内。
5.3 SEO与可访问性:不靠黑帽,靠结构化数据
很多人问“Leepy’s Blogs”怎么在Google搜“gcc linker script”排第一?答案不是关键词堆砌,而是
用Schema.org结构化数据告诉搜索引擎“这是什么”
。我在
layouts/_default/baseof.html
的
<head>
中加入:
{{ if .IsPage }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "{{ .Title }}",
"description": "{{ .Description }}",
"datePublished": "{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}",
"dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}",
"author": {
"@type": "Person",
"name": "Leepy"
},
"publisher": {
"@type": "Organization",
"name": "Leepy's Blogs",
"logo": {
"@type": "ImageObject",
"url": "https://leepyblogs.com/logo.png"
}
}
}
</script>
{{ end }}
这段JSON-LD代码,让Google知道这是一篇技术文章(
TechArticle
),作者是谁,发布时间是什么。配合Hugo自动生成的
sitemap.xml
(
/sitemap.xml
),Googlebot爬虫能精准理解页面语义。实测效果:在Google Search Console中,“gcc linker script”相关查询的点击率(CTR)从2.1%提升到7.8%,因为搜索结果中会显示“Leepy’s Blogs · 2024年3月15日 · 技术文章”这样的富摘要。更关键的是,这种结构化数据对屏幕阅读器同样有效,视障用户能通过辅助工具准确获知文章类型和作者,这才是真正的可访问性(Accessibility),而非仅仅满足WCAG形式要求。
提示:不要在
<meta name="keywords">中堆砌关键词,Google早已不使用该标签。真正的SEO始于清晰的URL结构(/posts/2024-03-15-gcc-linker-scripts/比/p?id=123好一万倍)和语义化的HTML标签(用<article>包裹正文,用<h2>到<h4>构建逻辑层级)。
5.4 离线可用性:PWA让博客成为手机上的“本地App”
“Leepy’s Blogs”最被低估的特性,是它能在地铁、飞机等无网络环境下完整使用。这得益于 渐进式Web应用(PWA) 的实现。我用Workbox(Google的PWA构建库)生成Service Worker:
# 在Hugo项目根目录
npx create-workbox-app@latest
# 选择“Generate a service worker”
# 配置缓存策略:HTML缓存7天,CSS/JS缓存30天,图片缓存1年
生成的
sw.js
被放入
static/
目录,Hugo在构建时自动复制到
public/
。然后在
layouts/partials/head.html
中注册:
{{ if not .Site.IsServer }}
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW registered', reg.scope))
.catch(err => console.log('SW registration failed', err));
});
}
</script>
{{ end }}
关键点在于
{{ if not .Site.IsServer }}
:本地开发时(
hugo server
)不注册SW,避免开发调试干扰。上线后,Service Worker会拦截所有网络请求,优先从Cache Storage返回已缓存资源。用户首次访问时,会缓存首页、CSS、JS;后续访问任意文章,SW会自动缓存该页面的HTML和关联资源。实测:在iPhone上访问
/posts/2024-03-15-gcc-linker-scripts/
后,关闭Wi-Fi和蜂窝数据,刷新页面,内容依然完整显示,包括代码高亮和Mermaid图表(因为它们是内联JS,已被缓存)。这不再是“网页”,而是真正意义上的“离线App”。当你的读者在通勤路上想查一个调试命令,而不需要等待加载,这就是PWA带来的体验升维。
6. 个人实践体会:博客是思维的实体化过程
写到这里,我合上笔记本,泡了杯茶。回看这六年来维护“Leepy’s Blogs”的轨迹,最深的体会不是技术多酷炫,而是它如何重塑了我的思考习惯。以前遇到一个嵌入式调试问题,我会在Stack Overflow上搜答案,复制粘贴后解决问题,然后遗忘。现在,我的第一反应是:“这个问题值得写成一篇博客吗?它的核心难点是什么?如何让三个月后的自己一眼看懂?”这个提问过程,本身就是一次强制性的知识蒸馏。当我把
dmesg
日志分析步骤拆解为“1. 过滤模块加载消息 2. 定位panic发生点 3. 关联call trace中的函数名”,我其实是在训练自己的问题分解能力;当我为一张内存映射图反复调整Mermaid语法,直到节点间距刚好合适,我是在培养对细节的绝对耐心。博客不是知识的终点,而是思维的起点。它逼我直面自己理解的模糊地带——比如,我曾自信地写“GCC链接脚本中的
.
符号代表当前位置计数器”,直到有读者留言指出:“在
SECTIONS
块外,
.
是未定义的”,我才去翻GNU ld手册第3.5.1节,确认了这个边界条件。这种被证伪的时刻,比写出十篇“正确”文章更有价值。所以,如果你正犹豫要不要开始自己的“Leepy’s Blogs”,请记住:它不承诺流量,不保证变现,它只提供一个最朴素的契约——
用公开的书写,倒逼自己成为更严谨、更诚实、更乐于分享的思考者
。而这份契约,是任何平台都无法授予,也无法剥夺的。
1万+

被折叠的 条评论
为什么被折叠?



