[{"data":1,"prerenderedAt":870},["ShallowReactive",2],{"content:\u002F2024\u002Fblog-using-nuxt":3,"surround:\u002F2024\u002Fblog-using-nuxt":858},{"id":4,"title":5,"body":6,"categories":818,"date":820,"description":821,"draft":822,"extension":823,"image":824,"meta":825,"navigation":840,"path":841,"permalink":842,"published":842,"readingTime":843,"recommend":842,"references":842,"seo":848,"sitemap":849,"stem":850,"tags":851,"type":855,"updated":856,"__hash__":857},"content\u002Fposts\u002F2024\u002Fblog-using-nuxt.md","博客进化：从 Hexo 到 Nuxt Content",{"type":7,"value":8,"toc":786},"minimark",[9,18,22,27,30,33,37,40,43,46,49,52,55,58,61,64,67,75,89,96,134,151,154,162,176,179,229,232,235,245,256,292,295,298,317,340,343,351,354,362,389,398,401,404,407,439,442,445,448,451,454,468,500,505,509,512,515,531,551,567,578,581,584,588,591,594,597,602,605,612,622,629,633,636,647,664,667,687,694,697,700,724,747,766,773,776,779],[10,11,14],"alert",{"title":12,"type":13},"技术提醒","warning",[15,16,17],"p",{},"本文代码为撰写时的状态，仅供参考，不代表最终实现。",[19,20,21],"h2",{"id":21},"升级动力",[23,24,26],"h3",{"id":25},"hexo-主题","Hexo 主题",[15,28,29],{},"2023年5月24日，我使用 Heo 风格的 Acrylic 主题，将个人网站升级为 Hexo 博客。但不久之后，Acrylic 主题停止维护，同时 Heo 风格的博客主题大行其道，令我感到「泯然于众Blog」。",[15,31,32],{},"于是在2024年1月9日，我选择了偶然之间瞥见的「千雪的咖啡厅」的上游主题 Stellar。并且惊喜地发现，Stellar 的作者 xaoxuu 也是知名主题 Volantis 的主要贡献者。朋友 Colsrch 负责管理 Volantis 主题交流群，我也曾于2020年加入了交流群。",[23,34,36],{"id":35},"站际关系","「站」际关系",[15,38,39],{},"在更换 Stellar 主题之后，我于2024年1月29日开始积极更新博客，并添加了一些友链，藉以丰富自己的「站」际关系。由于几天后友链之一「微光档案」无法访问，我通过 Internet Archive 找到并加入了其交流群，了解到网站正在备案。自此，命运的齿轮开始转动，我与「微光档案」的站长 KazariEX 在后来结下了更深的联系。",[23,41,42],{"id":42},"博客美化",[15,44,45],{},"Stellar 主题的更新迭代速度很快，就如同我博客文章的更新速度一样。在这期间，我通过插入自定义 CSS 来更改样式。随着修改幅度越来越大，我编写了一系列 CSS 文件、Hexo 脚本，甚至还使用 JS 修改 DOM，为博客 Logo 注入动画元素。我的底线是「不修改主题源代码」，以避免拉取新版本时处理合并冲突，就算修改也是向主题提交 Bug 修复 PR。",[15,47,48],{},"KazariEX 见此情况，几次建议我使用 Nuxt.js 重构博客并手写主题，但我当时认为博客重心在于文章，因此并未考虑。",[23,50,51],{"id":51},"重构思考",[15,53,54],{},"我的想法是，当 Hexo 无法满足我对博客的要求时（可能是几年后），我会考虑用 Astro 重构博客。但目前一切尚好。",[15,56,57],{},"2024年6月1日， KazariEX 带领我 10 小时极速上手 Nuxt 重构个人主页。从某种意义上讲，此刻我由原生前端开发转变为一名现代前端开发者。我通过实战了解了 Nuxt 和 Vue 的基础知识，并体验到了现代前端开发的便捷。",[15,59,60],{},"8月3日，在 KazariEX 不知第几次建议下，我开始尝试使用 Nuxt Content 构建博客。8月11日，新博客上线。",[19,62,63],{"id":63},"功能开发",[23,65,66],{"id":66},"深色模式",[15,68,69,70,74],{},"我选择直接使用个人主页项目中的 ",[71,72,73],"code",{"code":73},"ThemeToggle.vue"," 组件。这个组件的开发过程也很有意思。",[15,76,77,78,84,85,88],{},"我使用原生的 ",[71,79,82],{"className":80,"code":82,"language":83},[81],"language-sh","localStorage","sh"," 实现了颜色模式的持久化存储和自动加载，但后来觉得既然有了框架，使用 Pinia 来管理状态会更好。但随之而来遇到的问题是网页会在 ",[71,86,87],{"code":87},"mounted"," 钩子读取持久化存储的主题，如果主题与系统偏好不匹配时，刷新会导致短暂地使用系统偏好。",[10,90,93],{"title":91,"type":92},"简而言之","info",[15,94,95],{},"如果系统偏好浅色模式，而博客选择了深色主题，刷新时会闪白。",[15,97,98,99,106,107,115,116,121,122,127,128,133],{},"不少网站都有这个问题，比如 ",[100,101,105],"a",{"href":102,"rel":103},"https:\u002F\u002Fdocs.github.com\u002Fzh",[104],"nofollow","GitHub Docs","、",[100,108,111,112],{"href":109,"rel":110},"https:\u002F\u002Fvueuse.org\u002Fcore\u002FuseColorMode\u002F",[104],"VueUse 文档的 ",[71,113,114],{"code":114},"useColorMode","。最令我佩服的是 Astro 主题 ",[100,117,120],{"href":118,"rel":119},"https:\u002F\u002Fgithub.com\u002FEveSunMaple\u002FFrosti",[104],"Frosti","，我提出 ",[100,123,126],{"href":124,"rel":125},"https:\u002F\u002Fgithub.com\u002FEveSunMaple\u002FFrosti\u002Fissues\u002F16",[104],"Issue: 深色模式下刷新\u002F切换页面时的屏幕闪烁"," 后，作者 ",[100,129,132],{"href":130,"rel":131},"https:\u002F\u002Fgithub.com\u002FEveSunMaple",[104],"EveSunMaple"," 持续跟进，最终解决了问题。",[15,135,136,137,143,144,146,147,150],{},"话说回来，我在解决页面刷新时的主题问题时遇到了困难，于是请求 KazariEX 的支援。我们发现 VitePress 没有这个问题，就一起研究它的源码，最终发现 VitePress 使用原生 ",[71,138,141],{"className":139,"code":141,"language":142},[140],"language-html","\u003Cscript>","html"," 预处理主题，避免了在 ",[71,145,87],{"code":87}," 钩子中才切换主题的情况。我们参考这一思路解决了问题。后来 isYangs 引入了现成的 ",[71,148,149],{"code":149},"@nuxt\u002Fcolor-mode"," 模块，使代码更加优雅。",[23,152,153],{"id":153},"样式预处理",[15,155,156,157,161],{},"在开发过程中，我选择基于自己修改的 Stellar 主题编写样式。通过 ",[71,158,160],{"className":159,"code":160,"language":83},[81],"tokei .\u002Fapp"," 统计，博客前端排除空行后大约有4000行代码，其中大约一半是样式代码。",[15,163,164,165,171,172],{},"我选择了 SCSS 作为样式预处理器，主要使用 SCSS 的嵌套声明块、",[71,166,169],{"className":167,"code":169,"language":170},[168],"language-css","@mixin","css"," 和少量用于媒体查询宽度的变量。",[173,174,175],"strong",{},"绝大多数语法均兼容 CSS 3。",[15,177,178],{},"有群友提出使用原子化 CSS，有助于减少样式代码量。我认为原子化 CSS 能处理简易样式，但不适合设计导向或复杂样式，「风记星辰」的站长「宇」也支持我的观点。",[180,181,182,185,192,195,198,200,213,215,218,220,223,226],"chat",{},[15,183,184],{},"{.}",[15,186,187,188,191],{},"删一行样式都会出问题的哪种😵",[189,190],"br",{},"\n当我能给样式代码写注释的时候，它已经不是普通的易于改动的代码了",[15,193,194],{},"{Restent Ou}",[15,196,197],{},"所以我用 UnoCSS",[15,199,184],{},[15,201,202,203,205,206,208,209,212],{},"UnoCSS 能帮助处理这种近乎玄学的问题吗",[189,204],{},"\n我昨晚睡前还认真思考了一下要不要上 UnoCSS",[189,207],{},"\n最后觉得我的新博客既然注重样式，还是专门使用 ",[71,210,211],{"code":211},"\u003Cstyle>"," 好",[15,214,194],{},[15,216,217],{},"我提到原子化 CSS 是因为相比手写 CSS，其复杂度会相对降低",[15,219,184],{},[15,221,222],{},"毕竟我现在遇到的问题是 CSS 本身特性带来的问题，而不是原不原子化影响对样式的约束\u002F控制能力",[15,224,225],{},"{宇}",[15,227,228],{},"是的，其实现在专门设计的网站，几乎都不使用 CSS 框架了，自己写不仅节省代码，而且 HTML 也干净",[15,230,231],{},"因此，博客未来只可能会有限地引入原子化 CSS，例如仅对需要设置透明度、字体大小和颜色的元素应用原子化 CSS。",[23,233,234],{"id":234},"海量组件",[15,236,237,238,244],{},"先前我选择 Stellar 主题的原因之一，便是它「支持丰富的标签和动态数据组件」。没想到八个月前的回旋镖飞回到了自己头上，我望着博客 Markdown 文件里大量通过 Nunjucks 语法 ",[71,239,242],{"className":240,"code":242,"language":243},[241],"language-mdc","{% tag_name attr1 attr2:val2 %}","mdc"," 调用的组件，思考如何用 Nuxt Content 和 MDC 语法编写用于替代的 Vue 组件。随后，我便开始了漫长的组件编写之旅。",[15,246,247,248,251,252,255],{},"MDC 语法相比传统 Markdown 语法，能够更方便地使用 Vue 组件并传递参数。受限于一些组件（如聊天记录、时间线）的渲染方式，我甚至使用非标准方式获取 VNode 子节点中的默认插槽内容的文本值，写出了 ",[71,249,250],{"code":250},"as any"," 类型断言。不过总之，各种组件能",[173,253,254],{},"以比较优雅的方式调用","了。",[257,258,261,273,281],"tab",{":center":259,":tabs":260},"true","[\"使用Chat组件\", \"使用Tab组件\", \"Timeline组件简化源码\"]",[262,263,265],"template",{"v-slot:tab1":264},"",[266,267,271],"pre",{"className":268,"code":269,"filename":270,"language":243,"meta":264},[241],"::chat\n\n{.}\n\n这是我说的内容，显示在右侧\n\n{用户1}\n\n这是用户1的回复\\\n可以这样换行\n\n::\n","blog-using-nuxt.md",[71,272,269],{"__ignoreMap":264},[262,274,275],{"v-slot:tab2":264},[266,276,279],{"className":277,"code":278,"filename":270,"language":243,"meta":264},[241],"::tab\n---\ntabs: [\"使用Chat组件\", \"使用Tab组件\", \"Timeline组件简化源码\"]\ncenter: true\nactive: 2 # 默认显示第二个选项卡，可选\n---\n#tab1\n内容：使用Chat组件\n#tab2\n内容：使用Tab组件\n#tab3\n内容：聊天组件源代码\n::\n",[71,280,278],{"__ignoreMap":264},[262,282,283],{"v-slot:tab3":264},[266,284,290],{"className":285,"code":287,"filename":288,"language":289,"meta":264},[286],"language-vue","\u003Cscript lang=\"tsx\" setup>\nfunction render() {\n    return defineSlots().default?.().map((node: VNode) => {\n        const textContent = (node.children as any)?.default?.()[0].children || ''\n        const match = textContent?.match?.(\u002F^\\{(.*?)\\}$\u002F)\n        return match\n            ? \u003Cdiv class=\"timeline-caption\">{match[1]}\u003C\u002Fdiv>\n            : \u003Cdiv class=\"timeline-body card\">{node}\u003C\u002Fdiv>\n    })\n}\n\u003C\u002Fscript>\n","Timeline.vue","vue",[71,291,287],{"__ignoreMap":264},[23,293,294],{"id":294},"评论接入",[15,296,297],{},"2024年6月28日，我为上一版博客接入了 Twikoo 评论系统；在8月11日新博客上线当天，我通过简易代码重新接入了 Twikoo。",[15,299,300,301,307,308,311,312,316],{},"后来，KazariEX 提出使用 ",[71,302,305],{"className":303,"code":305,"language":306},[304],"language-js","useScriptTag()","js"," 引入 Twikoo，我觉得这就是一种抽象。从积极的插入脚本变为懒加载之后，无论是在外部使用动态 key 调用组件，抑或是在组件内监听 ",[71,309,310],{"code":310},"path","，都会在 SSG 环境下异常，只得如此在 ",[71,313,315],{"className":314,"code":315,"language":306},[304],"onMounted()"," 中手动重新调用，倒是显得不太优雅了。",[257,318,321,332],{":center":259,":tabs":319,":active":320},"[\"旧代码\", \"新代码\"]","2",[262,322,323],{"v-slot:tab1":264},[266,324,330],{"className":325,"code":327,"filename":328,"language":329,"meta":264},[326],"language-ts","onMounted(() => {\n    const script = document.createElement('script')\n    script.src = appConfig.twikoo.js\n    script.defer = true\n    script.onload = () => {\n        window.twikoo.init({\n            envId: appConfig.twikoo.envId,\n            el: '#tk-comment',\n        })\n    }\n    document.head.appendChild(script)\n})\n","Comment.vue > script setup","ts",[71,331,327],{"__ignoreMap":264},[262,333,334],{"v-slot:tab2":264},[266,335,338],{"className":336,"code":337,"filename":328,"language":329,"meta":264},[326],"function initTwikoo() {\n    window.twikoo?.init?.({\n        envId: appConfig.twikoo.envId,\n        el: '#twikoo',\n    })\n}\n\u002F\u002F 从其他页面路由至文章页面时，通过 onLoaded 事件初始化，onMounted 不起作用\nuseScriptTag(appConfig.twikoo.js, () => initTwikoo(), { defer: true })\n\u002F\u002F 在文章页面之间路由时不会触发 onLoaded 事件，需要手动初始化\nonMounted(() => initTwikoo())\n",[71,339,337],{"__ignoreMap":264},[23,341,342],{"id":342},"页面布局",[15,344,345,346,350],{},"我参考了 Stellar 主题的左中右三栏布局。其中，右侧栏可以通过元数据或 Front Matter 中的 ",[71,347,349],{"className":348,"code":349,"language":306},[304],"aside"," 数组来自定义组件。",[15,352,353],{},"Stellar 主题使用 Grid 布局，而我选择借鉴 VitePress 默认主题的 Flex 布局实现方式，这样不仅兼容性更好，还能在隐藏右侧栏时让正文占据此空间。",[266,355,360],{"className":356,"code":358,"filename":270,"language":359,"meta":264},[357],"language-yaml","---\n# hideAside: true # 隐藏右侧栏\naside: [toc, github-card]\n---\n","yaml",[71,361,358],{"__ignoreMap":264},[15,363,364,365,368,369,373,374,378,379,383,384,388],{},"在处理侧边栏显隐的逻辑时，我起初复用了 ",[71,366,349],{"className":367,"code":349,"language":306},[304],"，其值为 ",[71,370,372],{"className":371,"code":372,"language":306},[304],"false"," 时会隐藏侧边栏，但后来我发现这种写法意义不够明确，不利于其他人识别，尤其是代码中 ",[71,375,377],{"className":376,"code":377,"language":306},[304],"route.meta.aside !== false"," 的判断经常会被简化为 ",[71,380,382],{"className":381,"code":382,"language":306},[304],"route.meta.aside","。在 isYangs 的建议下，我将隐藏侧边栏的逻辑移到了 ",[71,385,387],{"className":386,"code":387,"language":306},[304],"hideAside"," 中，并修改了类型定义。",[266,390,396],{"className":391,"code":393,"filename":394,"language":395,"meta":264},[392],"language-diff","declare module 'vue-router' {\n    interface RouteMeta {\n-       aside: string[] | false\n+       hideAside?: boolean\n+       aside: string[]\n    }\n}\n","types\u002Findex.ts","diff",[71,397,393],{"__ignoreMap":264},[19,399,400],{"id":400},"带动效应",[23,402,403],{"id":403},"汲取灵感",[15,405,406],{},"我使用 Nuxt 和 Nuxt Content 开发博客时遇到了不少问题，但好在发现了不少使用 Nuxt Content 的小伙伴，他们的文章提供了不少灵感。",[408,409,410,419,431],"ul",{},[411,412,413,418],"li",{},[100,414,417],{"href":415,"rel":416},"https:\u002F\u002Fblog.ahzoo.cn\u002Fp\u002F511b3f9\u002F",[104],"从Hexo到Nuxt，正式升级为动态博客！- Z次元","：这篇文章来自我的友链，他的博客间接鼓励了我使用 Nuxt Content。",[411,420,421,426,427,430],{},[100,422,425],{"href":423,"rel":424},"https:\u002F\u002Fhadb.me\u002Fposts\u002Fswitch-blog-from-ghost-to-nuxt-content",[104],"博客从 Ghost 迁移至 Nuxt Content - HADB.ME","：我开发博客时，他在 Wakatime 排行榜上每日代码时长、语言排名与我极其相似，再访问他的个人主页，原来他也在使用 Nuxt Content 构建博客，非常巧合。我参考了他的 ",[71,428,429],{"code":429},"atom.xml"," 生成逻辑。",[411,432,433,438],{},[100,434,437],{"href":435,"rel":436},"https:\u002F\u002Fblog.gxres.net\u002Fposts\u002Frebuild-my-blog-with-nuxt",[104],"使用 Nuxt 重构我的博客 - Restent's Notebook","：刚开始参考了他的目录逻辑，但后来通过递归使用复用组件完成了逻辑，并做出了伴随滚动切换高亮项的目录。",[23,440,441],{"id":441},"开源反哺",[15,443,444],{},"我在使用 Nuxt Content 开发博客时，为其他人提供了一些样式思路。博客交流群里也有不少小伙伴受到鼓舞，使用现代的前端框架重构博客。",[19,446,447],{"id":447},"兼容优化",[23,449,450],{"id":450},"样式特性",[15,452,453],{},"当然，也少不了我们的老伙计——安卓上的夸克浏览器。",[455,456,457],"quote",{},[15,458,459,460,463,464,467],{},"如果一个现代的前端网站能在夸克APP上正常运行，那么它的兼容性",[173,461,462],{},"十分","良好；如果它将兼容夸克APP，那么我认为这实在",[173,465,466],{},"万分","厉害。",[15,469,470,471,475,476,479,480,484,485,490,491,495,496,499],{},"它除了不支持五年前的 ",[71,472,474],{"className":473,"code":474,"language":170},[168],"prefers-color-scheme"," 特性外，我还在测试环节留意到诸如 ",[71,477,478],{"code":478},"dvh"," 的动态视口单位、",[71,481,483],{"className":482,"code":483,"language":170},[168],":has()"," 函数式伪类等新特性也不受支持。对于动态视口单位，我通过多个声明语句渐进覆盖，「风记星辰」的站长「宇」推荐我阅读 ",[100,486,489],{"href":487,"rel":488},"https:\u002F\u002Fwww.thyuu.com\u002F72670#%E5%85%BC%E5%AE%B9%E6%80%A7%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88",[104],"2023年应该重新定义的CSS习惯(一)","，使用更优雅的媒体查询 ",[71,492,494],{"className":493,"code":494,"language":170},[168],"@supports"," 写法。对于 ",[71,497,483],{"className":498,"code":483,"language":170},[168]," 函数式伪类，我通过 Vue 的动态 class 绑定解决。",[455,501,502],{},[15,503,504],{},"代码越少，越使用新特性。越使用新特性，兼容性越差。兼容性越差，越需要更多的兼容性代码。所以代码越少，代码就越多。",[23,506,508],{"id":507},"ssg-ssr","SSG != SSR",[15,510,511],{},"Nuxt 之 SSR 开发体验，善也。然限于 Vercel 之速，以 SSG 部署尔。",[15,513,514],{},"上文样式之扰仅为滥觞，代码之兼容性亦困我于囹圄。幸有 KazariEX 助我。",[15,516,517,518,522,523,526,530],{},"其一为 ",[71,519,521],{"className":520,"code":521,"language":306},[304],"Object.groupBy()","——本地体验大善，上云部署错误，观之为 Node.js 20 环境，果断升至 22。此后正常部署，本地体验正常。",[173,524,525],{},"喜笑颜开之时，掏出夸克APP。不喜。",[71,527,529],{"className":528,"code":529,"language":306},[304],"import { group } from 'radash'"," 解决之。",[15,532,533,534,538,539,543,544,547,530],{},"其二为 ",[71,535,537],{"className":536,"code":537,"language":306},[304],"Array.prototype.sort()","。此为修改方法而非复制方法，会破坏响应性。忽闻 ",[71,540,542],{"className":541,"code":542,"language":306},[304],"Array.prototype.toSorted()","，大喜，如获至宝，遂部署。",[173,545,546],{},"复掏出夸克APP。不喜。",[71,548,550],{"className":549,"code":550,"language":306},[304],"import { alphabetical } from 'radash'",[15,552,553,554,558,559,562,563,566],{},"其三为 ",[71,555,557],{"className":556,"code":557,"language":306},[304],"Array.prototype.at()","。开启 Chrome 88，发觉友链页面先正常加载后报错。此为 SSG 与 CSR ",[173,560,561],{},"水合不匹配","，经查，友链页面提取域名之函数使用 ",[71,564,557],{"className":565,"code":557,"language":306},[304],"，而 Chrome 92 之前无此方法。遂改之。",[15,568,569,570,573,574,577],{},"其四为 SSG 下 HTML 属性 (attrs) 响应性之缺失。初，使用 URL 之查询参数 ",[71,571,572],{"code":572},"?page="," 以定页面，点击翻页功能如常，然刷新时渲染不符预期。测试 attrs 时，发现其难以具备响应性，遂弃查询参数，以 Vue Router 之动态路由匹配 ",[71,575,576],{"code":576},"\u002Fpage\u002F:page"," 替之。",[15,579,580],{},"其五为 SSG 下侧栏组件篡位。侧栏目录 (TOC) 组件在换文之时，会悄然以新文数据覆于旧文缓存中，偶以极其复杂之触发条件令其原形毕现，遂通过侧栏动态组件之 key 断其生命周期，乃造新 TOC 于新文而非旧 TOC 监听 URL 路径以篡其数据也。",[15,582,583],{},"SSG 之 Bug 种种，实扰人耳。",[23,585,587],{"id":586},"bug-排查","Bug 排查",[15,589,590],{},"发现了 Bug，当然要快速定位问题。还好我使用的是 Vercel，它提供了历史部署版本的快照，通过访问旧部署，即可快速定位到 Commit 和出现问题的代码。",[15,592,593],{},"然后只需要修复就行。至少听起来十分轻松。",[15,595,596],{},"嗯，我还会留下注释，防止自己后续优化代码时又改回可能导致 Bug 的写法。",[455,598,599],{},[15,600,601],{},"在这个项目里，没有一行注释是无辜的。",[23,603,604],{"id":604},"列表负载",[15,606,607,608,611],{},"我善于观察异常的苗头，因此发现了许多容易被忽视的问题。例如，我的博客上线第一天，相比于旧博客的带宽消耗翻了番。经过排查，首页、归档页面加载时，会附带一个约 2MB 的 ",[71,609,610],{"code":610},"payload.json","，细看发现竟含有我全站的文章元数据和正文内容。",[15,613,614,615,621],{},"KazariEX 猜我没学过数据库，因为我犯了类似 ",[71,616,619],{"className":617,"code":619,"language":620},[618],"language-sql","select *","sql"," 的错误。而我也确实没学过，毕竟这学期才会有《数据库原理与应用》课程。",[15,623,624,625,628],{},"不过凭直觉，应该在查询结果中排除 ",[71,626,627],{"code":627},"body"," 字段，后来我改成只选择指定字段，需要什么就加什么。虽然 Nuxt 相比于 Vue 不用手动书写 Vue 内置函数、组件、工具函数、组合式函数的导入语句，但在 Nuxt Content 的编程模式下，我仍需审慎组合、使用它提供的 API。",[23,630,632],{"id":631},"seo","SEO",[15,634,635],{},"2024年8月24日是我博客历史上灰暗的一天，因为我的必应和谷歌搜索流量几乎置零。",[15,637,638,639,642,643,646],{},"我的所有页面被错误的打上了 ",[71,640,641],{"code":641},"noindex"," 标记，这将阻止搜索引擎索引我的博客；我的 ",[71,644,645],{"code":645},"sitemap.xml"," 为空，这相当于博客告诉搜索引擎自己没有内容。",[15,648,649,650,653,654,657,658,663],{},"在我起初引入 ",[71,651,652],{"code":652},"@nuxtjs\u002Fseo@2.0.0-rc.16"," 时一切检查正常，但在一次依赖更新后，改变悄然发生。有用户在 ",[71,655,656],{"code":656},"@nuxtjs\u002Fseo@2.0.0-rc.18"," ",[100,659,662],{"href":660,"rel":661},"https:\u002F\u002Fgithub.com\u002Fharlan-zw\u002Fnuxt-seo\u002Fissues\u002F296",[104],"报告了空 sitemap 问题","，开发者讲虽然有一个“轻微的 (slightly) 破坏性 (breaking) 更改”，但造成空 sitemap 另有其因，应当升级依赖。",[23,665,666],{"id":666},"来自未来的文章",[15,668,669,670,674,675,657,682,686],{},"YAML 会以 UTC 时区解析 Front Matter 中的时间，刚更新的文章常常显示来自“8小时后”，但我希望以本地时区解析。刚开始修改了 ",[71,671,673],{"className":672,"code":673,"language":306},[304],"getPostDate()"," 函数，但语义化的 ",[676,677,678],"del",{},[71,679,681],{"className":680,"code":681,"language":142},[140],"\u003Ctime datetime=\"{{ date }}\">",[71,683,685],{"className":684,"code":685,"language":142},[140],"\u003Ctime :datetime=\"date\">"," 标签依旧不符合规范，因此编写了这个 Nitro 插件用于修正时区。",[266,688,692],{"className":689,"code":690,"filename":691,"language":329,"meta":264},[326],"import { getTimezoneOffset } from 'date-fns-tz'\nimport blogConfig from '~~\u002Fblog.config'\n\nconst timezoneOffset = getTimezoneOffset(blogConfig.timezone)\n\nexport default defineNitroPlugin((nitroApp) => {\n    nitroApp.hooks.hook('content:file:afterParse', (file) => {\n        if (file.date) {\n            file.date = new Date(new Date(file.date).getTime() - timezoneOffset)\n        }\n        if (file.updated) {\n            file.updated = new Date(new Date(file.updated).getTime() - timezoneOffset)\n        }\n    })\n})\n","server\u002Fplugins\u002FfixPostTimezone.ts",[71,693,690],{"__ignoreMap":264},[15,695,696],{},"Nuxt 虽然是热更新，但我甚至重启开发环境都不能确保其生效，必须手动清除缓存。",[23,698,699],{"id":699},"颜色追求",[10,701,702,711],{},[262,703,704],{"v-slot:title":264},[15,705,706,707,710],{},"行内代码 ",[71,708,709],{"code":709},"inline-code"," 的需求",[15,712,713,714,718,719,723],{},"其 ",[71,715,717],{"className":716,"code":717,"language":170},[168],"border-color"," 和 ",[71,720,722],{"className":721,"code":722,"language":170},[168],"background-color"," 应跟随文本颜色变化，而不是单调的灰色。",[15,725,726,727,730,731,734,735,738,739,742,743,746],{},"起初我想写为 ",[71,728,729],{"code":729},"rgba(currentcolor, 0.2)"," ，但一方面它不符合 SCSS 的函数参数个数要求，另一方面 CSS 仅支持自定义属性 ",[71,732,733],{"code":733},"rgba(var(--color), 0.2)"," 而不支持在其中使用 ",[71,736,737],{"code":737},"currentColor","。故只能使用 ",[71,740,741],{"code":741},"#{\"rgba(var(--color), 0.2)\"}"," 的形式绕过 SCSS 检查，还要通过 JS 计算出当前颜色并写入 ",[71,744,745],{"code":745},"--color"," 中，并且在切换深色模式后还需要重新计算值，可谓十分不优雅。",[257,748,749,758],{":center":259,":tabs":319},[262,750,751],{"v-slot:tab1":264},[266,752,756],{"className":753,"code":754,"filename":755,"language":289,"meta":264},[286],"\u003Cscript setup lang=\"ts\">\nconst code = ref\u003CHTMLElement>()\nconst color = ref\u003Cstring>()\nonMounted(() => {\n    const rgbColorStr = getComputedStyle(code.value!).color\n    color.value = rgbColorStr.match(\u002F\\d+\u002Fg)!.map(Number).join(',')\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n\u003Ccode ref=\"code\" :style=\"{ '--color': color }\">\u003Cslot \u002F>\u003C\u002Fcode>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped lang=\"scss\">\ncode {\n    padding: 0.1em 0.3em;\n    border: 1px solid #{\"rgba(var(--color), 0.2)\"};\n    border-radius: 4px;\n    background-color: #{\"rgba(var(--color), 0.05)\"};\n    font-size: 0.875em;\n}\n\u003C\u002Fstyle>\n","ProseCodeInline.vue",[71,757,754],{"__ignoreMap":264},[262,759,760],{"v-slot:tab2":264},[266,761,764],{"className":762,"code":763,"filename":755,"language":289,"meta":264},[286],"\u003Ctemplate>\n\u003Ccode>\u003Cslot \u002F>\u003C\u002Fcode>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped lang=\"scss\">\ncode {\n    padding: 0.1em 0.3em;\n    border: 1px solid var(--c-border);\n    border-color: color-mix(in srgb, currentcolor 10%, transparent);\n    border-radius: 4px;\n    background-color: var(--c-bg-2);\n    background-color: color-mix(in srgb, currentcolor 5%, transparent);\n    font-size: 0.875em;\n}\n\u003C\u002Fstyle>\n",[71,765,763],{"__ignoreMap":264},[15,767,768,769,772],{},"后来，KazariEX 提到了 CSS ",[71,770,771],{"code":771},"color-mix()"," 函数，我认为他又一次给出了真正优雅的答案。至于兼容性？不担心的，通过上文「多个声明语句渐进覆盖」的方法即可解决。",[19,774,775],{"id":775},"阶段总结",[15,777,778],{},"经过约一个月\u002F200小时的开发，我的博客由 Hexo 转为基于 Nuxt Content 开发的项目。项目选型、主要功能完成后，我选择不断优化已有代码，修复 Bug、优化性能、语义化和兼容性。我认为，开发就是不断的做加法和减法，在项目起步阶段，减法比加法更重要，这样才有利于后续功能的扩展，而不是面对「独木桥上架起的二层小洋房」无从下手。",[15,780,781,782,785],{},"另外，我认为，博客的精髓在于内容，技术与框架不过是其内在的骨架，样式也仅为锦上添花的一笔，",[173,783,784],{},"真正优秀的博客并不会因为架构、样式的陈旧或简单而蒙尘","。我仍将持续投入内容创作与功能建构，以期形成自己的独特表达。",{"title":264,"searchDepth":787,"depth":787,"links":788},4,[789,797,804,808,817],{"id":21,"depth":790,"text":21,"children":791},2,[792,794,795,796],{"id":25,"depth":793,"text":26},3,{"id":35,"depth":793,"text":36},{"id":42,"depth":793,"text":42},{"id":51,"depth":793,"text":51},{"id":63,"depth":790,"text":63,"children":798},[799,800,801,802,803],{"id":66,"depth":793,"text":66},{"id":153,"depth":793,"text":153},{"id":234,"depth":793,"text":234},{"id":294,"depth":793,"text":294},{"id":342,"depth":793,"text":342},{"id":400,"depth":790,"text":400,"children":805},[806,807],{"id":403,"depth":793,"text":403},{"id":441,"depth":793,"text":441},{"id":447,"depth":790,"text":447,"children":809},[810,811,812,813,814,815,816],{"id":450,"depth":793,"text":450},{"id":507,"depth":793,"text":508},{"id":586,"depth":793,"text":587},{"id":604,"depth":793,"text":604},{"id":631,"depth":793,"text":632},{"id":666,"depth":793,"text":666},{"id":699,"depth":793,"text":699},{"id":775,"depth":790,"text":775},[819],"技术","2024-08-27 18:30:55","抬笔一挥，便洒出占半的前端样式代码。开发功能并不慢，样式优化与问题修复却很耗时间。博客前端的现代化转型，且听我话其分晓。",false,"md","https:\u002F\u002Fassets.zhilu.cyou\u002Fcover3\u002Fblog-using-nuxt.jpg",{"aside":826,"slots":829},[827,828],"toc","meta-aside-github",{"aside-github":830},{"props":831,"type":7,"value":833},{"title":832},"项目仓库",[834],[835,836],"link-card",{"description":837,"link":838,"title":839},"My blog, powered by Nuxt 4 & Nuxt Content v3. 纸鹿摸鱼处，分享技术与生活。","https:\u002F\u002Fgithub.com\u002FL33Z22L11\u002Fblog-v3","L33Z22L11\u002Fblog-v3",true,"\u002F2024\u002Fblog-using-nuxt",null,{"text":844,"minutes":845,"time":846,"words":847},"22 min read",21.615,1296900,4323,{"title":5,"description":821},{"loc":841},"posts\u002F2024\u002Fblog-using-nuxt",[852,853,854],"Nuxt","博客迁移","静态站点","story","2024-09-04 21:18:32","2Q99f8CRuABXbOFF9VNl5m9q603tIaoOTp88-TqCOwc",[859,864],{"title":860,"path":861,"stem":862,"date":863,"type":855,"children":-1},"祖母的90年代南昌差旅之险","\u002F2024\u002Fgrandma-high-point","posts\u002F2024\u002Fgrandma-high-point","2024-08-25 06:46:54",{"title":865,"path":866,"stem":867,"date":868,"type":869,"children":-1},"Windows 上的开源软件入门","\u002F2024\u002Fsfd-xupt","posts\u002F2024\u002Fsfd-xupt","2024-09-17 17:51:12","tech",1782091375148]