[{"data":1,"prerenderedAt":772},["ShallowReactive",2],{"blog-list":3},[4,117,659],{"id":5,"title":6,"body":7,"date":103,"description":104,"draft":105,"extension":106,"meta":107,"navigation":108,"path":109,"readingTime":110,"seo":111,"stem":112,"tags":113,"__hash__":116},"blog\u002Fblog\u002Fi-cook-software.md","I cook software",{"type":8,"value":9,"toc":95},"minimark",[10,14,22,27,30,53,56,60,63,69,72,76,79,82,86,89,92],[11,12,13],"p",{},"I have a confession: I do not really think of myself as a programmer. I think of myself as a cook.",[11,15,16,17,21],{},"A cook gets a raw idea from someone — a craving, a half-sentence, ",[18,19,20],"em",{},"\"could we make it do the thing?\""," — and turns it into something you can put on a table. Programming is just the knife work.",[23,24,26],"h2",{"id":25},"mise-en-place","Mise en place",[11,28,29],{},"Before a single line of code, I like to line up the ingredients:",[31,32,33,41,47],"ul",{},[34,35,36,40],"li",{},[37,38,39],"strong",{},"The idea."," What are we actually making? Say the dish out loud.",[34,42,43,46],{},[37,44,45],{},"The constraints."," Time, budget, team, and the one weird thing that must work.",[34,48,49,52],{},[37,50,51],{},"The taste test."," How will we know it is good? Write it down before you are hungry.",[11,54,55],{},"Skipping this step is how you end up cooking a five-course meal for a party of one.",[23,57,59],{"id":58},"taste-as-you-go","Taste as you go",[11,61,62],{},"You would not cook a curry by throwing everything in and hoping. You taste, you adjust, you taste again. Software is the same:",[64,65,66],"blockquote",{},[11,67,68],{},"Ship small, look at it, fix it, ship it again.",[11,70,71],{},"Small batches are not a compromise. They are how you avoid serving something nobody ordered.",[23,73,75],{"id":74},"never-serve-what-you-would-not-eat","Never serve what you would not eat",[11,77,78],{},"My rule is simple: if I would not use it, I do not ship it. That single question catches more bad product decisions than any planning meeting ever has.",[11,80,81],{},"It is also how I ended up caring so much about the small stuff — the loading state, the empty state, the error nobody thought to write.",[23,83,85],{"id":84},"and-the-bugs","And the bugs",[11,87,88],{},"Every kitchen has pests. In software they are called bugs, and I have a very personal relationship with them: I hunt them, I squash them, and occasionally — you saw the website — I eat them.",[11,90,91],{},"Good software is less about never making a mess and more about cleaning it up fast, with a smile, before anyone notices.",[11,93,94],{},"Now go cook something.",{"title":96,"searchDepth":97,"depth":97,"links":98},"",2,[99,100,101,102],{"id":25,"depth":97,"text":26},{"id":58,"depth":97,"text":59},{"id":74,"depth":97,"text":75},{"id":84,"depth":97,"text":85},"2026-08-28","Why I think of building products as cooking — mise en place, tasting as you go, and never serving something you would not eat yourself.",false,"md",{},true,"\u002Fblog\u002Fi-cook-software","4 min read",{"title":6,"description":104},"blog\u002Fi-cook-software",[114,115],"craft","mindset","LWYRAwTy1loTPKEXaqVdJeuUkt7vybWQw-i83xNVHKE",{"id":118,"title":119,"body":120,"date":647,"description":648,"draft":105,"extension":106,"meta":649,"navigation":108,"path":650,"readingTime":651,"seo":652,"stem":653,"tags":654,"__hash__":658},"blog\u002Fblog\u002Fwhy-nuxt-content.md","Why Nuxt Content for a personal site",{"type":8,"value":121,"toc":641},[122,125,141,151,155,158,216,219,223,230,422,425,429,432,548,551,594,597,601,604,622,629,634,637],[11,123,124],{},"When I rebuilt my personal site I had three requirements:",[126,127,128,131,138],"ol",{},[34,129,130],{},"Writing should feel like writing, not like configuring a CMS.",[34,132,133,134,137],{},"The output should be ",[37,135,136],{},"static"," — fast, cheap, and boring to maintain.",[34,139,140],{},"Types everywhere, because I am that kind of person.",[11,142,143,150],{},[144,145,149],"a",{"href":146,"rel":147},"https:\u002F\u002Fcontent.nuxt.com",[148],"nofollow","Nuxt Content"," hit all three.",[23,152,154],{"id":153},"markdown-lives-in-the-repo","Markdown lives in the repo",[11,156,157],{},"Every post is just a file:",[159,160,163],"pre",{"className":161,"code":162,"language":106,"meta":96,"style":96},"language-md shiki shiki-themes github-dark github-dark","---\ntitle: Why Nuxt Content for a personal site\ndescription: Markdown in a Git repo, typed frontmatter...\ndate: 2026-08-14\ntags: [nuxt, content, cloudflare]\n---\n\nContent goes here.\n",[164,165,166,175,181,187,193,199,204,210],"code",{"__ignoreMap":96},[167,168,171],"span",{"class":169,"line":170},"line",1,[167,172,174],{"class":173},"sNs27","---\n",[167,176,177],{"class":169,"line":97},[167,178,180],{"class":179},"suv1-","title: Why Nuxt Content for a personal site\n",[167,182,184],{"class":169,"line":183},3,[167,185,186],{"class":179},"description: Markdown in a Git repo, typed frontmatter...\n",[167,188,190],{"class":169,"line":189},4,[167,191,192],{"class":179},"date: 2026-08-14\n",[167,194,196],{"class":169,"line":195},5,[167,197,198],{"class":179},"tags: [nuxt, content, cloudflare]\n",[167,200,202],{"class":169,"line":201},6,[167,203,174],{"class":173},[167,205,207],{"class":169,"line":206},7,[167,208,209],{"emptyLinePlaceholder":108},"\n",[167,211,213],{"class":169,"line":212},8,[167,214,215],{"class":179},"Content goes here.\n",[11,217,218],{},"Commit a file, and it is content. No database, no admin panel, no login to forget.",[23,220,222],{"id":221},"typed-frontmatter-with-zod","Typed frontmatter with Zod",[11,224,225,226,229],{},"The part I love most is the schema. In ",[164,227,228],{},"content.config.ts"," I describe exactly what a post looks like:",[159,231,235],{"className":232,"code":233,"language":234,"meta":96,"style":96},"language-ts shiki shiki-themes github-dark github-dark","import { defineContentConfig, defineCollection, z } from '@nuxt\u002Fcontent'\n\nexport default defineContentConfig({\n  collections: {\n    blog: defineCollection({\n      type: 'page',\n      source: 'blog\u002F**\u002F*.md',\n      schema: z.object({\n        title: z.string(),\n        description: z.string(),\n        date: z.date(),\n        tags: z.array(z.string()).default([]),\n        draft: z.boolean().default(false)\n      })\n    })\n  }\n})\n","ts",[164,236,237,253,257,272,277,287,298,308,318,330,340,351,374,398,404,410,416],{"__ignoreMap":96},[167,238,239,243,246,249],{"class":169,"line":170},[167,240,242],{"class":241},"sOPea","import",[167,244,245],{"class":179}," { defineContentConfig, defineCollection, z } ",[167,247,248],{"class":241},"from",[167,250,252],{"class":251},"s4wv1"," '@nuxt\u002Fcontent'\n",[167,254,255],{"class":169,"line":97},[167,256,209],{"emptyLinePlaceholder":108},[167,258,259,262,265,269],{"class":169,"line":183},[167,260,261],{"class":241},"export",[167,263,264],{"class":241}," default",[167,266,268],{"class":267},"sFR8T"," defineContentConfig",[167,270,271],{"class":179},"({\n",[167,273,274],{"class":169,"line":189},[167,275,276],{"class":179},"  collections: {\n",[167,278,279,282,285],{"class":169,"line":195},[167,280,281],{"class":179},"    blog: ",[167,283,284],{"class":267},"defineCollection",[167,286,271],{"class":179},[167,288,289,292,295],{"class":169,"line":201},[167,290,291],{"class":179},"      type: ",[167,293,294],{"class":251},"'page'",[167,296,297],{"class":179},",\n",[167,299,300,303,306],{"class":169,"line":206},[167,301,302],{"class":179},"      source: ",[167,304,305],{"class":251},"'blog\u002F**\u002F*.md'",[167,307,297],{"class":179},[167,309,310,313,316],{"class":169,"line":212},[167,311,312],{"class":179},"      schema: z.",[167,314,315],{"class":267},"object",[167,317,271],{"class":179},[167,319,321,324,327],{"class":169,"line":320},9,[167,322,323],{"class":179},"        title: z.",[167,325,326],{"class":267},"string",[167,328,329],{"class":179},"(),\n",[167,331,333,336,338],{"class":169,"line":332},10,[167,334,335],{"class":179},"        description: z.",[167,337,326],{"class":267},[167,339,329],{"class":179},[167,341,343,346,349],{"class":169,"line":342},11,[167,344,345],{"class":179},"        date: z.",[167,347,348],{"class":267},"date",[167,350,329],{"class":179},[167,352,354,357,360,363,365,368,371],{"class":169,"line":353},12,[167,355,356],{"class":179},"        tags: z.",[167,358,359],{"class":267},"array",[167,361,362],{"class":179},"(z.",[167,364,326],{"class":267},[167,366,367],{"class":179},"()).",[167,369,370],{"class":267},"default",[167,372,373],{"class":179},"([]),\n",[167,375,377,380,383,386,388,391,395],{"class":169,"line":376},13,[167,378,379],{"class":179},"        draft: z.",[167,381,382],{"class":267},"boolean",[167,384,385],{"class":179},"().",[167,387,370],{"class":267},[167,389,390],{"class":179},"(",[167,392,394],{"class":393},"s8ozJ","false",[167,396,397],{"class":179},")\n",[167,399,401],{"class":169,"line":400},14,[167,402,403],{"class":179},"      })\n",[167,405,407],{"class":169,"line":406},15,[167,408,409],{"class":179},"    })\n",[167,411,413],{"class":169,"line":412},16,[167,414,415],{"class":179},"  }\n",[167,417,419],{"class":169,"line":418},17,[167,420,421],{"class":179},"})\n",[11,423,424],{},"Now a typo in a date fails the build instead of shipping silently. That is the deal I want.",[23,426,428],{"id":427},"querying-is-a-one-liner","Querying is a one-liner",[11,430,431],{},"Listing posts, newest first, draft-free:",[159,433,435],{"className":232,"code":434,"language":234,"meta":96,"style":96},"const { data: posts } = await useAsyncData('blog-list', () =>\n  queryCollection('blog')\n    .where('draft', '=', false)\n    .order('date', 'DESC')\n    .all()\n)\n",[164,436,437,478,490,515,534,544],{"__ignoreMap":96},[167,438,439,442,445,449,452,455,458,461,464,467,469,472,475],{"class":169,"line":170},[167,440,441],{"class":241},"const",[167,443,444],{"class":179}," { ",[167,446,448],{"class":447},"s-3mD","data",[167,450,451],{"class":179},": ",[167,453,454],{"class":393},"posts",[167,456,457],{"class":179}," } ",[167,459,460],{"class":241},"=",[167,462,463],{"class":241}," await",[167,465,466],{"class":267}," useAsyncData",[167,468,390],{"class":179},[167,470,471],{"class":251},"'blog-list'",[167,473,474],{"class":179},", () ",[167,476,477],{"class":241},"=>\n",[167,479,480,483,485,488],{"class":169,"line":97},[167,481,482],{"class":267},"  queryCollection",[167,484,390],{"class":179},[167,486,487],{"class":251},"'blog'",[167,489,397],{"class":179},[167,491,492,495,498,500,503,506,509,511,513],{"class":169,"line":183},[167,493,494],{"class":179},"    .",[167,496,497],{"class":267},"where",[167,499,390],{"class":179},[167,501,502],{"class":251},"'draft'",[167,504,505],{"class":179},", ",[167,507,508],{"class":251},"'='",[167,510,505],{"class":179},[167,512,394],{"class":393},[167,514,397],{"class":179},[167,516,517,519,522,524,527,529,532],{"class":169,"line":189},[167,518,494],{"class":179},[167,520,521],{"class":267},"order",[167,523,390],{"class":179},[167,525,526],{"class":251},"'date'",[167,528,505],{"class":179},[167,530,531],{"class":251},"'DESC'",[167,533,397],{"class":179},[167,535,536,538,541],{"class":169,"line":195},[167,537,494],{"class":179},[167,539,540],{"class":267},"all",[167,542,543],{"class":179},"()\n",[167,545,546],{"class":169,"line":201},[167,547,397],{"class":179},[11,549,550],{},"And rendering a post:",[159,552,556],{"className":553,"code":554,"language":555,"meta":96,"style":96},"language-vue shiki shiki-themes github-dark github-dark","\u003CContentRenderer :value=\"post\" class=\"prose\" \u002F>\n","vue",[164,557,558],{"__ignoreMap":96},[167,559,560,563,567,570,573,575,578,581,583,586,588,591],{"class":169,"line":170},[167,561,562],{"class":179},"\u003C",[167,564,566],{"class":565},"sxg3X","ContentRenderer",[167,568,569],{"class":179}," :",[167,571,572],{"class":267},"value",[167,574,460],{"class":179},[167,576,577],{"class":251},"\"",[167,579,580],{"class":179},"post",[167,582,577],{"class":251},[167,584,585],{"class":267}," class",[167,587,460],{"class":179},[167,589,590],{"class":251},"\"prose\"",[167,592,593],{"class":179}," \u002F>\n",[11,595,596],{},"That is the whole blog.",[23,598,600],{"id":599},"static-on-cloudflare-pages","Static on Cloudflare Pages",[11,602,603],{},"Because everything is generated at build time, I do not need a server. The build command is literally:",[159,605,609],{"className":606,"code":607,"language":608,"meta":96,"style":96},"language-bash shiki shiki-themes github-dark github-dark","npm run generate\n","bash",[164,610,611],{"__ignoreMap":96},[167,612,613,616,619],{"class":169,"line":170},[167,614,615],{"class":267},"npm",[167,617,618],{"class":251}," run",[167,620,621],{"class":251}," generate\n",[11,623,624,625,628],{},"The output lands in ",[164,626,627],{},".output\u002Fpublic",", and Cloudflare Pages serves it from the edge. Free tier, instant cache invalidation on deploy, done.",[64,630,631],{},[11,632,633],{},"A personal site should cost you a coffee, not a subscription.",[11,635,636],{},"If you are on the fence: pick the boring static option. Your future self, half-asleep at 2am debugging a cache, will thank you.",[638,639,640],"style",{},"html pre.shiki code .sNs27, html code.shiki .sNs27{--shiki-default:#79B8FF;--shiki-default-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--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);}html pre.shiki code .sOPea, html code.shiki .sOPea{--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .sFR8T, html code.shiki .sFR8T{--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s-3mD, html code.shiki .s-3mD{--shiki-default:#FFAB70;--shiki-dark:#FFAB70}html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}",{"title":96,"searchDepth":97,"depth":97,"links":642},[643,644,645,646],{"id":153,"depth":97,"text":154},{"id":221,"depth":97,"text":222},{"id":427,"depth":97,"text":428},{"id":599,"depth":97,"text":600},"2026-08-14","Markdown in a Git repo, typed frontmatter, and a static build that deploys to Cloudflare Pages for free. Here is the setup I actually use.",{},"\u002Fblog\u002Fwhy-nuxt-content","6 min read",{"title":119,"description":648},"blog\u002Fwhy-nuxt-content",[655,656,657],"nuxt","content","cloudflare","nyXiUzN8c4sTUcWBs2AKXR7MVF73zzdPtBueJAtLubg",{"id":660,"title":661,"body":662,"date":762,"description":763,"draft":105,"extension":106,"meta":764,"navigation":108,"path":765,"readingTime":766,"seo":767,"stem":768,"tags":769,"__hash__":771},"blog\u002Fblog\u002Fi-eat-bugs.md","I eat bugs for breakfast",{"type":8,"value":663,"toc":757},[664,667,672,679,683,688,691,696,699,704,707,712,723,727,730,744,747,751,754],[11,665,666],{},"There is a famous line I keep on my desk:",[64,668,669],{},[11,670,671],{},"Debugging is being the detective in a crime movie where you are also the murderer.",[11,673,674,675,678],{},"Nobody likes discovering a bug. Everybody ",[18,676,677],{},"should"," like fixing one, because it is the closest thing our job has to a puzzle with a guaranteed answer.",[23,680,682],{"id":681},"my-not-so-secret-process","My not-so-secret process",[11,684,685],{},[37,686,687],{},"1. Reproduce it. Every single time.",[11,689,690],{},"If you cannot make it happen on demand, you are not debugging — you are guessing. Get the smallest possible reproduction first.",[11,692,693],{},[37,694,695],{},"2. Read the actual error.",[11,697,698],{},"By which I mean the whole stack trace, not the first line before your eyes glaze over. The answer is almost always down there.",[11,700,701],{},[37,702,703],{},"3. Change one thing at a time.",[11,705,706],{},"If you change three things and it works, you have learned nothing. If you change three things and it still fails, you have three problems and one of them is now hidden.",[11,708,709],{},[37,710,711],{},"4. Assume your code is wrong, not the universe.",[11,713,714,715,718,719,722],{},"The framework probably is not broken. The browser is probably not lying. ",[164,716,717],{},"console.log"," the thing you are ",[18,720,721],{},"sure"," is correct — it is the number one place bugs hide.",[23,724,726],{"id":725},"the-bugs-you-actually-want-to-eat","The bugs you actually want to eat",[11,728,729],{},"Some bugs are gifts. The crash on first load, the missing null check, the off-by-one — those are loud and fixable. The dangerous ones are quiet:",[31,731,732,735,738],{},[34,733,734],{},"The form that silently drops a field",[34,736,737],{},"The empty state nobody designed",[34,739,740,741],{},"The error swallowed by an empty ",[164,742,743],{},"catch",[11,745,746],{},"Those are the bugs that live in production for a year. Hunt them first.",[23,748,750],{"id":749},"a-snack-not-a-meal","A snack, not a meal",[11,752,753],{},"Fixing a bug should leave a small trail behind: a test, a comment, a note in the changelog. Otherwise the same bug walks back in through the front door like it pays rent.",[11,755,756],{},"So yes — I eat bugs. But I leave notes on the plate, so nobody has to serve that dish twice.",{"title":96,"searchDepth":97,"depth":97,"links":758},[759,760,761],{"id":681,"depth":97,"text":682},{"id":725,"depth":97,"text":726},{"id":749,"depth":97,"text":750},"2026-07-30","A short, unserious guide to debugging — the part of the job where you are the detective, the suspect, and the crime scene all at once.",{},"\u002Fblog\u002Fi-eat-bugs","5 min read",{"title":661,"description":763},"blog\u002Fi-eat-bugs",[770,114],"debugging","NQSzYIjH3McwicCmUrWA_NbueSazxb3jcZEIso6OYYQ",1789383482827]