聊聊这个博客是怎么跑起来的

最近把博客从 Hexo 换成了 Hugo,顺便重新设计了一下构建和部署流程。趁热乎记录一下,也方便以后自己查。 先说结论:整体流程 简单来说就是这么个事儿: 我写 Markdown → 推到 GitHub → Actions 自动编译 → 服务器拉取 → Cloudflare 加速 画个图更清楚: 本地写文章 (Markdown) ↓ git push GitHub main 分支 ↓ 触发 Actions GitHub Actions 编译 ↓ 生成静态文件 gh-pages 分支 ↓ 服务器 git pull Nginx 托管静态文件 ↓ Cloudflare CDN 缓存加速 ↓ 用户访问 为啥要搞这么复杂?主要是不想每次改个错别字都要登服务器手动操作。 为啥选 Hugo 之前用的 Hexo,没啥大毛病,但有几个痒点: npm 依赖太多了。每次 CI 光装依赖就得好一会儿,偶尔还会出点版本冲突的幺蛾子 本地预览慢。文章多了之后,热更新明显变卡 想尝尝鲜。Hugo 用 Go 写的,据说快得离谱 实际用下来,确实快。本地预览基本秒开,GitHub Actions 那边构建也就几秒钟的事。 ...

2026-02-04 · 2 分钟 · 260 字 · kkkk24

博客加载太慢?我是这样排查和解决的

从 2.28s 优化到 50ms,原来问题出在 Cloudflare 没缓存 HTML

2026-02-06 · 1 分钟 · 208 字 · kkkk24

给博客加个 Live2D 看板娘

之前逛别人博客的时候,经常看到左下角有个会动的小人,眼睛还会跟着鼠标跑,感觉挺有意思的。研究了一下发现这玩意叫 Live2D,于是决定给自己的 Hexo 博客也整一个。 折腾了一晚上,踩了不少坑,这里记录一下实现过程。 效果 加完之后,博客左下角会多一个二次元小人,能: 眼睛跟着鼠标动(有点魔性) 随机显示一言 换装、换模型 截图保存 还能玩个打砖块小游戏 说实话没啥实际用处,但看着就是舒服 😂 最简单的方式 如果只是想快速体验,一行代码就够了。 在主题的 layout/layout.ejs 里加上: <script src="https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0/dist/autoload.js"></script> 放在 </body> 之前就行。刷新页面,看板娘应该就出来了。 不过这种方式没法自定义,想要更多控制就得用下面的方法。 完整配置版 第一步:创建组件 在主题目录的 layout/_partial/ 下面新建一个 live2d.ejs: <%# 看板娘组件 %> <% if (theme.live2d && theme.live2d.enable) { %> <% // 读取配置,给个默认值兜底 const cdnPath = (theme.live2d.cdn && theme.live2d.cdn.url) ? theme.live2d.cdn.url : 'https://fastly.jsdelivr.net/npm/live2d-widgets@1.0.0/dist/'; const modelId = (theme.live2d.model && theme.live2d.model.id !== undefined) ? theme.live2d.model.id : 0; const modelCdnPath = (theme.live2d.model && theme.live2d.model.cdnPath) ? theme.live2d.model.cdnPath : 'https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/'; const toolItems = (theme.live2d.tools && theme.live2d.tools.items) ? theme.live2d.tools.items : ['hitokoto', 'asteroids', 'switch-model', 'switch-texture', 'photo', 'info', 'quit']; const drag = theme.live2d.drag !== undefined ? theme.live2d.drag : false; const logLevel = theme.live2d.logLevel || 'warn'; %> <script> const live2d_path = '<%= cdnPath %>'; // 动态加载资源 function loadExternalResource(url, type) { return new Promise((resolve, reject) => { let tag; if (type === 'css') { tag = document.createElement('link'); tag.rel = 'stylesheet'; tag.href = url; } else if (type === 'js') { tag = document.createElement('script'); tag.type = 'module'; tag.src = url; } if (tag) { tag.onload = () => resolve(url); tag.onerror = () => reject(url); document.head.appendChild(tag); } }); } (async () => { // 手机上就别加载了,太占地方 if (screen.width < 768) return; // 处理图片跨域(这个坑踩了好久) const OriginalImage = window.Image; window.Image = function(...args) { const img = new OriginalImage(...args); img.crossOrigin = "anonymous"; return img; }; window.Image.prototype = OriginalImage.prototype; // 加载资源 await Promise.all([ loadExternalResource(live2d_path + 'waifu.css', 'css'), loadExternalResource(live2d_path + 'waifu-tips.js', 'js') ]); // 初始化 initWidget({ waifuPath: live2d_path + 'waifu-tips.json', cdnPath: '<%= modelCdnPath %>', cubism2Path: live2d_path + 'live2d.min.js', cubism5Path: 'https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js', modelId: <%= modelId %>, tools: <%- JSON.stringify(toolItems) %>, drag: <%= drag %>, logLevel: '<%= logLevel %>' }); })(); </script> <% } %> 第二步:引入组件 在 layout/layout.ejs 里引入(放在 </body> 前面): ...

2026-02-03 · 2 分钟 · 393 字 · kkkk24