[{"data":1,"prerenderedAt":803},["ShallowReactive",2],{"blog-index":3},[4,110],{"id":5,"title":6,"author":7,"body":8,"date":95,"description":96,"extension":97,"image":98,"meta":99,"navigation":100,"path":101,"readingTime":102,"seo":103,"stem":104,"tags":105,"__hash__":109},"blog\u002Fblog\u002Fhello.md","Welcome to the LJNext Blog","LJ Next",{"type":9,"value":10,"toc":88},"minimark",[11,15,19,24,40,44,77,81,84],[12,13,6],"h1",{"id":14},"welcome-to-the-ljnext-blog",[16,17,18],"p",{},"We are starting this blog to share project updates, learnings, and practical tips from our day-to-day work. Expect short, actionable posts focused on the tools we use and the problems we solve.",[20,21,23],"h2",{"id":22},"what-you-will-find-here","What you will find here",[25,26,27,31,34,37],"ul",{},[28,29,30],"li",{},"Product and feature updates",[28,32,33],{},"Behind-the-scenes build notes",[28,35,36],{},"Design and UX ideas",[28,38,39],{},"Small experiments and results",[20,41,43],{"id":42},"a-tiny-example","A tiny example",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-ts shiki shiki-themes github-light github-dark","console.log(\"Hello, blog!\");\n","ts","",[52,53,54],"code",{"__ignoreMap":50},[55,56,59,63,67,70,74],"span",{"class":57,"line":58},"line",1,[55,60,62],{"class":61},"sVt8B","console.",[55,64,66],{"class":65},"sScJk","log",[55,68,69],{"class":61},"(",[55,71,73],{"class":72},"sZZnC","\"Hello, blog!\"",[55,75,76],{"class":61},");\n",[20,78,80],{"id":79},"next-up","Next up",[16,82,83],{},"We are preparing a deeper dive into our new homepage, including the decisions behind performance, accessibility, and content structure. Stay tuned.",[85,86,87],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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":50,"searchDepth":89,"depth":90,"links":91},2,3,[92,93,94],{"id":22,"depth":89,"text":23},{"id":42,"depth":89,"text":43},{"id":79,"depth":89,"text":80},"2026-02-23","Why we are launching this blog and what you can expect going forward.","md","\u002Ficon.png",{},true,"\u002Fblog\u002Fhello","2 min read",{"title":6,"description":96},"blog\u002Fhello",[106,107,108],"company","nuxt","content","GCRWYj-pxyNCH91YY8RIdeu0MW5u2TOMRW9GUgER5PM",{"id":111,"title":112,"author":7,"body":113,"date":95,"description":791,"extension":97,"image":792,"meta":793,"navigation":100,"path":794,"readingTime":795,"seo":796,"stem":797,"tags":798,"__hash__":802},"blog\u002Fblog\u002Fwhy-we-built-our-own-cms.md","Why We Built Our Own CMS Architecture Instead of Using WordPress",{"type":9,"value":114,"toc":780},[115,118,121,124,128,131,158,161,165,168,200,206,379,382,386,389,415,418,422,425,455,578,585,589,592,607,611,614,702,705,709,712,726,729,733,736,763,765,768,777],[12,116,112],{"id":117},"why-we-built-our-own-cms-architecture-instead-of-using-wordpress",[16,119,120],{},"When we started building the new LJ Next site, the first question everyone asked was: \"Why not just use WordPress?\" It powers over 40% of the web, has thousands of plugins, and every hosting provider supports it out of the box. Fair question.",[16,122,123],{},"Here is why we went a different route — and what we gained from it.",[20,125,127],{"id":126},"the-problem-with-wordpress-for-our-use-case","The problem with WordPress for our use case",[16,129,130],{},"WordPress is a fantastic tool for many projects. But for a performance-focused web studio building custom client work, it introduced friction at every level:",[25,132,133,140,146,152],{},[28,134,135,139],{},[136,137,138],"strong",{},"Plugin dependency hell."," Need a contact form? Plugin. SEO? Plugin. Caching? Plugin. Each one adds weight, potential conflicts, and security surface area.",[28,141,142,145],{},[136,143,144],{},"Opaque rendering pipeline."," PHP templating with layers of hooks and filters makes it hard to reason about what actually gets sent to the browser.",[28,147,148,151],{},[136,149,150],{},"Database overhead."," Every page load hits MySQL, even for content that rarely changes. You end up needing caching layers on top of caching layers.",[28,153,154,157],{},[136,155,156],{},"Hosting constraints."," WordPress needs PHP and MySQL, which means either shared hosting (slow) or managed WordPress hosting (expensive for what you get).",[16,159,160],{},"We wanted something leaner, something where the architecture itself solves the problems that WordPress solves with plugins.",[20,162,164],{"id":163},"what-we-chose-instead","What we chose instead",[16,166,167],{},"Our stack is straightforward:",[25,169,170,176,182,188,194],{},[28,171,172,175],{},[136,173,174],{},"Nuxt 3"," as the application framework",[28,177,178,181],{},[136,179,180],{},"Nuxt Content"," as the file-based CMS",[28,183,184,187],{},[136,185,186],{},"Cloudflare Pages"," for hosting and edge delivery",[28,189,190,193],{},[136,191,192],{},"Tailwind CSS"," for styling",[28,195,196,199],{},[136,197,198],{},"TypeScript"," everywhere",[16,201,202,203,205],{},"The key piece is ",[136,204,180],{},". It turns Markdown files into a queryable content layer with zero database overhead. Content lives in the repository, gets version-controlled with Git, and renders at build time into static HTML.",[45,207,209],{"className":47,"code":208,"language":49,"meta":50,"style":50},"\u002F\u002F content.config.ts — defining our blog collection\nexport default defineContentConfig({\n    collections: {\n        blog: defineCollection({\n            type: \"page\",\n            source: \"blog\u002F*.md\",\n            schema: z.object({\n                title: z.string(),\n                description: z.string().optional(),\n                date: z.string(),\n                tags: z.array(z.string()).optional(),\n                author: z.string().optional(),\n            }),\n        }),\n    },\n});\n",[52,210,211,217,232,237,248,260,271,282,294,310,320,341,355,361,367,373],{"__ignoreMap":50},[55,212,213],{"class":57,"line":58},[55,214,216],{"class":215},"sJ8bj","\u002F\u002F content.config.ts — defining our blog collection\n",[55,218,219,223,226,229],{"class":57,"line":89},[55,220,222],{"class":221},"szBVR","export",[55,224,225],{"class":221}," default",[55,227,228],{"class":65}," defineContentConfig",[55,230,231],{"class":61},"({\n",[55,233,234],{"class":57,"line":90},[55,235,236],{"class":61},"    collections: {\n",[55,238,240,243,246],{"class":57,"line":239},4,[55,241,242],{"class":61},"        blog: ",[55,244,245],{"class":65},"defineCollection",[55,247,231],{"class":61},[55,249,251,254,257],{"class":57,"line":250},5,[55,252,253],{"class":61},"            type: ",[55,255,256],{"class":72},"\"page\"",[55,258,259],{"class":61},",\n",[55,261,263,266,269],{"class":57,"line":262},6,[55,264,265],{"class":61},"            source: ",[55,267,268],{"class":72},"\"blog\u002F*.md\"",[55,270,259],{"class":61},[55,272,274,277,280],{"class":57,"line":273},7,[55,275,276],{"class":61},"            schema: z.",[55,278,279],{"class":65},"object",[55,281,231],{"class":61},[55,283,285,288,291],{"class":57,"line":284},8,[55,286,287],{"class":61},"                title: z.",[55,289,290],{"class":65},"string",[55,292,293],{"class":61},"(),\n",[55,295,297,300,302,305,308],{"class":57,"line":296},9,[55,298,299],{"class":61},"                description: z.",[55,301,290],{"class":65},[55,303,304],{"class":61},"().",[55,306,307],{"class":65},"optional",[55,309,293],{"class":61},[55,311,313,316,318],{"class":57,"line":312},10,[55,314,315],{"class":61},"                date: z.",[55,317,290],{"class":65},[55,319,293],{"class":61},[55,321,323,326,329,332,334,337,339],{"class":57,"line":322},11,[55,324,325],{"class":61},"                tags: z.",[55,327,328],{"class":65},"array",[55,330,331],{"class":61},"(z.",[55,333,290],{"class":65},[55,335,336],{"class":61},"()).",[55,338,307],{"class":65},[55,340,293],{"class":61},[55,342,344,347,349,351,353],{"class":57,"line":343},12,[55,345,346],{"class":61},"                author: z.",[55,348,290],{"class":65},[55,350,304],{"class":61},[55,352,307],{"class":65},[55,354,293],{"class":61},[55,356,358],{"class":57,"line":357},13,[55,359,360],{"class":61},"            }),\n",[55,362,364],{"class":57,"line":363},14,[55,365,366],{"class":61},"        }),\n",[55,368,370],{"class":57,"line":369},15,[55,371,372],{"class":61},"    },\n",[55,374,376],{"class":57,"line":375},16,[55,377,378],{"class":61},"});\n",[16,380,381],{},"That is the entire CMS configuration. No admin panel to maintain, no database to back up, no plugin updates to worry about.",[20,383,385],{"id":384},"performance-not-even-close","Performance: not even close",[16,387,388],{},"WordPress with a caching plugin and CDN can get decent Lighthouse scores. But it takes effort and ongoing tuning. Our approach is fast by default:",[25,390,391,397,403,409],{},[28,392,393,396],{},[136,394,395],{},"Static generation"," means every page is pre-rendered HTML. Time to first byte is the time it takes Cloudflare's edge to serve a file — typically under 20ms.",[28,398,399,402],{},[136,400,401],{},"No runtime PHP."," The server does not execute code on each request. It serves files.",[28,404,405,408],{},[136,406,407],{},"Minimal JavaScript."," Nuxt's hybrid rendering only hydrates what needs interactivity. Blog posts ship almost zero client-side JS.",[28,410,411,414],{},[136,412,413],{},"Asset optimization"," is handled at build time, not by a plugin that runs on every request.",[16,416,417],{},"A typical blog post on our site loads in under 500ms on a 3G connection. Try that with a stock WordPress install.",[20,419,421],{"id":420},"developer-experience-matters","Developer experience matters",[16,423,424],{},"This is where the gap is widest. Working on our site feels like working on a modern application, not fighting a legacy system:",[25,426,427,433,439,445],{},[28,428,429,432],{},[136,430,431],{},"Type safety."," Every content field is validated by Zod schemas. If a blog post is missing a required field, the build fails with a clear error.",[28,434,435,438],{},[136,436,437],{},"Component-driven."," Blog cards, headers, filters — they are all Vue components. Reusable, testable, easy to reason about.",[28,440,441,444],{},[136,442,443],{},"Git-based workflow."," Content changes go through pull requests. We can review, diff, and roll back any change.",[28,446,447,450,451,454],{},[136,448,449],{},"Local development"," is instant. ",[52,452,453],{},"nuxt dev"," starts in seconds, hot-reloads everything, and the content preview updates in real time.",[45,456,460],{"className":457,"code":458,"language":459,"meta":50,"style":50},"language-vue shiki shiki-themes github-light github-dark","\u003C!-- Querying blog posts is just a function call -->\n\u003Cscript setup lang=\"ts\">\nconst { data: posts } = await useAsyncData(\"blog\", () =>\n    queryCollection(\"blog\").order(\"date\", \"DESC\").all(),\n);\n\u003C\u002Fscript>\n","vue",[52,461,462,467,491,532,565,569],{"__ignoreMap":50},[55,463,464],{"class":57,"line":58},[55,465,466],{"class":215},"\u003C!-- Querying blog posts is just a function call -->\n",[55,468,469,472,476,479,482,485,488],{"class":57,"line":89},[55,470,471],{"class":61},"\u003C",[55,473,475],{"class":474},"s9eBZ","script",[55,477,478],{"class":65}," setup",[55,480,481],{"class":65}," lang",[55,483,484],{"class":61},"=",[55,486,487],{"class":72},"\"ts\"",[55,489,490],{"class":61},">\n",[55,492,493,496,499,503,506,510,513,515,518,521,523,526,529],{"class":57,"line":90},[55,494,495],{"class":221},"const",[55,497,498],{"class":61}," { ",[55,500,502],{"class":501},"s4XuR","data",[55,504,505],{"class":61},": ",[55,507,509],{"class":508},"sj4cs","posts",[55,511,512],{"class":61}," } ",[55,514,484],{"class":221},[55,516,517],{"class":221}," await",[55,519,520],{"class":65}," useAsyncData",[55,522,69],{"class":61},[55,524,525],{"class":72},"\"blog\"",[55,527,528],{"class":61},", () ",[55,530,531],{"class":221},"=>\n",[55,533,534,537,539,541,544,547,549,552,555,558,560,563],{"class":57,"line":239},[55,535,536],{"class":65},"    queryCollection",[55,538,69],{"class":61},[55,540,525],{"class":72},[55,542,543],{"class":61},").",[55,545,546],{"class":65},"order",[55,548,69],{"class":61},[55,550,551],{"class":72},"\"date\"",[55,553,554],{"class":61},", ",[55,556,557],{"class":72},"\"DESC\"",[55,559,543],{"class":61},[55,561,562],{"class":65},"all",[55,564,293],{"class":61},[55,566,567],{"class":57,"line":250},[55,568,76],{"class":61},[55,570,571,574,576],{"class":57,"line":262},[55,572,573],{"class":61},"\u003C\u002F",[55,575,475],{"class":474},[55,577,490],{"class":61},[16,579,580,581,584],{},"Compare that to writing a ",[52,582,583],{},"WP_Query"," with meta queries and custom taxonomies. The cognitive overhead is not comparable.",[20,586,588],{"id":587},"what-about-non-technical-editors","What about non-technical editors?",[16,590,591],{},"This is the one area where WordPress genuinely shines. Its admin panel is approachable for non-developers. We solved this differently:",[25,593,594,597,604],{},[28,595,596],{},"For our own content, Markdown in VS Code is faster than any WYSIWYG editor.",[28,598,599,600,603],{},"For client projects that need a visual editor, we use ",[136,601,602],{},"Nuxt Studio"," — a hosted editing layer that connects to the Git repository and provides a visual interface without changing the architecture.",[28,605,606],{},"The content format (Markdown) is portable. If we ever want to switch tools, the content moves with us.",[20,608,610],{"id":609},"the-cost-comparison","The cost comparison",[16,612,613],{},"WordPress is \"free\" until you count the real costs:",[615,616,617,632],"table",{},[618,619,620],"thead",{},[621,622,623,626,629],"tr",{},[624,625],"th",{},[624,627,628],{},"WordPress",[624,630,631],{},"Our Stack",[633,634,635,647,658,669,680,691],"tbody",{},[621,636,637,641,644],{},[638,639,640],"td",{},"Hosting",[638,642,643],{},"$20–50\u002Fmo (managed)",[638,645,646],{},"$0 (Cloudflare free tier)",[621,648,649,652,655],{},[638,650,651],{},"SSL",[638,653,654],{},"Included or plugin",[638,656,657],{},"Included",[621,659,660,663,666],{},[638,661,662],{},"CDN",[638,664,665],{},"Plugin + config",[638,667,668],{},"Built-in (edge network)",[621,670,671,674,677],{},[638,672,673],{},"Backups",[638,675,676],{},"Plugin or host feature",[638,678,679],{},"Git history",[621,681,682,685,688],{},[638,683,684],{},"Security updates",[638,686,687],{},"Constant",[638,689,690],{},"None needed (static)",[621,692,693,696,699],{},[638,694,695],{},"Build time",[638,697,698],{},"N\u002FA (runtime)",[638,700,701],{},"~30s per deploy",[16,703,704],{},"For a studio building multiple sites, the savings compound quickly.",[20,706,708],{"id":707},"when-wordpress-is-still-the-right-choice","When WordPress is still the right choice",[16,710,711],{},"We are not anti-WordPress. It is the right tool when:",[25,713,714,717,720,723],{},[28,715,716],{},"The client's team is non-technical and needs a familiar admin interface",[28,718,719],{},"The project needs e-commerce with WooCommerce",[28,721,722],{},"There is an existing WordPress ecosystem the project must integrate with",[28,724,725],{},"Time-to-launch matters more than long-term architecture",[16,727,728],{},"For everything else — especially performance-critical marketing sites, blogs, and landing pages — a modern static-first architecture wins.",[20,730,732],{"id":731},"what-we-learned","What we learned",[16,734,735],{},"Building our own CMS layer taught us a few things:",[737,738,739,745,751,757],"ol",{},[28,740,741,744],{},[136,742,743],{},"Simpler systems are easier to maintain."," Fewer moving parts means fewer things break.",[28,746,747,750],{},[136,748,749],{},"Performance should be a default, not an optimization."," If your architecture is fast by design, you spend zero time tuning it.",[28,752,753,756],{},[136,754,755],{},"Developer experience drives quality."," When the tooling is good, you ship better work, faster.",[28,758,759,762],{},[136,760,761],{},"Content and presentation should be separate."," Markdown files do not care how they are rendered. We can redesign the entire site without touching a single blog post.",[20,764,80],{"id":79},[16,766,767],{},"In upcoming posts, we will dig into the specifics: how we structured our Nuxt Content collections, the component architecture behind our blog, and how we handle i18n for content that needs to work in both German and English.",[16,769,770,771,776],{},"If you are considering a similar move away from traditional CMS platforms, ",[772,773,775],"a",{"href":774},"\u002Fcontact","reach out",". We are happy to share what we have learned.",[85,778,779],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":50,"searchDepth":89,"depth":90,"links":781},[782,783,784,785,786,787,788,789,790],{"id":126,"depth":89,"text":127},{"id":163,"depth":89,"text":164},{"id":384,"depth":89,"text":385},{"id":420,"depth":89,"text":421},{"id":587,"depth":89,"text":588},{"id":609,"depth":89,"text":610},{"id":707,"depth":89,"text":708},{"id":731,"depth":89,"text":732},{"id":79,"depth":89,"text":80},"How choosing Nuxt Content over WordPress gave us full control, better performance, and a developer experience that actually scales.",null,{},"\u002Fblog\u002Fwhy-we-built-our-own-cms","6 min read",{"title":112,"description":791},"blog\u002Fwhy-we-built-our-own-cms",[799,107,800,801],"architecture","cms","performance","HDqboX15a0qDOYFHgLA0-JssP2EVXp5GS8_w4w18KNI",1781469664027]