[{"data":1,"prerenderedAt":416},["ShallowReactive",2],{"blog-en-2025-12-14-nuxt-blog-architecture":3},{"id":4,"title":5,"body":6,"category":397,"date":398,"description":399,"draft":400,"extension":401,"lang":402,"meta":403,"navigation":409,"path":410,"seo":411,"sitemap":412,"slug":413,"stem":414,"updated":398,"__hash__":415},"blog/blog/en/2025-12-14-nuxt-blog-architecture.md","Nuxt 4 + @nuxt/content + Vercel: Technical Stack and Architecture Decisions for a Personal Blog",{"type":7,"value":8,"toc":385},"minimark",[9,13,16,29,32,37,40,63,67,70,73,84,87,91,94,97,108,111,122,125,129,132,140,143,146,154,158,161,164,175,179,182,185,193,196,199,203,206,217,221,224,227,238,241,252,255,263,342,346,354,357,361,364,378,381],[10,11,12],"p",{},"This article is not a tutorial, but a record of technical decisions made\nat the beginning of the project.\nThe goal of this blog is not to showcase UI experiments or practice frameworks,\nbut to serve as a long-term platform for writing and technical accumulation.\nFrom an architectural perspective, this blog represents a typical content-driven\nsite, whose objective is to maintain a long-term balance between content production,\nperformance, and deployment cost.",[10,14,15],{},"Therefore, the technical stack must satisfy:",[17,18,19,23,26],"ul",{},[20,21,22],"li",{},"Long-term maintainability",[20,24,25],{},"Content-first workflow",[20,27,28],{},"Minimal operational overhead",[10,30,31],{},"The goal is not to pursue the newest technology,\nbut to minimize the overall maintenance cost of the project.",[33,34,36],"h2",{"id":35},"technology-overview","🧭 Technology Overview",[10,38,39],{},"Core stack:",[17,41,42,45,48,51,54,57,60],{},[20,43,44],{},"Framework: Nuxt 4",[20,46,47],{},"Content: @nuxt/content",[20,49,50],{},"UI: @nuxt/ui + Tailwind",[20,52,53],{},"i18n: @nuxtjs/i18n",[20,55,56],{},"Image: @nuxt/image + build metadata",[20,58,59],{},"Deploy: Vercel",[20,61,62],{},"Analytics：Google Analytics (GA4)",[33,64,66],{"id":65},"why-nuxt-4-instead-of-pure-vue","🚀 Why Nuxt 4 Instead of Pure Vue",[10,68,69],{},"Compared to static site generation (SSG),\nSSR provides more stable first-load performance and SEO results\nfor continuously updated developer blogs.",[10,71,72],{},"Key advantages:",[17,74,75,78,81],{},[20,76,77],{},"Built-in SSR with strong SEO characteristics",[20,79,80],{},"Convention-based routing and layouts",[20,82,83],{},"Smooth integration with content, image, and i18n ecosystems",[10,85,86],{},"Building these manually with Vue would shift effort toward infrastructure\ninstead of content.",[33,88,90],{"id":89},"choosing-nuxtcontent","📝 Choosing @nuxt/content",[10,92,93],{},"A blog is fundamentally a content product.",[10,95,96],{},"Important considerations:",[17,98,99,102,105],{},[20,100,101],{},"Markdown writing experience",[20,103,104],{},"Git-based version tracking",[20,106,107],{},"Multilingual content organization",[10,109,110],{},"@nuxt/content enables:",[17,112,113,116,119],{},[20,114,115],{},"Content stored directly in the repository",[20,117,118],{},"Natural querying via queryCollection()",[20,120,121],{},"Schema validation through content.config.ts",[10,123,124],{},"This approach aligns with a static-first architecture\nand avoids the operational cost of Headless CMS platforms\nsuch as Strapi or Contentful.",[33,126,128],{"id":127},"ui-layer-nuxtui-tailwind","🎨 UI Layer: @nuxt/ui + Tailwind",[10,130,131],{},"Goals:",[17,133,134,137],{},[20,135,136],{},"Consistency first",[20,138,139],{},"Customization later",[10,141,142],{},"@nuxt/ui provides structured components,\nwhile Tailwind handles visual customization.",[10,144,145],{},"A maintainable approach:",[17,147,148,151],{},[20,149,150],{},"Centralized theme configuration",[20,152,153],{},"Brand-level styling via CSS variables",[33,155,157],{"id":156},"i18n-as-a-first-class-architectural-capability","🌍 i18n as a First-Class Architectural Capability",[10,159,160],{},"The blog is designed as a multilingual site from day one\nsupporting zh / ja / en.",[10,162,163],{},"Benefits:",[17,165,166,169,172],{},[20,167,168],{},"Route and language alignment",[20,170,171],{},"Locale-based content filtering",[20,173,174],{},"Independent evolution of UI text and articles",[33,176,178],{"id":177},"image-optimization-strategy","🖼 Image Optimization Strategy",[10,180,181],{},"Images are a major performance risk.",[10,183,184],{},"Two-layer approach:",[17,186,187,190],{},[20,188,189],{},"Runtime optimization via @nuxt/image",[20,191,192],{},"Build-time metadata generation",[10,194,195],{},"Predefined dimensions effectively reduce\nCumulative Layout Shift (CLS).",[10,197,198],{},"Blog performance issues are often build-time problems,\nnot runtime problems.",[33,200,202],{"id":201},"️-deployment-with-vercel","☁️ Deployment with Vercel",[10,204,205],{},"Reasons:",[17,207,208,211,214],{},[20,209,210],{},"Seamless Nuxt deployment",[20,212,213],{},"Fast preview environments",[20,215,216],{},"Low operational overhead",[33,218,220],{"id":219},"analyticsgoogle-analytics-ga4","📊 Analytics：Google Analytics (GA4)",[10,222,223],{},"Continuous optimization of a content-driven site relies on a feedback loop.\nFor analytics, I chose Google Analytics (GA4) instead of relying on the built-in analytics provided by the hosting platform.",[10,225,226],{},"There are three main reasons:",[17,228,229,232,235],{},[20,230,231],{},"GA4 provides a more mature analytics system for content-driven sites, with richer data dimensions.",[20,233,234],{},"It allows long-term data retention, which is important for observing content trends over time.",[20,236,237],{},"It is decoupled from the hosting platform and does not depend on a specific deployment service.",[10,239,240],{},"For a personal blog, the analytics requirements are actually quite simple:",[17,242,243,246,249],{},[20,244,245],{},"Which articles receive consistent traffic",[20,247,248],{},"Which countries or regions users mainly come from",[20,250,251],{},"What kinds of topics users tend to focus on",[10,253,254],{},"Although GA4’s event-based data model is more complex than traditional analytics systems, in a blog scenario basic page view tracking is usually sufficient.",[10,256,257,258,262],{},"Integrating GA4 into a Nuxt project is also straightforward.\nIt can be injected globally at the application level using the ",[259,260,261],"code",{},"nuxt-gtag"," module.",[264,265,270],"pre",{"className":266,"code":267,"language":268,"meta":269,"style":269},"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","",[259,271,272,281,299,312,318,330,336],{"__ignoreMap":269},[273,274,277],"span",{"class":275,"line":276},"line",1,[273,278,280],{"class":279},"sCsY4","// nuxt.config.ts\n",[273,282,284,288,291,295],{"class":275,"line":283},2,[273,285,287],{"class":286},"s8jYJ","export",[273,289,290],{"class":286}," default",[273,292,294],{"class":293},"snPdu"," defineNuxtConfig",[273,296,298],{"class":297},"sxrX7","({\n",[273,300,302,305,309],{"class":275,"line":301},3,[273,303,304],{"class":297},"  modules: [",[273,306,308],{"class":307},"sIIMD","\"nuxt-gtag\"",[273,310,311],{"class":297},"],\n",[273,313,315],{"class":275,"line":314},4,[273,316,317],{"class":297},"  gtag: {\n",[273,319,321,324,327],{"class":275,"line":320},5,[273,322,323],{"class":297},"    id: ",[273,325,326],{"class":307},"\"G-XXXXXXXXXX\"",[273,328,329],{"class":297},",\n",[273,331,333],{"class":275,"line":332},6,[273,334,335],{"class":297},"  },\n",[273,337,339],{"class":275,"line":338},7,[273,340,341],{"class":297},"});\n",[33,343,345],{"id":344},"️-trade-offs","⚖️ Trade-offs",[17,347,348,351],{},[20,349,350],{},"Dependency upgrades require attention",[20,352,353],{},"Multilingual content increases workload",[10,355,356],{},"These costs are acceptable for sustainable iteration.",[33,358,360],{"id":359},"conclusion","✅ Conclusion",[10,362,363],{},"The core logic:",[17,365,366,369,372,375],{},[20,367,368],{},"Nuxt 4 provides the engineering foundation",[20,370,371],{},"@nuxt/content unifies writing and versioning",[20,373,374],{},"@nuxt/ui + Tailwind balance speed and flexibility",[20,376,377],{},"i18n, Image, Vercel, and Analytics support scalability",[10,379,380],{},"This represents a practical developer blog architecture,\nfocused not on technical complexity,\nbut on enabling long-term content creation.",[382,383,384],"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":269,"searchDepth":283,"depth":283,"links":386},[387,388,389,390,391,392,393,394,395,396],{"id":35,"depth":283,"text":36},{"id":65,"depth":283,"text":66},{"id":89,"depth":283,"text":90},{"id":127,"depth":283,"text":128},{"id":156,"depth":283,"text":157},{"id":177,"depth":283,"text":178},{"id":201,"depth":283,"text":202},{"id":219,"depth":283,"text":220},{"id":344,"depth":283,"text":345},{"id":359,"depth":283,"text":360},"architecture","2025-12-14T00:00:00.000Z","An architectural perspective on the technical decisions behind this blog:\nwhy Nuxt 4 is used for an SSR developer blog,\nwhy @nuxt/content serves as the content layer,\nand the trade-offs around multilingual support, image optimization,\ndeployment, and analytics.",false,"md","en",{"tags":404},[405,406,407,408],"nuxt","nuxt-content","i18n","frontend-architecture",true,"/blog/en/2025-12-14-nuxt-blog-architecture",{"title":5,"description":399},{"loc":410},null,"blog/en/2025-12-14-nuxt-blog-architecture","SkaYhDhAi-juNkDFWzgVrdA0ZC2duqiUTuo5U-2Tu6U",1784966030444]