[{"data":1,"prerenderedAt":500},["ShallowReactive",2],{"blog-zh-2025-12-14-nuxt-blog-architecture":3},{"id":4,"title":5,"body":6,"category":481,"date":482,"description":483,"draft":484,"extension":485,"lang":486,"meta":487,"navigation":493,"path":494,"seo":495,"sitemap":496,"slug":497,"stem":498,"updated":482,"__hash__":499},"blog/blog/zh/2025-12-14-nuxt-blog-architecture.md","Nuxt 4 + @nuxt/content + Vercel：个人博客的技术选型与架构思考",{"type":7,"value":8,"toc":469},"minimark",[9,13,16,19,22,35,38,43,46,69,73,76,79,90,93,97,100,111,118,145,148,151,155,158,166,187,190,205,208,212,219,225,240,247,251,254,257,272,275,278,281,285,288,299,302,306,309,320,323,334,337,344,423,427,430,438,441,445,448,462,465],[10,11,12],"p",{},"这篇文章并不是教程，而是一次从项目启动阶段出发的技术选型记录。",[10,14,15],{},"这个博客的目标并不是展示 UI 或练习框架，\n而是作为一个长期写作与技术沉淀的平台。",[10,17,18],{},"从架构角度来看，这个博客属于典型的\ncontent-driven site（内容驱动型网站），\n其核心目标是在内容生产、性能与部署成本之间保持长期平衡。",[10,20,21],{},"因此技术选型需要满足：",[23,24,25,29,32],"ul",{},[26,27,28],"li",{},"长期可维护",[26,30,31],{},"内容优先",[26,33,34],{},"极低运维成本",[10,36,37],{},"所以技术选型并不是追求“最新技术”，\n而是追求“对当前项目总体维护成本最低的方案”。",[39,40,42],"h2",{"id":41},"技术选型概览","🧭 技术选型概览",[10,44,45],{},"本博客的核心技术栈：",[23,47,48,51,54,57,60,63,66],{},[26,49,50],{},"Framework：Nuxt 4",[26,52,53],{},"Content：@nuxt/content",[26,55,56],{},"UI：@nuxt/ui + Tailwind",[26,58,59],{},"i18n：@nuxtjs/i18n",[26,61,62],{},"Image：@nuxt/image + build metadata",[26,64,65],{},"Deploy & Hosting：Vercel",[26,67,68],{},"Analytics：Google Analytics (GA4)",[39,70,72],{"id":71},"为什么是-nuxt-4而不是纯-vue-自拼方案","🚀 为什么是 Nuxt 4，而不是纯 Vue + 自拼方案",[10,74,75],{},"相比纯静态生成（SSG），\nSSR 在内容持续更新的 developer blog 中，\n能够提供更稳定的首屏性能与 SEO 表现。",[10,77,78],{},"如果从项目启动阶段做技术决策，Nuxt 4 对我最有吸引力的点有三条：",[23,80,81,84,87],{},[26,82,83],{},"天然 SSR，博客类页面首屏性能与 SEO 表现更稳定",[26,85,86],{},"路由、数据获取、布局体系是约定式，减少样板代码",[26,88,89],{},"与内容系统、图片系统、i18n 生态衔接非常顺",[10,91,92],{},"如果换成纯 Vue 自己拼 SSR、路由分层、内容查询，会把大量精力浪费在“基础设施”而不是内容本身。",[39,94,96],{"id":95},"内容层选-nuxtcontent","📝 内容层选 @nuxt/content",[10,98,99],{},"博客的本质是“内容产品”，所以我更看重：",[23,101,102,105,108],{},[26,103,104],{},"Markdown 写作体验是否顺畅",[26,106,107],{},"内容是否可被 Git 追踪",[26,109,110],{},"多语言内容是否好管理",[10,112,113,117],{},[114,115,116],"code",{},"@nuxt/content"," 正好匹配：",[23,119,120,131,138],{},[26,121,122,123,126,127,130],{},"内容直接落在 ",[114,124,125],{},"content/"," 目录（如 ",[114,128,129],{},"content/blog/zh","）",[26,132,133,134,137],{},"通过 ",[114,135,136],{},"queryCollection(\"blog\")"," 查询，和页面层衔接自然",[26,139,140,141,144],{},"结合 ",[114,142,143],{},"content.config.ts"," 能做基础字段约束，降低内容结构不一致带来的风险",[10,146,147],{},"它的价值不是“功能多”，而是让写作流和开发流统一在同一套仓库里。",[10,149,150],{},"这种方案本质上更接近 static-first architecture，\n避免引入 Headless CMS（如 Strapi、Contentful）带来的额外运行成本，\n更适合个人长期写作项目。",[39,152,154],{"id":153},"ui-层用-nuxtui-tailwind-4","🎨 UI 层用 @nuxt/ui + Tailwind 4",[10,156,157],{},"我对 UI 层的诉求是：",[23,159,160,163],{},[26,161,162],{},"先保证一致性，再做个性化",[26,164,165],{},"能快速出页面，同时保留改造空间",[10,167,168,171,172,175,176,175,179,182,183,186],{},[114,169,170],{},"@nuxt/ui"," 负责提供高质量基础组件（如 ",[114,173,174],{},"UPageHero","、",[114,177,178],{},"UBlogPost",[114,180,181],{},"UCard","），\n",[114,184,185],{},"Tailwind 4"," 负责定制和微调。",[10,188,189],{},"我希望采用一个长期可维护的模式：",[23,191,192,199],{},[26,193,194,195,198],{},"在 ",[114,196,197],{},"app.config.ts"," 统一主题色、组件 slots",[26,200,194,201,204],{},[114,202,203],{},"app/assets/css/main.css"," 用变量和渐变做品牌层样式",[10,206,207],{},"这样做的好处是：既不会陷入“从零造轮子”，也避免被组件库默认设计语言强绑定。",[39,209,211],{"id":210},"将-i18n-作为架构层的一等能力","🌍 将 i18n 作为架构层的一等能力",[10,213,214,215,218],{},"这个博客一开始就按多语言站点设计，目标是支持 ",[114,216,217],{},"zh / ja / en"," 三套内容。",[10,220,221,224],{},[114,222,223],{},"@nuxtjs/i18n"," 在这里的价值很直接：",[23,226,227,230,237],{},[26,228,229],{},"语言资源与页面路由同构",[26,231,232,233,236],{},"列表查询时可直接按 ",[114,234,235],{},"lang"," 过滤",[26,238,239],{},"页面文本和文章内容可以分别演进",[10,241,242,243,246],{},"通过按 ",[114,244,245],{},"locale"," 加载对应语言的文章内容，可以避免后期补多语言时的大规模重构。",[39,248,250],{"id":249},"nuxtimage-自定义图片元数据脚本","🖼 @nuxt/image + 自定义图片元数据脚本",[10,252,253],{},"图片是博客性能的高风险区。",[10,255,256],{},"我会采用两层策略：",[23,258,259,265],{},[26,260,261,262],{},"展示层使用 ",[114,263,264],{},"@nuxt/image",[26,266,267,268,271],{},"构建层用 ",[114,269,270],{},"scripts/gen-image-meta.mjs"," 扫描内容并生成宽高元数据",[10,273,274],{},"第二层很关键：提前写入尺寸信息可以有效减少布局抖动（Cumulative Layout Shift, CLS），移动端阅读体验会更加稳定。",[10,276,277],{},"也就是说，选型不是“只做压缩”，而是把图片问题前置到构建阶段解决。",[10,279,280],{},"博客性能问题往往不是运行时问题，\n而是构建阶段没有提前处理的问题。",[39,282,284],{"id":283},"️-部署在-vercel","☁️ 部署在 Vercel",[10,286,287],{},"我选择 Vercel 的考虑是：",[23,289,290,293,296],{},[26,291,292],{},"对 Nuxt 项目部署流程友好，发布路径短",[26,294,295],{},"与前端项目协作简单，适合个人站持续迭代",[26,297,298],{},"配置环境变量与预览环境方便，便于快速验证改动",[10,300,301],{},"这类内容站的核心是“稳定上线 + 低维护成本”，Vercel 在这点上更匹配我的目标。",[39,303,305],{"id":304},"google-analytics-ga4","📊 Google Analytics (GA4)",[10,307,308],{},"内容站的持续优化离不开反馈闭环。\n在数据分析工具上，我选择的是 Google Analytics (GA4)，而不是依赖部署平台自带的统计工具。\n原因主要有三个：",[23,310,311,314,317],{},[26,312,313],{},"GA4 在内容站分析方面更成熟，数据维度更完整",[26,315,316],{},"可以长期保存访问数据，适合持续观察内容趋势",[26,318,319],{},"与平台解耦，不依赖特定部署服务",[10,321,322],{},"对于个人博客来说，分析需求其实非常简单：",[23,324,325,328,331],{},[26,326,327],{},"哪些文章被持续访问",[26,329,330],{},"用户主要来自哪些国家或地区",[26,332,333],{},"用户更关注哪些主题内容",[10,335,336],{},"GA4 的事件模型虽然比传统统计更复杂，但在博客场景中，只需要最基础的页面访问统计即可满足需求。",[10,338,339,340,343],{},"在 Nuxt 项目中接入 GA4 也比较简单，可以通过 ",[114,341,342],{},"nuxt-gtag"," 模块在应用层统一注入。",[345,346,351],"pre",{"className":347,"code":348,"language":349,"meta":350,"style":350},"language-ts shiki shiki-themes github-light github-light github-dark","// nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: [\"nuxt-gtag\"],\n  gtag: {\n    id: \"G-XXXXXXXXXX\",\n  },\n});\n","ts","",[114,352,353,362,380,393,399,411,417],{"__ignoreMap":350},[354,355,358],"span",{"class":356,"line":357},"line",1,[354,359,361],{"class":360},"sCsY4","// nuxt.config.ts\n",[354,363,365,369,372,376],{"class":356,"line":364},2,[354,366,368],{"class":367},"s8jYJ","export",[354,370,371],{"class":367}," default",[354,373,375],{"class":374},"snPdu"," defineNuxtConfig",[354,377,379],{"class":378},"sxrX7","({\n",[354,381,383,386,390],{"class":356,"line":382},3,[354,384,385],{"class":378},"  modules: [",[354,387,389],{"class":388},"sIIMD","\"nuxt-gtag\"",[354,391,392],{"class":378},"],\n",[354,394,396],{"class":356,"line":395},4,[354,397,398],{"class":378},"  gtag: {\n",[354,400,402,405,408],{"class":356,"line":401},5,[354,403,404],{"class":378},"    id: ",[354,406,407],{"class":388},"\"G-XXXXXXXXXX\"",[354,409,410],{"class":378},",\n",[354,412,414],{"class":356,"line":413},6,[354,415,416],{"class":378},"  },\n",[354,418,420],{"class":356,"line":419},7,[354,421,422],{"class":378},"});\n",[39,424,426],{"id":425},"️-这套选型的边界与代价","⚖️ 这套选型的边界与代价",[10,428,429],{},"技术选型本质上是权衡，而不是寻找“最优解”。这套也一样：",[23,431,432,435],{},[26,433,434],{},"模块化程度高，升级依赖时要关注兼容性",[26,436,437],{},"多语言内容会增加维护量",[10,439,440],{},"但在“个人博客 + 可持续迭代”这个目标下，这些代价是可接受的。",[39,442,444],{"id":443},"结论","✅ 结论",[10,446,447],{},"如果从开发起点看，这套选型的核心逻辑是：",[23,449,450,453,456,459],{},[26,451,452],{},"用 Nuxt 4 统一 SSR 与工程骨架",[26,454,455],{},"用 @nuxt/content 保持写作与版本管理的一致性",[26,457,458],{},"用 @nuxt/ui + Tailwind 平衡效率和定制",[26,460,461],{},"用 i18n、Image、Vercel、Analytics 分别解决可扩展性、性能、部署和数据反馈",[10,463,464],{},"从整体来看，这套技术选型更像是一种\ndeveloper blog architecture 的实践，\n目标并不是追求技术复杂度或先进性，\n而是构建一套能够支撑内容长期稳定产生的系统。",[466,467,468],"style",{},"html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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":350,"searchDepth":364,"depth":364,"links":470},[471,472,473,474,475,476,477,478,479,480],{"id":41,"depth":364,"text":42},{"id":71,"depth":364,"text":72},{"id":95,"depth":364,"text":96},{"id":153,"depth":364,"text":154},{"id":210,"depth":364,"text":211},{"id":249,"depth":364,"text":250},{"id":283,"depth":364,"text":284},{"id":304,"depth":364,"text":305},{"id":425,"depth":364,"text":426},{"id":443,"depth":364,"text":444},"architecture","2025-12-14T00:00:00.000Z","从项目启动前的视角梳理本博客的技术选型逻辑：\n为什么使用 Nuxt 4 构建 SSR 博客，\n为什么选择 @nuxt/content 作为内容层，\n以及在多语言、图片优化、部署与数据分析上的权衡。",false,"md","zh",{"tags":488},[489,490,491,492],"nuxt","nuxt-content","i18n","frontend-architecture",true,"/blog/zh/2025-12-14-nuxt-blog-architecture",{"title":5,"description":483},{"loc":494},null,"blog/zh/2025-12-14-nuxt-blog-architecture","hS1nbnCPDV7ZEBGZ4kSL1VwKRFk0HCNIMAHaaiZLGa0",1784966031977]