[{"data":1,"prerenderedAt":1441},["ShallowReactive",2],{"blog-\u002Fblog\u002Fvue-components-worth-inheriting":3},{"id":4,"title":5,"body":6,"date":1428,"description":1429,"extension":1430,"icon":1431,"meta":1432,"navigation":275,"path":1433,"readingTime":196,"seo":1434,"stem":1435,"tags":1436,"__hash__":1440},"content\u002Fblog\u002Fvue-components-worth-inheriting.md","Vue components I'd actually want to inherit",{"type":7,"value":8,"toc":1421},"minimark",[9,13,16,21,37,44,200,703,706,709,713,720,723,1112,1115,1124,1128,1141,1148,1375,1382,1389,1393,1396,1403,1406,1410,1417],[10,11,12],"p",{},"Over seven years of full-stack engineering, you inherit a lot of codebases. Some of them are clean, but most of them feature the classic Vue 3 anti-pattern: a single, massive 800-line component packed with an unorganised collection of reactive references, inline template calculations, and leaky event listeners. When you push a change to one corner of that component, an entirely unrelated layout module breaks on the other side of the screen.",[10,14,15],{},"Building components that survive multiple developer handovers isn't about using the newest experimental syntax features; it is about writing clean, self-documenting code. It means treating your component wrappers as strict architectural contracts where the data flow is completely predictable, types are bulletproof, and internal complexity is hidden behind isolated layers.",[17,18,20],"h2",{"id":19},"the-prop-interface-as-an-immutable-contract","The prop interface as an immutable contract",[10,22,23,24,28,29,32,33,36],{},"The fastest way to ruin a component lifecycle is to write lazy, open-ended prop definitions. The moment a junior developer sees a prop typed as a generic ",[25,26,27],"code",{},"Object"," or an open ",[25,30,31],{},"any"," array, they will start passing arbitrary data structures through it. Within weeks, your component template is littered with defensive optional-chaining statements like ",[25,34,35],{},"item?.meta?.user?.profile?.id"," just to prevent the browser runtime from throwing execution faults.",[10,38,39,40,43],{},"A component you actually want to inherit uses explicit, exported TypeScript interfaces for its input layer. By using type-only macros like ",[25,41,42],{},"defineProps\u003CProps>()",", you force the consumer to pass data structures that align perfectly with your system data models.",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-typescript shiki shiki-themes tokyo-night","\u002F\u002F types\u002Fdashboard.ts — Shared domain types across layouts\nexport interface MetricCardProps {\n  label: string;\n  currentValue: number;\n  previousValue: number;\n  formatter?: (val: number) => string;\n  status?: 'nominal' | 'warning' | 'critical';\n}\n","typescript","",[25,52,53,62,81,99,112,124,155,194],{"__ignoreMap":50},[54,55,58],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"sbD-w","\u002F\u002F types\u002Fdashboard.ts — Shared domain types across layouts\n",[54,63,65,69,73,77],{"class":56,"line":64},2,[54,66,68],{"class":67},"su0L4","export",[54,70,72],{"class":71},"sd1Qi"," interface",[54,74,76],{"class":75},"sE3pS"," MetricCardProps",[54,78,80],{"class":79},"sgJMe"," {\n",[54,82,84,88,92,96],{"class":56,"line":83},3,[54,85,87],{"class":86},"syYvs","  label",[54,89,91],{"class":90},"sAklC",":",[54,93,95],{"class":94},"sySf4"," string",[54,97,98],{"class":90},";\n",[54,100,102,105,107,110],{"class":56,"line":101},4,[54,103,104],{"class":86},"  currentValue",[54,106,91],{"class":90},[54,108,109],{"class":94}," number",[54,111,98],{"class":90},[54,113,115,118,120,122],{"class":56,"line":114},5,[54,116,117],{"class":86},"  previousValue",[54,119,91],{"class":90},[54,121,109],{"class":94},[54,123,98],{"class":90},[54,125,127,131,134,137,141,143,145,148,151,153],{"class":56,"line":126},6,[54,128,130],{"class":129},"s3R4Z","  formatter",[54,132,133],{"class":90},"?:",[54,135,136],{"class":79}," (",[54,138,140],{"class":139},"sT800","val",[54,142,91],{"class":90},[54,144,109],{"class":94},[54,146,147],{"class":79},")",[54,149,150],{"class":71}," =>",[54,152,95],{"class":94},[54,154,98],{"class":90},[54,156,158,161,163,166,170,173,176,178,181,183,185,187,190,192],{"class":56,"line":157},7,[54,159,160],{"class":86},"  status",[54,162,133],{"class":90},[54,164,165],{"class":90}," '",[54,167,169],{"class":168},"sPY7s","nominal",[54,171,172],{"class":90},"'",[54,174,175],{"class":90}," |",[54,177,165],{"class":90},[54,179,180],{"class":168},"warning",[54,182,172],{"class":90},[54,184,175],{"class":90},[54,186,165],{"class":90},[54,188,189],{"class":168},"critical",[54,191,172],{"class":90},[54,193,98],{"class":90},[54,195,197],{"class":56,"line":196},8,[54,198,199],{"class":79},"}\n",[45,201,205],{"className":202,"code":203,"language":204,"meta":50,"style":50},"language-vue shiki shiki-themes tokyo-night","\u003C!-- components\u002FMetricCard.vue — Strict, documented input interfaces -->\n\u003Cscript setup lang=\"ts\">\nimport type { MetricCardProps } from '~\u002Ftypes\u002Fdashboard';\n\n\u002F\u002F Enforce defaults cleanly without boilerplate runtime checks\nconst props = withDefaults(defineProps\u003CMetricCardProps>(), {\n  formatter: (val: number) => val.toLocaleString(),\n  status: 'nominal'\n});\n\nconst variance = computed(() => props.currentValue - props.previousValue);\nconst percentChange = computed(() => {\n  if (props.previousValue === 0) return 0;\n  return (variance.value \u002F props.previousValue) * 100;\n});\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv :class=\"['card-wrapper', `status-${props.status}`]\">\n    \u003Cspan class=\"label-text\">{{ props.label }}\u003C\u002Fspan>\n    \u003Cdiv class=\"metrics-row\">\n      \u003Cspan class=\"value-display\">{{ props.formatter(props.currentValue) }}\u003C\u002Fspan>\n      \u003Cspan :class=\"['variance-tag', variance >= 0 ? 'positive' : 'negative']\">\n        {{ percentChange.toFixed(1) }}%\n      \u003C\u002Fspan>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[25,206,207,212,242,271,277,282,318,350,363,371,376,415,433,466,501,508,518,523,533,556,588,608,638,658,664,674,684,694],{"__ignoreMap":50},[54,208,209],{"class":56,"line":57},[54,210,211],{"class":60},"\u003C!-- components\u002FMetricCard.vue — Strict, documented input interfaces -->\n",[54,213,214,218,222,225,228,231,234,237,239],{"class":56,"line":64},[54,215,217],{"class":216},"s3M2c","\u003C",[54,219,221],{"class":220},"s0U2E","script",[54,223,224],{"class":71}," setup",[54,226,227],{"class":71}," lang",[54,229,230],{"class":90},"=",[54,232,233],{"class":90},"\"",[54,235,236],{"class":168},"ts",[54,238,233],{"class":90},[54,240,241],{"class":216},">\n",[54,243,244,247,250,253,256,259,262,264,267,269],{"class":56,"line":83},[54,245,246],{"class":67},"import",[54,248,249],{"class":71}," type",[54,251,252],{"class":79}," { ",[54,254,255],{"class":94},"MetricCardProps",[54,257,258],{"class":79}," }",[54,260,261],{"class":67}," from",[54,263,165],{"class":90},[54,265,266],{"class":168},"~\u002Ftypes\u002Fdashboard",[54,268,172],{"class":90},[54,270,98],{"class":90},[54,272,273],{"class":56,"line":101},[54,274,276],{"emptyLinePlaceholder":275},true,"\n",[54,278,279],{"class":56,"line":114},[54,280,281],{"class":60},"\u002F\u002F Enforce defaults cleanly without boilerplate runtime checks\n",[54,283,284,288,291,294,297,300,303,305,307,310,313,316],{"class":56,"line":126},[54,285,287],{"class":286},"sN7LL","const",[54,289,290],{"class":71}," props",[54,292,293],{"class":90}," =",[54,295,296],{"class":129}," withDefaults",[54,298,299],{"class":79},"(",[54,301,302],{"class":129},"defineProps",[54,304,217],{"class":90},[54,306,255],{"class":75},[54,308,309],{"class":90},">",[54,311,312],{"class":79},"()",[54,314,315],{"class":90},",",[54,317,80],{"class":79},[54,319,320,322,324,326,328,330,332,334,336,339,342,345,347],{"class":56,"line":157},[54,321,130],{"class":129},[54,323,91],{"class":90},[54,325,136],{"class":79},[54,327,140],{"class":139},[54,329,91],{"class":90},[54,331,109],{"class":94},[54,333,147],{"class":79},[54,335,150],{"class":71},[54,337,338],{"class":75}," val",[54,340,341],{"class":90},".",[54,343,344],{"class":129},"toLocaleString",[54,346,312],{"class":79},[54,348,349],{"class":90},",\n",[54,351,352,354,356,358,360],{"class":56,"line":196},[54,353,160],{"class":86},[54,355,91],{"class":90},[54,357,165],{"class":90},[54,359,169],{"class":168},[54,361,362],{"class":90},"'\n",[54,364,366,369],{"class":56,"line":365},9,[54,367,368],{"class":79},"})",[54,370,98],{"class":90},[54,372,374],{"class":56,"line":373},10,[54,375,276],{"emptyLinePlaceholder":275},[54,377,379,381,384,386,389,392,394,396,398,401,404,406,408,411,413],{"class":56,"line":378},11,[54,380,287],{"class":286},[54,382,383],{"class":71}," variance",[54,385,293],{"class":90},[54,387,388],{"class":129}," computed",[54,390,391],{"class":79},"(()",[54,393,150],{"class":71},[54,395,290],{"class":75},[54,397,341],{"class":90},[54,399,400],{"class":67},"currentValue",[54,402,403],{"class":90}," -",[54,405,290],{"class":75},[54,407,341],{"class":90},[54,409,410],{"class":67},"previousValue",[54,412,147],{"class":79},[54,414,98],{"class":90},[54,416,418,420,423,425,427,429,431],{"class":56,"line":417},12,[54,419,287],{"class":286},[54,421,422],{"class":71}," percentChange",[54,424,293],{"class":90},[54,426,388],{"class":129},[54,428,391],{"class":79},[54,430,150],{"class":71},[54,432,80],{"class":79},[54,434,436,439,441,444,446,448,451,455,458,462,464],{"class":56,"line":435},13,[54,437,438],{"class":71},"  if",[54,440,136],{"class":79},[54,442,443],{"class":75},"props",[54,445,341],{"class":90},[54,447,410],{"class":67},[54,449,450],{"class":71}," ===",[54,452,454],{"class":453},"sOJ5S"," 0",[54,456,457],{"class":79},") ",[54,459,461],{"class":460},"sEsAJ","return",[54,463,454],{"class":453},[54,465,98],{"class":90},[54,467,469,472,474,477,479,482,485,487,489,491,493,496,499],{"class":56,"line":468},14,[54,470,471],{"class":460},"  return",[54,473,136],{"class":79},[54,475,476],{"class":75},"variance",[54,478,341],{"class":90},[54,480,481],{"class":67},"value",[54,483,484],{"class":90}," \u002F",[54,486,290],{"class":75},[54,488,341],{"class":90},[54,490,410],{"class":67},[54,492,457],{"class":79},[54,494,495],{"class":90},"*",[54,497,498],{"class":453}," 100",[54,500,98],{"class":90},[54,502,504,506],{"class":56,"line":503},15,[54,505,368],{"class":79},[54,507,98],{"class":90},[54,509,511,514,516],{"class":56,"line":510},16,[54,512,513],{"class":216},"\u003C\u002F",[54,515,221],{"class":220},[54,517,241],{"class":216},[54,519,521],{"class":56,"line":520},17,[54,522,276],{"emptyLinePlaceholder":275},[54,524,526,528,531],{"class":56,"line":525},18,[54,527,217],{"class":216},[54,529,530],{"class":220},"template",[54,532,241],{"class":216},[54,534,536,539,542,545,547,549,552,554],{"class":56,"line":535},19,[54,537,538],{"class":216},"  \u003C",[54,540,541],{"class":220},"div",[54,543,544],{"class":71}," :class",[54,546,230],{"class":90},[54,548,233],{"class":90},[54,550,551],{"class":168},"['card-wrapper', `status-${props.status}`]",[54,553,233],{"class":90},[54,555,241],{"class":216},[54,557,559,562,564,567,569,571,574,576,578,582,584,586],{"class":56,"line":558},20,[54,560,561],{"class":216},"    \u003C",[54,563,54],{"class":220},[54,565,566],{"class":71}," class",[54,568,230],{"class":90},[54,570,233],{"class":90},[54,572,573],{"class":168},"label-text",[54,575,233],{"class":90},[54,577,309],{"class":216},[54,579,581],{"class":580},"sXIJe","{{ props.label }}",[54,583,513],{"class":216},[54,585,54],{"class":220},[54,587,241],{"class":216},[54,589,591,593,595,597,599,601,604,606],{"class":56,"line":590},21,[54,592,561],{"class":216},[54,594,541],{"class":220},[54,596,566],{"class":71},[54,598,230],{"class":90},[54,600,233],{"class":90},[54,602,603],{"class":168},"metrics-row",[54,605,233],{"class":90},[54,607,241],{"class":216},[54,609,611,614,616,618,620,622,625,627,629,632,634,636],{"class":56,"line":610},22,[54,612,613],{"class":216},"      \u003C",[54,615,54],{"class":220},[54,617,566],{"class":71},[54,619,230],{"class":90},[54,621,233],{"class":90},[54,623,624],{"class":168},"value-display",[54,626,233],{"class":90},[54,628,309],{"class":216},[54,630,631],{"class":580},"{{ props.formatter(props.currentValue) }}",[54,633,513],{"class":216},[54,635,54],{"class":220},[54,637,241],{"class":216},[54,639,641,643,645,647,649,651,654,656],{"class":56,"line":640},23,[54,642,613],{"class":216},[54,644,54],{"class":220},[54,646,544],{"class":71},[54,648,230],{"class":90},[54,650,233],{"class":90},[54,652,653],{"class":168},"['variance-tag', variance >= 0 ? 'positive' : 'negative']",[54,655,233],{"class":90},[54,657,241],{"class":216},[54,659,661],{"class":56,"line":660},24,[54,662,663],{"class":580},"        {{ percentChange.toFixed(1) }}%\n",[54,665,667,670,672],{"class":56,"line":666},25,[54,668,669],{"class":216},"      \u003C\u002F",[54,671,54],{"class":220},[54,673,241],{"class":216},[54,675,677,680,682],{"class":56,"line":676},26,[54,678,679],{"class":216},"    \u003C\u002F",[54,681,541],{"class":220},[54,683,241],{"class":216},[54,685,687,690,692],{"class":56,"line":686},27,[54,688,689],{"class":216},"  \u003C\u002F",[54,691,541],{"class":220},[54,693,241],{"class":216},[54,695,697,699,701],{"class":56,"line":696},28,[54,698,513],{"class":216},[54,700,530],{"class":220},[54,702,241],{"class":216},[10,704,705],{},"By compiling the props via static type analysis, any structural mismatch between your data layers triggers an immediate error inside your local IDE and halts your compilation pipeline.",[10,707,708],{},"Notice also that we avoid mutating props locally or checking for layout conditions directly inside the template block. If a value requires processing before presentation, isolate that conversion inside an explicit computed property. This approach keeps the raw template logic completely declarative and easy to scan at a glance.",[17,710,712],{"id":711},"knowing-when-to-extract-a-composable","Knowing when to extract a composable",[10,714,715,716,719],{},"The introduction of the Composition API triggered an obsession where developers began extracting every single line of reactive logic into external composable functions. If your component requires a basic three-line visibility toggle for a modal dropdown, you do not need to create an external ",[25,717,718],{},"useModalToggle.ts"," file. Moving microscopic local configurations outside the component scope adds unnecessary architectural navigation overhead.",[10,721,722],{},"The boundary for creating a true composable is when a stateful pattern needs to be reused across distinct layout components, or when a component's internal state tracking compromises its readability.",[45,724,726],{"className":47,"code":725,"language":49,"meta":50,"style":50},"\u002F\u002F composables\u002FuseActiveStream.ts — Isolated stateful event pipeline\nimport { ref, onMounted, onUnmounted } from 'vue';\n\nexport function useActiveStream(endpointUrl: string) {\n  const payload = ref\u003CRecord\u003Cstring, any> | null>(null);\n  const latencyScore = ref\u003Cnumber>(0);\n  let socketInstance: WebSocket | null = null;\n\n  const establishConnection = () => {\n    socketInstance = new WebSocket(endpointUrl);\n    socketInstance.onmessage = (event) => {\n      const startTime = performance.now();\n      payload.value = JSON.parse(event.data);\n      latencyScore.value = performance.now() - startTime;\n    };\n  };\n\n  onMounted(() => establishConnection());\n  onUnmounted(() => socketInstance?.close());\n\n  return { payload, latencyScore };\n}\n",[25,727,728,733,764,768,791,837,864,887,891,908,928,950,972,1004,1031,1038,1045,1049,1066,1087,1091,1108],{"__ignoreMap":50},[54,729,730],{"class":56,"line":57},[54,731,732],{"class":60},"\u002F\u002F composables\u002FuseActiveStream.ts — Isolated stateful event pipeline\n",[54,734,735,737,739,742,744,747,749,752,754,756,758,760,762],{"class":56,"line":64},[54,736,246],{"class":67},[54,738,252],{"class":79},[54,740,741],{"class":94},"ref",[54,743,315],{"class":90},[54,745,746],{"class":94}," onMounted",[54,748,315],{"class":90},[54,750,751],{"class":94}," onUnmounted",[54,753,258],{"class":79},[54,755,261],{"class":67},[54,757,165],{"class":90},[54,759,204],{"class":168},[54,761,172],{"class":90},[54,763,98],{"class":90},[54,765,766],{"class":56,"line":83},[54,767,276],{"emptyLinePlaceholder":275},[54,769,770,772,775,778,780,783,785,787,789],{"class":56,"line":101},[54,771,68],{"class":67},[54,773,774],{"class":71}," function",[54,776,777],{"class":129}," useActiveStream",[54,779,299],{"class":79},[54,781,782],{"class":139},"endpointUrl",[54,784,91],{"class":90},[54,786,95],{"class":94},[54,788,147],{"class":79},[54,790,80],{"class":79},[54,792,793,796,799,801,804,806,809,811,814,816,819,821,823,826,828,830,833,835],{"class":56,"line":114},[54,794,795],{"class":286},"  const",[54,797,798],{"class":71}," payload",[54,800,293],{"class":90},[54,802,803],{"class":129}," ref",[54,805,217],{"class":90},[54,807,808],{"class":75},"Record",[54,810,217],{"class":90},[54,812,813],{"class":94},"string",[54,815,315],{"class":90},[54,817,818],{"class":94}," any",[54,820,309],{"class":90},[54,822,175],{"class":90},[54,824,825],{"class":94}," null",[54,827,309],{"class":90},[54,829,299],{"class":79},[54,831,832],{"class":453},"null",[54,834,147],{"class":79},[54,836,98],{"class":90},[54,838,839,841,844,846,848,850,853,855,857,860,862],{"class":56,"line":126},[54,840,795],{"class":286},[54,842,843],{"class":71}," latencyScore",[54,845,293],{"class":90},[54,847,803],{"class":129},[54,849,217],{"class":90},[54,851,852],{"class":94},"number",[54,854,309],{"class":90},[54,856,299],{"class":79},[54,858,859],{"class":453},"0",[54,861,147],{"class":79},[54,863,98],{"class":90},[54,865,866,869,872,874,877,879,881,883,885],{"class":56,"line":157},[54,867,868],{"class":286},"  let",[54,870,871],{"class":71}," socketInstance",[54,873,91],{"class":90},[54,875,876],{"class":75}," WebSocket",[54,878,175],{"class":90},[54,880,825],{"class":94},[54,882,293],{"class":90},[54,884,825],{"class":453},[54,886,98],{"class":90},[54,888,889],{"class":56,"line":196},[54,890,276],{"emptyLinePlaceholder":275},[54,892,893,895,898,900,903,906],{"class":56,"line":365},[54,894,795],{"class":286},[54,896,897],{"class":129}," establishConnection",[54,899,293],{"class":90},[54,901,902],{"class":79}," () ",[54,904,905],{"class":71},"=>",[54,907,80],{"class":79},[54,909,910,913,915,918,920,922,924,926],{"class":56,"line":373},[54,911,912],{"class":75},"    socketInstance",[54,914,293],{"class":90},[54,916,917],{"class":90}," new",[54,919,876],{"class":129},[54,921,299],{"class":79},[54,923,782],{"class":75},[54,925,147],{"class":79},[54,927,98],{"class":90},[54,929,930,932,934,937,939,941,944,946,948],{"class":56,"line":378},[54,931,912],{"class":75},[54,933,341],{"class":90},[54,935,936],{"class":129},"onmessage",[54,938,293],{"class":90},[54,940,136],{"class":79},[54,942,943],{"class":139},"event",[54,945,457],{"class":79},[54,947,905],{"class":71},[54,949,80],{"class":79},[54,951,952,955,958,960,963,965,968,970],{"class":56,"line":417},[54,953,954],{"class":286},"      const",[54,956,957],{"class":71}," startTime",[54,959,293],{"class":90},[54,961,962],{"class":75}," performance",[54,964,341],{"class":90},[54,966,967],{"class":129},"now",[54,969,312],{"class":79},[54,971,98],{"class":90},[54,973,974,977,979,981,983,986,988,991,993,995,997,1000,1002],{"class":56,"line":435},[54,975,976],{"class":75},"      payload",[54,978,341],{"class":90},[54,980,481],{"class":67},[54,982,293],{"class":90},[54,984,985],{"class":94}," JSON",[54,987,341],{"class":90},[54,989,990],{"class":129},"parse",[54,992,299],{"class":79},[54,994,943],{"class":75},[54,996,341],{"class":90},[54,998,999],{"class":67},"data",[54,1001,147],{"class":79},[54,1003,98],{"class":90},[54,1005,1006,1009,1011,1013,1015,1017,1019,1021,1024,1027,1029],{"class":56,"line":468},[54,1007,1008],{"class":75},"      latencyScore",[54,1010,341],{"class":90},[54,1012,481],{"class":67},[54,1014,293],{"class":90},[54,1016,962],{"class":75},[54,1018,341],{"class":90},[54,1020,967],{"class":129},[54,1022,1023],{"class":79},"() ",[54,1025,1026],{"class":90},"-",[54,1028,957],{"class":75},[54,1030,98],{"class":90},[54,1032,1033,1036],{"class":56,"line":503},[54,1034,1035],{"class":79},"    }",[54,1037,98],{"class":90},[54,1039,1040,1043],{"class":56,"line":510},[54,1041,1042],{"class":79},"  }",[54,1044,98],{"class":90},[54,1046,1047],{"class":56,"line":520},[54,1048,276],{"emptyLinePlaceholder":275},[54,1050,1051,1054,1057,1059,1061,1064],{"class":56,"line":525},[54,1052,1053],{"class":129},"  onMounted",[54,1055,1056],{"class":79},"(() ",[54,1058,905],{"class":71},[54,1060,897],{"class":129},[54,1062,1063],{"class":79},"())",[54,1065,98],{"class":90},[54,1067,1068,1071,1073,1075,1077,1080,1083,1085],{"class":56,"line":535},[54,1069,1070],{"class":129},"  onUnmounted",[54,1072,1056],{"class":79},[54,1074,905],{"class":71},[54,1076,871],{"class":75},[54,1078,1079],{"class":90},"?.",[54,1081,1082],{"class":129},"close",[54,1084,1063],{"class":79},[54,1086,98],{"class":90},[54,1088,1089],{"class":56,"line":558},[54,1090,276],{"emptyLinePlaceholder":275},[54,1092,1093,1095,1097,1100,1102,1104,1106],{"class":56,"line":590},[54,1094,471],{"class":460},[54,1096,252],{"class":79},[54,1098,1099],{"class":75},"payload",[54,1101,315],{"class":90},[54,1103,843],{"class":75},[54,1105,258],{"class":79},[54,1107,98],{"class":90},[54,1109,1110],{"class":56,"line":610},[54,1111,199],{"class":79},[10,1113,1114],{},"By extracting this network tracking logic out of the view script, your layout files remain focused solely on layout orientation and structural event routing.",[10,1116,1117,1118,1123],{},"I leaned on this clean separation when constructing the metrics processing blocks inside ",[1119,1120,1122],"a",{"href":1121},"\u002Fprojects\u002Fnuxt-saas","DataSync"," — where high-frequency database replication lines must stream live status logs directly into multiple dashboard components. The UI component simply calls the composable, consumes the returned reactive primitives, and remains entirely agnostic about socket handshakes, buffer processing, or memory cleanup arrays.",[17,1125,1127],{"id":1126},"protecting-your-state-boundaries-read-only-leaks","Protecting your state boundaries: read-only leaks",[10,1129,1130,1131,1133,1134,1137,1138,1140],{},"When you return a reactive reference (",[25,1132,741],{},") from a composable or pass a complex object down via ",[25,1135,1136],{},"provide\u002Finject"," boundaries, any child component can directly mutate that state by reassigning its inner ",[25,1139,481],{}," property. This creates silent, highly unpredictable state bugs where data changes skip your central tracking streams.",[10,1142,1143,1144,1147],{},"To protect your system state from unauthorised layout mutations, always wrap your exposed refs inside a ",[25,1145,1146],{},"readonly"," proxy before exporting them to consumer components.",[45,1149,1151],{"className":47,"code":1150,"language":49,"meta":50,"style":50},"\u002F\u002F Keeping state updates strictly unidirectional\nimport { ref, readonly } from 'vue';\n\nexport function useSystemConfig() {\n  const internalConfig = ref({\n    theme: 'dark',\n    refreshInterval: 3000\n  });\n\n  const updateInterval = (newMs: number) => {\n    if (newMs \u003C 1000) return; \u002F\u002F Centralised validation rule enforcement\n    internalConfig.value.refreshInterval = newMs;\n  };\n\n  return {\n    \u002F\u002F The consumer can read the configuration values but cannot mutate them directly\n    config: readonly(internalConfig),\n    updateInterval\n  };\n}\n",[25,1152,1153,1158,1183,1187,1200,1214,1230,1240,1247,1251,1275,1300,1321,1327,1331,1337,1342,1360,1365,1371],{"__ignoreMap":50},[54,1154,1155],{"class":56,"line":57},[54,1156,1157],{"class":60},"\u002F\u002F Keeping state updates strictly unidirectional\n",[54,1159,1160,1162,1164,1166,1168,1171,1173,1175,1177,1179,1181],{"class":56,"line":64},[54,1161,246],{"class":67},[54,1163,252],{"class":79},[54,1165,741],{"class":94},[54,1167,315],{"class":90},[54,1169,1170],{"class":94}," readonly",[54,1172,258],{"class":79},[54,1174,261],{"class":67},[54,1176,165],{"class":90},[54,1178,204],{"class":168},[54,1180,172],{"class":90},[54,1182,98],{"class":90},[54,1184,1185],{"class":56,"line":83},[54,1186,276],{"emptyLinePlaceholder":275},[54,1188,1189,1191,1193,1196,1198],{"class":56,"line":101},[54,1190,68],{"class":67},[54,1192,774],{"class":71},[54,1194,1195],{"class":129}," useSystemConfig",[54,1197,312],{"class":79},[54,1199,80],{"class":79},[54,1201,1202,1204,1207,1209,1211],{"class":56,"line":114},[54,1203,795],{"class":286},[54,1205,1206],{"class":71}," internalConfig",[54,1208,293],{"class":90},[54,1210,803],{"class":129},[54,1212,1213],{"class":79},"({\n",[54,1215,1216,1219,1221,1223,1226,1228],{"class":56,"line":126},[54,1217,1218],{"class":86},"    theme",[54,1220,91],{"class":90},[54,1222,165],{"class":90},[54,1224,1225],{"class":168},"dark",[54,1227,172],{"class":90},[54,1229,349],{"class":90},[54,1231,1232,1235,1237],{"class":56,"line":157},[54,1233,1234],{"class":86},"    refreshInterval",[54,1236,91],{"class":90},[54,1238,1239],{"class":453}," 3000\n",[54,1241,1242,1245],{"class":56,"line":196},[54,1243,1244],{"class":79},"  })",[54,1246,98],{"class":90},[54,1248,1249],{"class":56,"line":365},[54,1250,276],{"emptyLinePlaceholder":275},[54,1252,1253,1255,1258,1260,1262,1265,1267,1269,1271,1273],{"class":56,"line":373},[54,1254,795],{"class":286},[54,1256,1257],{"class":129}," updateInterval",[54,1259,293],{"class":90},[54,1261,136],{"class":79},[54,1263,1264],{"class":139},"newMs",[54,1266,91],{"class":90},[54,1268,109],{"class":94},[54,1270,457],{"class":79},[54,1272,905],{"class":71},[54,1274,80],{"class":79},[54,1276,1277,1280,1282,1284,1287,1290,1292,1294,1297],{"class":56,"line":378},[54,1278,1279],{"class":71},"    if",[54,1281,136],{"class":79},[54,1283,1264],{"class":75},[54,1285,1286],{"class":71}," \u003C",[54,1288,1289],{"class":453}," 1000",[54,1291,457],{"class":79},[54,1293,461],{"class":460},[54,1295,1296],{"class":90},";",[54,1298,1299],{"class":60}," \u002F\u002F Centralised validation rule enforcement\n",[54,1301,1302,1305,1307,1309,1311,1314,1316,1319],{"class":56,"line":417},[54,1303,1304],{"class":75},"    internalConfig",[54,1306,341],{"class":90},[54,1308,481],{"class":75},[54,1310,341],{"class":90},[54,1312,1313],{"class":67},"refreshInterval",[54,1315,293],{"class":90},[54,1317,1318],{"class":75}," newMs",[54,1320,98],{"class":90},[54,1322,1323,1325],{"class":56,"line":435},[54,1324,1042],{"class":79},[54,1326,98],{"class":90},[54,1328,1329],{"class":56,"line":468},[54,1330,276],{"emptyLinePlaceholder":275},[54,1332,1333,1335],{"class":56,"line":503},[54,1334,471],{"class":460},[54,1336,80],{"class":79},[54,1338,1339],{"class":56,"line":510},[54,1340,1341],{"class":60},"    \u002F\u002F The consumer can read the configuration values but cannot mutate them directly\n",[54,1343,1344,1347,1349,1351,1353,1356,1358],{"class":56,"line":520},[54,1345,1346],{"class":86},"    config",[54,1348,91],{"class":90},[54,1350,1170],{"class":129},[54,1352,299],{"class":79},[54,1354,1355],{"class":75},"internalConfig",[54,1357,147],{"class":79},[54,1359,349],{"class":90},[54,1361,1362],{"class":56,"line":525},[54,1363,1364],{"class":75},"    updateInterval\n",[54,1366,1367,1369],{"class":56,"line":535},[54,1368,1042],{"class":79},[54,1370,98],{"class":90},[54,1372,1373],{"class":56,"line":558},[54,1374,199],{"class":79},[10,1376,1377,1378,1381],{},"If a child layout attempts to execute an operation like ",[25,1379,1380],{},"config.value.refreshInterval = 500",", the Vue runtime compiler will block the assignment immediately and throw a clear warning log inside the development console.",[10,1383,1384,1385,1388],{},"If the consumer requires a state modification, they are forced to run the explicit, validated mutator function (",[25,1386,1387],{},"updateInterval",") exposed by the host controller. This pattern preserves a strict, unidirectional data loop that makes tracking performance mutations straightforward during complex debugging sessions.",[17,1390,1392],{"id":1391},"trade-offs-and-what-doesnt-work","Trade-offs and what doesn't work",[10,1394,1395],{},"The biggest trap when defining strict Vue component architectures is over-engineering your component abstraction lines too early in the project life cycle. Developers frequently look at a single template layout, assume it will eventually need to support ten different edge-case configurations, and build an overly abstract wrapper filled with deep dynamic slot trees, recursive rendering methods, and dozens of optional flags.",[10,1397,1398,1399,1402],{},"This predictive architecture usually results in code that is incredibly difficult to read and maintain. If your component requires more than three nested ",[25,1400,1401],{},"\u003Cslot>"," insertion nodes or features a prop matrix that exceeds ten distinct configuration parameters, it is a clear structural signal that your layout is handling too many responsibilities.",[10,1404,1405],{},"Do not try to make a single component handle every visual permutation across your application. It is far more efficient to build three distinct, highly specialised components that share a single underlying business logic composable than to maintain one massive monolithic element that attempts to process every conditional edge case natively.",[17,1407,1409],{"id":1408},"closing-take","Closing take",[10,1411,1412,1413,341],{},"Write your Vue components with the assumption that the engineer who inherits your repository six months from now is a sleep-deprived developer who has never seen your initial design specifications. Keep your component interfaces typed explicitly via clean TypeScript abstractions, protect your state trees using read-only wrappers, and keep your layout files lean by offloading heavy background calculations into standalone composable files. If you want to see how these component conventions scale when integrated into an enterprise web interface, inspect the clean layout architecture detailed on the ",[1119,1414,1416],{"href":1415},"\u002Fprojects\u002Fpulse-dashboard","Pulse Dashboard project page",[1418,1419,1420],"style",{},"html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}html pre.shiki code .su0L4, html code.shiki .su0L4{--shiki-default:#7DCFFF}html pre.shiki code .sd1Qi, html code.shiki .sd1Qi{--shiki-default:#BB9AF7}html pre.shiki code .sE3pS, html code.shiki .sE3pS{--shiki-default:#C0CAF5}html pre.shiki code .sgJMe, html code.shiki .sgJMe{--shiki-default:#9ABDF5}html pre.shiki code .syYvs, html code.shiki .syYvs{--shiki-default:#73DACA}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sySf4, html code.shiki .sySf4{--shiki-default:#0DB9D7}html pre.shiki code .s3R4Z, html code.shiki .s3R4Z{--shiki-default:#7AA2F7}html pre.shiki code .sT800, html code.shiki .sT800{--shiki-default:#E0AF68}html pre.shiki code .sPY7s, html code.shiki .sPY7s{--shiki-default:#9ECE6A}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 pre.shiki code .s3M2c, html code.shiki .s3M2c{--shiki-default:#BA3C97}html pre.shiki code .s0U2E, html code.shiki .s0U2E{--shiki-default:#F7768E}html pre.shiki code .sN7LL, html code.shiki .sN7LL{--shiki-default:#9D7CD8;--shiki-default-font-style:italic}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}html pre.shiki code .sEsAJ, html code.shiki .sEsAJ{--shiki-default:#BB9AF7;--shiki-default-font-style:italic}html pre.shiki code .sXIJe, html code.shiki .sXIJe{--shiki-default:#9AA5CE}",{"title":50,"searchDepth":64,"depth":64,"links":1422},[1423,1424,1425,1426,1427],{"id":19,"depth":64,"text":20},{"id":711,"depth":64,"text":712},{"id":1126,"depth":64,"text":1127},{"id":1391,"depth":64,"text":1392},{"id":1408,"depth":64,"text":1409},"2026-03-29","A critical look at building production-grade Vue 3 components using the Composition API, focusing on strict prop contracts, safe composables, and maintainable types.","md","🏗️",{},"\u002Fblog\u002Fvue-components-worth-inheriting",{"title":5,"description":1429},"blog\u002Fvue-components-worth-inheriting",[1437,1438,1439],"Vue","Nuxt","TypeScript","ZxGa40Vn590VUHaTP5WLtBgEGrSEd7f0-1dL-rqyYhw",1779864409785]