[{"data":1,"prerenderedAt":337},["ShallowReactive",2],{"post-life/blog-trade-off":3},{"id":4,"title":5,"body":6,"date":327,"description":328,"extension":259,"meta":329,"navigation":330,"path":331,"seo":332,"stem":333,"tags":334,"__hash__":336},"content/life/一个从零实现的个人博客.md","一个从零实现的个人博客",{"type":7,"value":8,"toc":307},"minimark",[9,12,16,28,39,42,45,48,71,75,78,81,94,98,101,104,107,110,116,123,127,132,137,148,154,159,163,166,194,198,208,215,218,221,224,244,247,251,254,284,287,295,303],[10,11,5],"h1",{"id":5},[13,14,15],"h2",{"id":15},"起因",[17,18,19,20,24,25,27],"p",{},"我也有经常写笔记的习惯，经常使用",[21,22,23],"code",{},"Obsidian","，但写法、内容几乎都是面向我自己。我也有想过把内容分享出来，但在之前想完整复刻",[21,26,23],{},"的关系图谱很困难，一拖再拖就没有实现。",[17,29,30,31,38],{},"年后，在投递的这段时间里，我难免焦虑，不写代码快有两个月了，对常用的东西也会手生，所以我尝试开发了",[32,33,37],"a",{"href":34,"rel":35},"https://github.com/L0st1/ai-chat",[36],"nofollow","ai-chat","，整个过程很流畅。在出租屋里写代码的时候我也不再焦虑，那干脆就再开发一个博客项目吧",[17,40,41],{},"这大概就是我为什么没有直接使用一个博客框架的原因，博客一共分为三个部分，技术文章、我的思考、我的日常",[13,43,44],{"id":44},"选型",[17,46,47],{},"我并不是像正常的产品需求一样进行拆分，我没有原型，只是将需求的内容一点一点补充",[49,50,51,55,58,61,64],"ul",{},[52,53,54],"li",{},"我的博客部署在哪里",[52,56,57],{},"如何将md文档直接渲染成html，我如果想在md中写一些样式怎么实现",[52,59,60],{},"如何在文档提交后，就立即更新",[52,62,63],{},"是否需要制定版本与回滚、访问统计、性能指标",[52,65,66,70],{},[67,68,69],"del",{},"是否需要ESLint、CommitLint","肯定需要",[72,73,74],"h3",{"id":74},"部署",[17,76,77],{},"最初我的想法是托管在GitHub，为此我甚至准备好了Actions；但后来我切换到了Vercel\nGithub Pages仅支持静态HTML，如果以后想添加更丰富的内容就很困难\nVercel开箱即用，包含完整的CI/CD、数据收集、日志、性能指标（回滚需要Pro），也支持SSR、Serverless",[72,79,80],{"id":80},"渲染",[17,82,83,84,87,88],{},"简单来说就是md的渲染，",[21,85,86],{},"@nuxt/content","，毋庸置疑\n那如果想在md中写写一些样式呢，可以直接写Vue组件，也可以使用 ",[89,90,93],"tooltip",{"text":91,"tone":92},"Markdown Components","info","MDC",[72,95,97],{"id":96},"cicd","CI/CD",[17,99,100],{},"CI/CD依靠Vercel即可",[13,102,103],{"id":103},"遇到过的问题",[72,105,106],{"id":106},"文章的名称是纯中文字符时路由出错",[17,108,109],{},"像往常一样推送后发现很多文章都显示同样内容，并且路由都是父级路由，再发现它们都是纯中文命名",[111,112,113],"blockquote",{},[17,114,115],{},"Nuxt Content 用 slugify 生成 path，默认会去掉非 ASCII 字符，导致纯中文文件名被 slug 成空串，多个文件都变成 /engineering 或 /thinking，产生冲突。数据库显示 stem 仍正确，但 path 错误。正在向 nuxt.config.ts 添加 content:file:afterParse 钩子，根据 stem 恢复正确的 path。",[17,117,118,119,122],{},"所以每篇文章现在都添加了属性",[21,120,121],{},"path","作为它们的路由",[72,124,126],{"id":125},"lcp异常","LCP异常",[128,129,131],"h5",{"id":130},"fcpttfb","FCP、TTFB",[111,133,134],{},[17,135,136],{},"以下数据均为我在同一设备访问页面产生",[17,138,139,144],{},[140,141],"img",{"alt":142,"src":143},"优化前：FCP、LCP、TTFB 等指标（其一）","/img/blog-fcp-lcp-metrics-before-1.png",[140,145],{"alt":146,"src":147},"优化前：FCP、LCP、TTFB 等指标（其二）","/img/blog-fcp-lcp-metrics-before-2.png",[17,149,150],{},[151,152,153],"em",{},"当前 FCP 偏高的本质原因不是页面慢，而是卡在 1.8s 的阈值附近。从数据上看，FCP 和 LCP 完全重合，说明页面没有提前渲染任何可见内容，而是依赖主内容一次性渲染。同时 TTFB 达到 1s，也显著推迟了首屏时间。",[17,155,156],{},[151,157,158],{},"优先降低 TTFB，再让 FCP 和 LCP 解耦，从而把 FCP 降低至 1.8s 以内。",[128,160,162],{"id":161},"解决方案","解决方案：",[17,164,165],{},"TTFB指标表现差通常因为SSR 渲染、API（服务器处理请求并生成响应的时间）、CDN命中率、网络延迟",[49,167,168,187],{},[52,169,170,173,174,176,177,180,181,183,184],{},[21,171,172],{},"/","不进行SSR，",[21,175,172],{},"进行预渲染 —— ",[21,178,179],{},"/blog/**","已预渲染，但首页",[21,182,172],{},"仍为 SSR，每次请求都会在 Vercel 上执行 serverless +",[21,185,186],{},"queryCollection",[52,188,189,190,193],{},"降低首屏CSS —— 项目将 KaTeX 全局 CSS 在 ",[21,191,192],{},"app.vue"," 中加载，可改移到仅文章页",[128,195,197],{"id":196},"效果如下","效果如下：",[17,199,200,204],{},[140,201],{"alt":202,"src":203},"优化后：Vercel 性能指标（其一）","/img/blog-vercel-performance-after-1.png",[140,205],{"alt":206,"src":207},"优化后：Vercel 性能指标（其二）","/img/blog-vercel-performance-after-2.png",[17,209,210,211],{},"因为vercel当前统计数据是统计值，固有所偏差\n",[140,212],{"alt":213,"src":214},"Vercel 分析数据说明","/img/blog-vercel-analytics-variance.png",[72,216,217],{"id":217},"字体问题",[17,219,220],{},"我本地使用霞鹜文楷，我希望我的博客也是霞鹜文楷",[17,222,223],{},"有两种方案：",[49,225,226,241],{},[52,227,228,229,234,235,240],{},"本地提供woff2，构建时把字体打进产物，预渲染出的 HTML 里只引用 CSS，但这样会导致资源的体积变大（尽管去采用专门提供给Web的集合，如",[32,230,233],{"href":231,"rel":232},"https://github.com/CMBill/lxgw-wenkai-web",[36],"霞鹜文楷 网络字体仓库","，",[32,236,239],{"href":237,"rel":238},"https://www.npmjs.com/package/@callmebill/lxgw-wenkai-web?activeTab=code",[36],"体积","仍然不小）",[52,242,243],{},"引用CDN，这仍会导致需要加载的资源体积变大，并且要考虑公共CDN的稳定性，且会丧失Vercel的优势",[17,245,246],{},"目前没有较好的思路，或许可以采用性能指标来衡量哪种效果更好，又或许是我想得不对，等待后续验证",[72,248,250],{"id":249},"mdc中使用原子类不生效问题","MDC中使用原子类不生效问题",[17,252,253],{},"比如",[255,256,261],"pre",{"className":257,"code":258,"language":259,"meta":260,"style":260},"language-md shiki shiki-themes github-light github-dark","::blur-reveal{class=\"pt-8\" :duration=\"0.75\"}\n一些填充内容\n::\n","md","",[21,262,263,272,278],{"__ignoreMap":260},[264,265,268],"span",{"class":266,"line":267},"line",1,[264,269,271],{"class":270},"sVt8B","::blur-reveal{class=\"pt-8\" :duration=\"0.75\"}\n",[264,273,275],{"class":266,"line":274},2,[264,276,277],{"class":270},"一些填充内容\n",[264,279,281],{"class":266,"line":280},3,[264,282,283],{"class":270},"::\n",[17,285,286],{},"因为UnoCSS不会扫描未被import到构建流程的文件，所以md文章中样式不会被扫描",[17,288,289,290],{},"在UnoCSS的配置里添加即可，目的是告诉UnoCSS去扫描md文件中的原子类，",[32,291,294],{"href":292,"rel":293},"https://github.com/L0st1/blog/blob/main/uno.config.ts",[36],"源码",[255,296,301],{"className":297,"code":299,"language":300},[298],"language-text","  content: {\n    filesystem: ['content/**/*.md'],\n  },\n","text",[21,302,299],{"__ignoreMap":260},[304,305,306],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":260,"searchDepth":308,"depth":308,"links":309},4,[310,311,316],{"id":15,"depth":274,"text":15},{"id":44,"depth":274,"text":44,"children":312},[313,314,315],{"id":74,"depth":280,"text":74},{"id":80,"depth":280,"text":80},{"id":96,"depth":280,"text":97},{"id":103,"depth":274,"text":103,"children":317},[318,319,325,326],{"id":106,"depth":280,"text":106},{"id":125,"depth":280,"text":126,"children":320},[321,323,324],{"id":130,"depth":322,"text":131},5,{"id":161,"depth":322,"text":162},{"id":196,"depth":322,"text":197},{"id":217,"depth":280,"text":217},{"id":249,"depth":280,"text":250},"2026-04-17","记录我从零开始开发博客项目，逐渐认识Nuxt，以及遇到的问题",{},true,"/life/blog-trade-off",{"title":5,"description":328},"life/一个从零实现的个人博客",[335],"Others","b-5-W1c5KMQLHQ7yVxrSCHuaMgx0EXz1Lxo7TVs6CMc",1776757930552]