[{"data":1,"prerenderedAt":270},["ShallowReactive",2],{"post-keeping-component-props-simple":3,"blog-posts":14},{"_path":4,"title":5,"description":6,"date":7,"tags":8,"readingTime":12,"body":13},"\u002Fblog\u002Fkeeping-component-props-simple\u002F","Keeping Component Props Simple","When to split a component, when to use slots, and how to avoid prop objects that grow forever.","2026-04-03",[9,10,11],"vue","frontend","best-practices",5,"# Keeping Component Props Simple\n\nProps are a public API. Every new prop is a promise you will support—or a smell that the component is doing too much.\n\n## Favor composition over mega-props\n\nIf a card needs `title`, `subtitle`, `image`, `imageAlt`, `badge`, `badgeColor`, `footerText`, and `footerLink`, the component is guessing every layout variant callers might need. That guesswork turns into boolean flags and optional strings that interact in surprising ways.\n\nSlots move the flexible regions to the caller:\n\n```vue\n\u003CBlogCard :title=\"post.title\" :date=\"post.date\">\n  \u003Ctemplate #footer>\n    \u003CNuxtLink :to=\"post._path\">Read more\u003C\u002FNuxtLink>\n  \u003C\u002Ftemplate>\n\u003C\u002FBlogCard>\n```\n\n```vue\n\u003C!-- BlogCard.vue -->\n\u003Cscript setup>\ndefineProps({\n  title: { type: String, required: true },\n  date: { type: String, required: true },\n});\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Carticle class=\"blog-card\">\n    \u003Ch2>{{ title }}\u003C\u002Fh2>\n    \u003Ctime>{{ date }}\u003C\u002Ftime>\n    \u003Cslot \u002F>\n    \u003Cfooter v-if=\"$slots.footer\">\n      \u003Cslot name=\"footer\" \u002F>\n    \u003C\u002Ffooter>\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n```\n\nThe parent owns markup for the footer; the card owns structure and spacing. When a new footer layout appears—share buttons, a tag list, a “featured” link—you do not add three props. You change the slot content.\n\nI use the same split for headers, media, and actions. Ask: is this a fixed piece of data the card must understand, or a region that should accept arbitrary UI? Titles and dates are data. Badges and footers are often regions.\n\nComposition also means splitting components. A `ProjectCard` and a `BlogCard` that share 30% styling should share a CSS class or a tiny presentational shell—not one `UniversalCard` with a `type` prop and two divergent templates. Duplication of three lines of markup is cheaper than a shared component that needs a wiki to configure.\n\n## Group related config, not unrelated flags\n\nA single `variant` enum (`\"compact\" | \"featured\"`) beats five boolean props that interact in weird ways. Boolean explosions look like this:\n\n```vue\n\u003C!-- Hard to reason about -->\n\u003CCard\n  :compact=\"true\"\n  :featured=\"true\"\n  :show-image=\"false\"\n  :dense=\"true\"\n  :bordered=\"false\"\n\u002F>\n```\n\nWhich combinations are valid? Does `compact` override `dense`? Can `featured` exist without an image? Call sites become experiments.\n\nPrefer a small set of named modes:\n\n```vue\n\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{\n  title: string;\n  variant?: 'default' | 'compact' | 'featured';\n}>();\n\u003C\u002Fscript>\n```\n\nDocument what each variant changes—spacing, typography, whether media is required. When options are truly independent (for example, `disabled` on a button vs `loading`), separate booleans are fine. The smell is independent-looking flags that actually encode one design decision.\n\nWhen a prop object grows (`:options=\"{ a, b, c, d }\"`), ask whether those fields belong together as one concept. A coherent `pagination` object can be clearer than four loose props. An `options` bag that means “everything we did not want to name” is a warning sign—split the component or promote the important fields.\n\n## Document the contract\n\nTypeScript interfaces or JSDoc on props pay off the moment someone else (or future you) reuses the component. Required vs optional should be obvious at the call site.\n\nGood contracts answer:\n\n- What is required to render something useful?\n- What has a sensible default?\n- What emits fire, and with what payload?\n- What slots exist, and when are they optional?\n\n```vue\n\u003Cscript setup lang=\"ts\">\nconst props = withDefaults(\n  defineProps\u003C{\n    title: string;\n    description?: string;\n    to: string;\n    variant?: 'default' | 'compact';\n  }>(),\n  { variant: 'default' },\n);\n\ndefineEmits\u003C{\n  select: [id: string];\n}>();\n\u003C\u002Fscript>\n```\n\nI avoid props that are only there for one parent’s edge case. If a single call site needs a one-off layout, either use a slot, fork a local variant, or pass content as children. Polluting the shared API for one screen locks everyone into that exception.\n\nDefaults deserve care. A default that hides important content (`showTitle: true` with title sometimes empty) creates empty headings. Prefer required props for content that defines the component, and defaults for presentation knobs.\n\n## Know when to split vs when to stop\n\nSplit when:\n\n- Two call sites use disjoint subsets of props\n- You need `v-if` on half the template based on a mode flag\n- The story of the component cannot be explained in one sentence\n\nDo not split when:\n\n- The only difference is a class name (use a `variant` or class prop)\n- You are creating three files to avoid eight lines of duplication\n- The “components” cannot be understood without opening all siblings\n\nA useful gut check: if removing one prop forces a rewrite of the examples in your README (or Storybook), the API may be too clever. Simple props survive refactors because callers can still read them.\n\nReview props the way you review a public package API. Before merging a new optional flag, ask who needs it today, who will inherit it tomorrow, and whether a slot or a sibling component removes the need entirely. I keep a short “props changelog” in the component comment when a shared piece is used in more than two places—what was added, why, and what not to pile on next. That small note stops the slow crawl toward a mega-config object nobody wants to touch.\n\n## Wrap-up\n\nSimple props stay maintainable: compose smaller pieces, use slots for flexible regions, group related options into clear variants, and document required vs optional at the boundary. Resist the boolean-prop explosion and the mega-card that accepts everything. Your future refactors—and anyone reusing the component—will thank you.",[15,23,33,42,49,57,64,72,79,86,93,100,107,114,116,124,130,138,147,155,161,168,174,182,190,197,203,209,216,222,228,234,241,248,255,263],{"_path":16,"title":17,"description":18,"date":19,"tags":20,"readingTime":12},"\u002Fblog\u002Fclient-side-tools-that-earn-trust\u002F","Client-Side Tools That Earn Trust","How to design browser tools that feel private by default—local processing, clear data boundaries, and UX that never asks people to guess where their files go.","2026-07-20",[21,10,22],"privacy","tools",{"_path":24,"title":25,"description":26,"date":27,"tags":28,"readingTime":32},"\u002Fblog\u002Fusb-c-cables-that-actually-matter\u002F","USB-C Cables That Actually Matter","How to buy USB-C cables by wattage and data speed so you stop guessing which cord charges slowly or fails a file transfer.","2026-07-14",[29,30,31],"gadgets","hardware","productivity",4,{"_path":34,"title":35,"description":36,"date":37,"tags":38,"readingTime":12},"\u002Fblog\u002Fcolor-contrast-people-can-read\u002F","Color Contrast People Can Actually Read","Quick checks for text and UI colors that stay legible in light mode, dark mode, and on mediocre displays.","2026-07-07",[39,40,41],"accessibility","css","design",{"_path":43,"title":44,"description":45,"date":46,"tags":47,"readingTime":12},"\u002Fblog\u002Fportable-ssds-worth-carrying\u002F","Portable SSDs Worth Carrying","When a pocket SSD beats cloud sync for travel, backups, and large media—and how to pick one that stays fast and durable.","2026-06-26",[29,48,31],"storage",{"_path":50,"title":51,"description":52,"date":53,"tags":54,"readingTime":12},"\u002Fblog\u002Fwireless-earbuds-that-last-the-day\u002F","Wireless Earbuds That Last the Day","Simple charging and fit habits that stretch earbud battery life without babysitting the case every few hours.","2026-06-18",[29,55,56],"audio","habits",{"_path":58,"title":59,"description":60,"date":61,"tags":62,"readingTime":32},"\u002Fblog\u002Fwhen-a-second-monitor-helps\u002F","When a Second Monitor Actually Helps","A practical take on dual screens for coding, writing, and research—plus when a single good display is still the better desk.","2026-06-10",[29,31,63],"workspace",{"_path":65,"title":66,"description":67,"date":68,"tags":69,"readingTime":32},"\u002Fblog\u002Fshort-session-game-picks\u002F","Short-Session Games That Respect Your Evening","Genres and design patterns that fit a 30-minute window without the “one more hour” trap.","2026-06-04",[70,56,71],"gaming","indie",{"_path":73,"title":74,"description":75,"date":76,"tags":77,"readingTime":12},"\u002Fblog\u002Fphone-charging-habits-that-age-better\u002F","Phone Charging Habits That Age Better","Practical charging routines that keep a phone useful longer—without obsessing over the perfect battery percentage.","2026-05-23",[29,78,56],"mobile",{"_path":80,"title":81,"description":82,"date":83,"tags":84,"readingTime":12},"\u002Fblog\u002Ffinishing-side-projects-without-burning-out\u002F","Finishing Side Projects Without Burning Out","A practical playbook for shrinking scope, shipping thin vertical slices, and keeping personal projects fun long enough to actually launch.","2026-05-14",[31,85,56],"side-projects",{"_path":87,"title":88,"description":89,"date":90,"tags":91,"readingTime":12},"\u002Fblog\u002Faccessible-forms-people-finish\u002F","Accessible Forms That People Actually Finish","Practical accessibility checks for labels, errors, and focus so your forms work for more users.","2026-05-07",[39,92,10],"html",{"_path":94,"title":95,"description":96,"date":97,"tags":98,"readingTime":12},"\u002Fblog\u002Fvue-composition-api-patterns\u002F","Practical Vue Composition API Patterns","Reusable patterns for composables, shared state, and cleaner component logic in Vue 3.","2026-04-27",[9,99,10],"javascript",{"_path":101,"title":102,"description":103,"date":104,"tags":105,"readingTime":12},"\u002Fblog\u002Fdesigning-empty-states-that-teach\u002F","Designing Empty States That Teach the Product","Empty screens are not placeholders—they are onboarding. How to write, layout, and sequence first-run UI so people know what to do next.","2026-04-19",[106,10,41],"ux",{"_path":108,"title":109,"description":110,"date":111,"tags":112,"readingTime":12},"\u002Fblog\u002Fwhy-indie-games-keep-winning\u002F","Why Indie Games Keep Winning Attention","How small teams punch above their weight with focused design, personality, and smart scope.","2026-04-13",[70,71,113],"industry",{"_path":4,"title":5,"description":6,"date":7,"tags":115,"readingTime":12},[9,10,11],{"_path":117,"title":118,"description":119,"date":120,"tags":121,"readingTime":12},"\u002Fblog\u002Fcalm-frontend-performance\u002F","A Calm Approach to Frontend Performance","Performance work that sticks: measure what users feel, fix the critical path first, and avoid optimization theater on personal and portfolio sites.","2026-03-26",[122,10,123],"performance","web",{"_path":125,"title":126,"description":127,"date":128,"tags":129,"readingTime":12},"\u002Fblog\u002Fcss-layout-without-the-hacks\u002F","CSS Layout Without the Hacks","Modern Flexbox and Grid techniques that replace brittle floats, magic numbers, and overflow tricks.","2026-03-17",[40,10,41],{"_path":131,"title":132,"description":133,"date":134,"tags":135,"readingTime":12},"\u002Fblog\u002Fcloud-gaming-what-gets-in-the-way\u002F","Cloud Gaming: What Still Gets in the Way","Latency, input feel, and library lock-in—why streaming games is impressive and still uneven.","2026-03-10",[70,136,137],"cloud-gaming","tech",{"_path":139,"title":140,"description":141,"date":142,"tags":143,"readingTime":12},"\u002Fblog\u002Fshipping-static-sites-with-nuxt\u002F","Shipping Static Sites with Nuxt","How I build and deploy a Nuxt site to Cloudflare Pages with predictable routes and content.","2026-03-02",[144,145,146],"nuxt","ssg","devops",{"_path":148,"title":149,"description":150,"date":151,"tags":152,"readingTime":12},"\u002Fblog\u002Fdebugging-faster-in-the-browser\u002F","Debugging Faster in the Browser","A short toolkit of DevTools habits—breakpoints, network filters, and DOM inspection—that cut debug time.","2026-02-24",[99,153,154],"devtools","debugging",{"_path":156,"title":157,"description":158,"date":159,"tags":160,"readingTime":12},"\u002Fblog\u002Fsmall-javascript-habits-that-scale\u002F","Small JavaScript Habits That Scale","Everyday habits—naming, early returns, and clearer async flow—that keep front-end codebases maintainable.","2026-02-18",[99,11,10],{"_path":162,"title":163,"description":164,"date":165,"tags":166,"readingTime":32},"\u002Fblog\u002Fhealthier-gaming-routine\u002F","Building a Healthier Gaming Routine","Simple habits for sessions that stay fun—breaks, goals, and knowing when to stop.","2026-02-02",[70,56,167],"wellness",{"_path":169,"title":170,"description":171,"date":172,"tags":173,"readingTime":12},"\u002Fblog\u002Fresponsive-images-without-layout-shift\u002F","Responsive Images Without Layout Shift","Width, height, srcset, and lazy loading patterns that keep pages stable while images load.","2026-01-22",[92,122,10],{"_path":175,"title":176,"description":177,"date":178,"tags":179,"readingTime":12},"\u002Fblog\u002Fgit-habits-for-small-teams\u002F","Git Habits for Solo and Small Teams","Branch naming, commit messages, and PR hygiene that keep history useful without slowing you down.","2026-01-09",[180,181,31],"git","workflow",{"_path":183,"title":184,"description":185,"date":186,"tags":187,"readingTime":12},"\u002Fblog\u002Fwhat-makes-a-game-stream-worth-watching\u002F","What Makes a Game Stream Worth Watching","Commentary, pacing, and community—why some streams click even when the gameplay is messy.","2025-12-14",[70,188,189],"streaming","content",{"_path":191,"title":192,"description":193,"date":194,"tags":195,"readingTime":12},"\u002Fblog\u002Fwhen-to-reach-for-typescript-vue\u002F","When to Reach for TypeScript on a Vue App","A pragmatic take on where TypeScript pays off in Vue—and where plain JS is still fine.","2025-11-30",[196,9,10],"typescript",{"_path":198,"title":199,"description":200,"date":201,"tags":202,"readingTime":12},"\u002Fblog\u002Fenvironment-variables-for-static-sites\u002F","Environment Variables for Static Sites","How to use build-time env vars in Nuxt and other static generators without leaking secrets or breaking CI.","2025-10-05",[144,146,10],{"_path":204,"title":205,"description":206,"date":207,"tags":208,"readingTime":12},"\u002Fblog\u002Fpicking-a-side-project-stack\u002F","Picking a Side Project Stack Without Overthinking It","How to choose tools for a personal project when the goal is shipping, not building the perfect architecture.","2025-09-18",[31,10,11],{"_path":210,"title":211,"description":212,"date":213,"tags":214,"readingTime":32},"\u002Fblog\u002Fco-op-games-for-busy-friends\u002F","Co-op Games That Work When Everyone Is Busy","Async-friendly and drop-in co-op picks for friend groups that rarely share the same free evening.","2025-08-22",[70,215,56],"co-op",{"_path":217,"title":218,"description":219,"date":220,"tags":221,"readingTime":12},"\u002Fblog\u002Fsemantic-html-still-matters\u002F","Semantic HTML Still Matters","Landmarks, headings, and native elements that make pages easier to use, style, and maintain.","2025-07-06",[92,39,10],{"_path":223,"title":224,"description":225,"date":226,"tags":227,"readingTime":12},"\u002Fblog\u002Fgit-without-the-jargon\u002F","Learning Git Without the Jargon","A plain-language mental model for commits, branches, and merges when tutorials assume you already know Git.","2025-05-14",[180,181,31],{"_path":229,"title":230,"description":231,"date":232,"tags":233,"readingTime":12},"\u002Fblog\u002Fwhy-loading-states-matter\u002F","Why Loading States Matter More Than Animations","Skeletons, disabled buttons, and honest feedback beat flashy spinners when data takes time to arrive.","2025-03-28",[106,10,41],{"_path":235,"title":236,"description":237,"date":238,"tags":239,"readingTime":12},"\u002Fblog\u002Fnaming-things-so-future-you-survives\u002F","Naming Things So Future You Survives","Practical rules for naming variables, components, and files so a codebase stays readable months after the clever jokes fade.","2025-03-10",[99,11,240],"maintainability",{"_path":242,"title":243,"description":244,"date":245,"tags":246,"readingTime":12},"\u002Fblog\u002Fsave-systems-that-respect-time\u002F","Save Systems That Respect the Player’s Time","Checkpoints, autosaves, and manual slots are design choices. How thoughtful saving reduces rage-quits without deleting tension from the game.","2025-02-28",[70,247,106],"game-design",{"_path":249,"title":250,"description":251,"date":252,"tags":253,"readingTime":12},"\u002Fblog\u002Fmicrocopy-that-makes-interfaces-human\u002F","Microcopy That Makes Interfaces Feel Human","Button labels, error lines, and helper text are product design. How to write UI words that reduce hesitation without sounding like a chatbot.","2025-01-22",[106,254,41],"writing",{"_path":256,"title":257,"description":258,"date":259,"tags":260,"readingTime":12},"\u002Fblog\u002Freadmes-that-get-projects-running\u002F","READMEs That Get Projects Running","How to write a README that helps someone—including future you—install, run, and understand a project without opening a support chat.","2024-12-18",[261,262,56],"documentation","open-source",{"_path":264,"title":265,"description":266,"date":267,"tags":268,"readingTime":12},"\u002Fblog\u002Fprogressive-enhancement-still-pays-off\u002F","Progressive Enhancement Still Pays Off","Build the core experience in HTML first, then layer CSS and JavaScript so your site stays useful when networks, scripts, or devices misbehave.","2024-11-05",[92,10,269],"architecture",1784620504901]