[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site":3,"post-zhong-wen-zi-ti-zi-ji-hua-zhi":59,"related-zhong-wen-zi-ti-zi-ji-hua-zhi":77},{"name":4,"url":5,"description":6,"bio":7,"avatar":8,"skills":9,"tools":16,"social":34,"hero":47,"heatmap":52,"icp":55,"police":56,"policeIcon":57,"shortName":58},"时过境迁技术笔记","https:\u002F\u002Fhsgjq.top","一个记录前端与 Node 实践的个人博客，内容围绕 Vue、Nuxt、NestJS 与工程化展开，附源码级的原理笔记。","一名热爱技术的全栈开发者，专注 TypeScript 与 Web 工程化，习惯把踩过的坑和思考记录在这里。","\u002Fuploads\u002Fc485c101-179e-48db-96d1-238693784c38.jpg",[10,11,12,13,14,15],"TypeScript","Vue 3","Nuxt","NestJS","Node.js","工程化",[17,23,28],{"items":18,"title":22},[19,20,21],"Cursor","Git","Chrome DevTools","开发工具",{"items":24,"title":27},[25,26],"Vite","npm","构建工具",{"items":29,"title":33},[30,31,32],"Docker","CI\u002FCD(GitHub Actions)","Vercel","部署运维",[35,39,43],{"href":36,"icon":37,"label":38},"https:\u002F\u002Fgithub.com\u002Fhsgjq","github","GitHub",{"href":40,"icon":41,"label":42},"2971861643@qq.com","mail","邮箱",{"href":44,"icon":45,"label":46},"\u002Frss.xml","rss","RSS",{"lines":48,"subtitle":51},[49,50],"人生若只如初见，","何事秋风悲画扇。","《木兰花·拟古决绝词柬友》",{"enabled":53,"platform":37,"username":54},true,"hsgjq","桂ICP备2024046985号","桂公网安备45011002000103号","\u002Fuploads\u002F72c67ef3-bc86-4b64-b0df-0aba71065050.png","时过境迁",{"slug":60,"title":61,"excerpt":62,"date":63,"updatedAt":64,"tags":65,"content":67,"series":66,"seriesIndex":68,"seriesNav":69,"prev":71,"next":74},"zhong-wen-zi-ti-zi-ji-hua-zhi","中文字体子集化之后首屏少了 900KB","整包中文字体动辄几 MB。按实际用到的字符子集化、font-display 设 strategy，代价是新字会掉字体。","2026-06-30","2026-10-19",[66],"性能","引入一款想用的中文字体，woff2 文件 10.6MB。首屏字体请求把渲染堵了将近两秒。\n\n中文字库大是因为字符多，常用汉字三千上下，全量收录是两万多。而一个博客实际用到的字符，把历史文章里出现过的全收集起来，也就一千多个。\n\n子集化就是只保留用到的那些：\n\n```bash\npyftsubset font.ttf \\\n  --text-file=used-chars.txt \\\n  --flavor=woff2 \\\n  --output-file=font-subset.woff2\n```\n\n10.6MB 变成 380KB。加上 font-display: swap，首屏不再等字体：\n\n```css\n@font-face {\n  font-family: 'Brand';\n  src: url('\u002Ffonts\u002Fbrand-subset.woff2') format('woff2');\n  font-display: swap;\n}\n```\n\n代价有三个，都得记着。\n\n新字会掉字体。子集里没有的字符（用户昵称里的生僻字、以后写的文章里的新字）会退回默认字体，一句话里混两种字形，很难看。我的处理是保留一份系统字体栈，并且每次发新文章后重新生成一次字符表——这一步必须自动化，手工做一定会忘。\n\nfont-display 的三种行为要想清楚。swap 是先用后备字体显示、加载完替换，会有一次跳字；block 是短暂不可见；optional 由浏览器决定，可能整个页面生命周期都不换。我把标题设 optional、正文设 swap，因为标题跳一下影响不大，正文不能空白。\n\n数字和标点也要在子集里。第一版我漏了破折号和书名号，页面上出现方框。\n\n另一处省下来的：图片。首屏那张封面从 480KB 的 jpg 换成 webp 是 60KB，比字体那一千多 KB 更容易做到。优化顺序应该是\"体积最大的先动\"，而我是先啃了字体。",6,[70],{"slug":60,"title":61,"seriesIndex":68},{"slug":72,"title":73},"shen-se-mo-shi-yan-se-bian-liang","深色模式：颜色变量该放哪一层",{"slug":75,"title":76},"tu-pian-dao-di-cun-na-cong-dui","图片到底存哪：从对象存储的念头回到自建目录",[78,88,94,99,106],{"slug":79,"title":80,"excerpt":81,"date":82,"tags":83},"gang-deng-lu-jiu-guo-qi-yi-ci","一个 401，查出四个前端机制：缓存回放、隐藏页的 rAF、百分比圆角、缓动过冲","响应头里的 token 被 304 原样回放，把刚签发的新 token 顶掉，表现成刚登录就过期。顺带记三个前端细节：隐藏标签页里 rAF 不执行、扁盒子的 50% 圆角是胶囊、缓动曲线的过冲只有 0.0008。","2026-10-13",[84,85,86,87],"CSS","JavaScript","Vue","浏览器",{"slug":89,"title":90,"excerpt":91,"date":92,"tags":93},"dian-xiang-mu-ye-mian-jiu-si-yi","点「项目」页面就死：一次客户端无限重定向","为了保留旧地址大小写加的一条跳转规则，被注册成了客户端路由；路由匹配不区分大小写，于是它自己跳自己。","2026-10-02",[12,87],{"slug":95,"title":96,"excerpt":97,"date":92,"tags":98},"cong-da-bao-shang-chuan-dao-push-ji","从打包上传到 push 即上线：把 CD 接通","私有仓库的镜像包默认也是私有的，健康检查的探测地址在宿主机有 nginx 时会打错地方。两处都是配好之后才验出来的。",[30,15],{"slug":100,"title":101,"excerpt":102,"date":103,"tags":104},"shang-xian-na-tian-yu-ming-zheng-shu","上线那天：域名、证书，和一条我打错的 IP","把 69 打成 96，于是照着\"安全组没放行\"这条错线索查了一整晚。分层排查的前提是先确认敲的是哪扇门。","2026-10-01",[105],"随笔",{"slug":107,"title":108,"excerpt":109,"date":110,"tags":111},"jian-li-de-xie-gang-hui-bian-cheng","键里的斜杠会变成冒号：清缓存清了个空","按源码里的分组名去匹配存储键，结果一条都没删掉，接口还回 200。返回一个可被证伪的数字，这类问题当场就能发现。","2026-09-30",[12,15]]