[{"data":1,"prerenderedAt":1269},["ShallowReactive",2],{"content:\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref":3,"surroundings:\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref":1261},{"id":4,"title":5,"body":6,"description":1240,"extension":1241,"meta":1242,"navigation":1254,"path":1255,"seo":1256,"stem":1259,"__hash__":1260},"content\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002Findex.md","Cutting Vue Reactivity Cost with shallowRef",{"type":7,"value":8,"toc":1222},"minimark",[9,14,38,64,169,174,218,222,228,234,240,246,250,255,462,466,532,536,543,547,554,690,694,697,701,710,784,788,794,968,972,983,987,1017,1021,1027,1033,1039,1045,1049,1067,1076,1088,1097,1106,1115,1124,1137,1154,1163,1172,1181,1185,1207,1212,1215,1218],[10,11,13],"h1",{"id":12},"how-to-cut-vue-reactivity-cost-with-shallowref","How to Cut Vue Reactivity Cost with shallowRef",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002F","Vue & Nuxt Performance",", within ",[19,25,27],{"href":26},"\u002Fframework-performance\u002F","Framework Performance",". Vue's ",[30,31,32],"code",{},"ref()"," and ",[30,35,36],{},"reactive()"," make state deeply reactive: every nested object and array is wrapped in a proxy, every property access inside a render or computed is tracked, and every mutation at any depth can trigger updates. For typical component state, this is cheap and convenient. For large data — a table of 10,000 rows, a big JSON document, chart series, map features, a third-party SDK instance — deep reactivity becomes overhead: proxies are created lazily on access, tracking adds work to every read, and memory grows.",[15,39,40,41,47,48,51,52,57,58,63],{},"Most large data is not mutated property by property. It is loaded, displayed and replaced wholesale. For that, ",[42,43,44],"strong",{},[30,45,46],{},"shallowRef"," (reactive only at the ",[30,49,50],{},".value"," level), ",[42,53,54],{},[30,55,56],{},"shallowReactive"," (reactive only on top-level properties) and ",[42,59,60],{},[30,61,62],{},"markRaw"," (never reactive) give the same rendering behaviour with a fraction of the cost.",[15,65,66],{},[67,68,74,75,74,82,74,86,74,89,74,98,74,104,74,111,74,119,74,124,74,128,74,135,74,139,74,143,74,147,74,151,74,155,74,159,74,163,74],"svg",{"viewBox":69,"width":70,"role":71,"ariaLabel":72,"style":73},"0 0 760 220","100%","img","Bar chart comparing time to render and update a large table with deep ref versus shallowRef.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[76,77],"rect",{"className":78,"x":80,"y":80,"width":70,"height":70,"fill":81},[79],"svg-canvas","0","#ffffff",[83,84,85],"title",{},"Rendering a 10,000-row table in Vue",[87,88,72],"desc",{},[76,90],{"x":91,"y":91,"width":92,"height":93,"rx":94,"fill":95,"stroke":96,"style":97},"1","758","218","10","none","currentColor","stroke-opacity:0.18",[99,100,85],"text",{"x":101,"y":102,"fill":96,"style":103},"28.0","34.0","font-size:16px;font-weight:700",[99,105,110],{"x":106,"y":107,"fill":96,"style":108,"textAnchor":109},"174.9","70.0","font-size:13px","end","ref() - initial render",[76,112],{"x":113,"y":114,"width":115,"height":116,"rx":117,"fill":95,"stroke":96,"style":118},"186.9","56.0","481.1","19","3","fill-opacity:0.7;stroke-opacity:0.35",[99,120,123],{"x":121,"y":107,"fill":96,"style":122},"674.0","font-size:12px;font-weight:600","420ms",[99,125,127],{"x":106,"y":126,"fill":96,"style":108,"textAnchor":109},"101.0","shallowRef - initial render",[76,129],{"x":113,"y":130,"width":131,"height":116,"rx":117,"fill":132,"stroke":133,"style":134},"87.0","297.8","#ffc300","#b8860b","fill-opacity:0.7;stroke-opacity:0.9",[99,136,138],{"x":137,"y":126,"fill":96,"style":122},"490.7","260ms",[99,140,142],{"x":106,"y":141,"fill":96,"style":108,"textAnchor":109},"132.0","ref() - replace data",[76,144],{"x":113,"y":145,"width":146,"height":116,"rx":117,"fill":95,"stroke":96,"style":118},"118.0","435.3",[99,148,150],{"x":149,"y":141,"fill":96,"style":122},"628.2","380ms",[99,152,154],{"x":106,"y":153,"fill":96,"style":108,"textAnchor":109},"163.0","shallowRef - replace data",[76,156],{"x":113,"y":157,"width":158,"height":116,"rx":117,"fill":132,"stroke":133,"style":134},"149.0","240.6",[99,160,162],{"x":161,"y":153,"fill":96,"style":122},"433.4","210ms",[99,164,168],{"x":101,"y":165,"fill":166,"style":167},"198.0","#51617a","font-size:12.5px","Same template; times dominated by rendering, with proxy creation and tracking removed in the shallow case.",[170,171,173],"h2",{"id":172},"rapid-diagnosis","Rapid Diagnosis",[175,176,177,192,206,212],"ul",{},[178,179,180,183,184,187,188,191],"li",{},[42,181,182],{},"Profile the slow interaction"," in the Performance panel and Vue DevTools; look for time in reactivity internals (proxy getters, ",[30,185,186],{},"track",", ",[30,189,190],{},"trigger",").",[178,193,194,197,198,201,202,205],{},[42,195,196],{},"Check how large data is stored",": ",[30,199,200],{},"ref"," or ",[30,203,204],{},"reactive"," holding big arrays or objects.",[178,207,208,211],{},[42,209,210],{},"Check third-party instances"," (charts, maps, editors) stored in reactive state.",[178,213,214,217],{},[42,215,216],{},"Check watchers",": deep watchers on large structures traverse everything on each change.",[170,219,221],{"id":220},"root-cause-analysis","Root Cause Analysis",[15,223,224,227],{},[42,225,226],{},"1. Deep proxies on large data."," Overhead on every access and mutation.",[15,229,230,233],{},[42,231,232],{},"2. Third-party objects made reactive."," Proxying complex instances breaks or slows them.",[15,235,236,239],{},[42,237,238],{},"3. Deep watchers."," Traversing large objects to detect changes.",[15,241,242,245],{},[42,243,244],{},"4. Mutation patterns."," Many small mutations trigger many updates instead of one replacement.",[170,247,249],{"id":248},"step-by-step-resolution","Step-by-Step Resolution",[251,252,254],"h3",{"id":253},"_1-use-shallowref-for-large-replaced-data","1. Use shallowRef for large, replaced data",[256,257,262],"pre",{"className":258,"code":259,"language":260,"meta":261,"style":261},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","import { shallowRef, triggerRef } from 'vue';\nconst rows = shallowRef([]);\nasync function load() {\n  rows.value = await fetchRows();          \u002F\u002F replacing .value triggers updates\n}\nfunction updateRow(i, patch) {\n  rows.value = rows.value.map((r, j) => (j === i ? { ...r, ...patch } : r));  \u002F\u002F replace, don't mutate\n  \u002F\u002F or: mutate in place, then triggerRef(rows) to notify explicitly\n}\n\u002F\u002F trade-off: nested mutations are not tracked; code must replace data or call\n\u002F\u002F triggerRef, which is a deliberate change in how the data is updated.\n","javascript","",[30,263,264,287,307,322,344,350,374,439,445,450,456],{"__ignoreMap":261},[265,266,269,273,277,280,284],"span",{"class":267,"line":268},"line",1,[265,270,272],{"class":271},"sPARh","import",[265,274,276],{"class":275},"saISM"," { shallowRef, triggerRef } ",[265,278,279],{"class":271},"from",[265,281,283],{"class":282},"sZ8jY"," 'vue'",[265,285,286],{"class":275},";\n",[265,288,290,293,297,300,304],{"class":267,"line":289},2,[265,291,292],{"class":271},"const",[265,294,296],{"class":295},"sPXB4"," rows",[265,298,299],{"class":271}," =",[265,301,303],{"class":302},"smZ65"," shallowRef",[265,305,306],{"class":275},"([]);\n",[265,308,310,313,316,319],{"class":267,"line":309},3,[265,311,312],{"class":271},"async",[265,314,315],{"class":271}," function",[265,317,318],{"class":302}," load",[265,320,321],{"class":275},"() {\n",[265,323,325,328,331,334,337,340],{"class":267,"line":324},4,[265,326,327],{"class":275},"  rows.value ",[265,329,330],{"class":271},"=",[265,332,333],{"class":271}," await",[265,335,336],{"class":302}," fetchRows",[265,338,339],{"class":275},"();          ",[265,341,343],{"class":342},"sjfSM","\u002F\u002F replacing .value triggers updates\n",[265,345,347],{"class":267,"line":346},5,[265,348,349],{"class":275},"}\n",[265,351,353,356,359,362,366,368,371],{"class":267,"line":352},6,[265,354,355],{"class":271},"function",[265,357,358],{"class":302}," updateRow",[265,360,361],{"class":275},"(",[265,363,365],{"class":364},"sQw3B","i",[265,367,187],{"class":275},[265,369,370],{"class":364},"patch",[265,372,373],{"class":275},") {\n",[265,375,377,379,381,384,387,390,393,395,398,401,404,407,410,413,416,419,422,425,427,430,433,436],{"class":267,"line":376},7,[265,378,327],{"class":275},[265,380,330],{"class":271},[265,382,383],{"class":275}," rows.value.",[265,385,386],{"class":302},"map",[265,388,389],{"class":275},"((",[265,391,392],{"class":364},"r",[265,394,187],{"class":275},[265,396,397],{"class":364},"j",[265,399,400],{"class":275},") ",[265,402,403],{"class":271},"=>",[265,405,406],{"class":275}," (j ",[265,408,409],{"class":271},"===",[265,411,412],{"class":275}," i ",[265,414,415],{"class":271},"?",[265,417,418],{"class":275}," { ",[265,420,421],{"class":271},"...",[265,423,424],{"class":275},"r, ",[265,426,421],{"class":271},[265,428,429],{"class":275},"patch } ",[265,431,432],{"class":271},":",[265,434,435],{"class":275}," r));  ",[265,437,438],{"class":342},"\u002F\u002F replace, don't mutate\n",[265,440,442],{"class":267,"line":441},8,[265,443,444],{"class":342},"  \u002F\u002F or: mutate in place, then triggerRef(rows) to notify explicitly\n",[265,446,448],{"class":267,"line":447},9,[265,449,349],{"class":275},[265,451,453],{"class":267,"line":452},10,[265,454,455],{"class":342},"\u002F\u002F trade-off: nested mutations are not tracked; code must replace data or call\n",[265,457,459],{"class":267,"line":458},11,[265,460,461],{"class":342},"\u002F\u002F triggerRef, which is a deliberate change in how the data is updated.\n",[251,463,465],{"id":464},"_2-mark-third-party-instances-raw","2. Mark third-party instances raw",[256,467,469],{"className":258,"code":468,"language":260,"meta":261,"style":261},"import { markRaw, shallowRef } from 'vue';\nconst chart = shallowRef(null);\nonMounted(() => { chart.value = markRaw(new Chart(canvas.value, config)); });\n",[30,470,471,484,503],{"__ignoreMap":261},[265,472,473,475,478,480,482],{"class":267,"line":268},[265,474,272],{"class":271},[265,476,477],{"class":275}," { markRaw, shallowRef } ",[265,479,279],{"class":271},[265,481,283],{"class":282},[265,483,286],{"class":275},[265,485,486,488,491,493,495,497,500],{"class":267,"line":289},[265,487,292],{"class":271},[265,489,490],{"class":295}," chart",[265,492,299],{"class":271},[265,494,303],{"class":302},[265,496,361],{"class":275},[265,498,499],{"class":295},"null",[265,501,502],{"class":275},");\n",[265,504,505,508,511,513,516,518,521,523,526,529],{"class":267,"line":309},[265,506,507],{"class":302},"onMounted",[265,509,510],{"class":275},"(() ",[265,512,403],{"class":271},[265,514,515],{"class":275}," { chart.value ",[265,517,330],{"class":271},[265,519,520],{"class":302}," markRaw",[265,522,361],{"class":275},[265,524,525],{"class":271},"new",[265,527,528],{"class":302}," Chart",[265,530,531],{"class":275},"(canvas.value, config)); });\n",[251,533,535],{"id":534},"_3-replace-deep-watchers","3. Replace deep watchers",[15,537,538,539,542],{},"Watch specific properties or a computed signature (such as an ID list or version number) instead of ",[30,540,541],{},"{ deep: true }"," on large objects.",[251,544,546],{"id":545},"_4-derive-with-computed","4. Derive with computed",[15,548,549,550,553],{},"Use ",[30,551,552],{},"computed"," for filtered, sorted or aggregated views; they cache results until dependencies change.",[15,555,556],{},[67,557,74,560,74,563,74,566,74,568,74,571,74,573,74,578,74,585,74,589,74,594,74,597,74,601,74,605,74,609,74,611,74,615,74,617,74,620,74,623,74,627,74,629,74,631,74,633,74,636,74,640,74,644,74,648,74,650,74,653,74,655,74,658,74,661,74,665,74,667,74,669,74,671,74,674,74,677,74,681,74,683,74,685,74,687,74],{"viewBox":558,"width":70,"role":71,"ariaLabel":559,"style":73},"0 0 760 274","Which Vue reactivity API suits different kinds of state.",[76,561],{"className":562,"x":80,"y":80,"width":70,"height":70,"fill":81},[79],[83,564,565],{},"Choosing a reactivity primitive",[87,567,559],{},[76,569],{"x":91,"y":91,"width":92,"height":570,"rx":94,"fill":95,"stroke":96,"style":97},"272",[99,572,565],{"x":101,"y":102,"fill":96,"style":103},[76,574],{"x":101,"y":114,"width":575,"height":576,"rx":80,"fill":96,"stroke":96,"style":577},"210.0","30.0","fill-opacity:0.06;stroke-opacity:0.4",[99,579,584],{"x":580,"y":581,"fill":96,"style":582,"textAnchor":583},"38.0","75.5","font-size:12.5px;font-weight:700","start","State",[76,586],{"x":587,"y":114,"width":588,"height":576,"rx":80,"fill":96,"stroke":96,"style":577},"238.0","247.0",[99,590,593],{"x":591,"y":581,"fill":96,"style":582,"textAnchor":592},"361.5","middle","Primitive",[76,595],{"x":596,"y":114,"width":588,"height":576,"rx":80,"fill":96,"stroke":96,"style":577},"485.0",[99,598,600],{"x":599,"y":581,"fill":96,"style":582,"textAnchor":592},"608.5","Why",[76,602],{"x":101,"y":603,"width":575,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},"86.0","stroke-opacity:0.35",[99,606,608],{"x":580,"y":607,"fill":96,"style":582,"textAnchor":583},"105.5","Small component state",[76,610],{"x":587,"y":603,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,612,614],{"x":591,"y":607,"fill":96,"style":613,"textAnchor":592},"font-size:12px","ref \u002F reactive",[76,616],{"x":596,"y":603,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,618,619],{"x":599,"y":607,"fill":96,"style":613,"textAnchor":592},"deep reactivity is cheap here",[76,621],{"x":101,"y":622,"width":575,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},"116.0",[99,624,626],{"x":580,"y":625,"fill":96,"style":582,"textAnchor":583},"135.5","Large list replaced on fetch",[76,628],{"x":587,"y":622,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,630,46],{"x":591,"y":625,"fill":96,"style":613,"textAnchor":592},[76,632],{"x":596,"y":622,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,634,635],{"x":599,"y":625,"fill":96,"style":613,"textAnchor":592},"track only the reference",[76,637],{"x":101,"y":638,"width":575,"height":639,"rx":80,"fill":95,"stroke":96,"style":604},"146.0","46.0",[99,641,643],{"x":580,"y":642,"fill":96,"style":582,"textAnchor":583},"165.5","Object with a few changing",[99,645,647],{"x":580,"y":646,"fill":96,"style":582,"textAnchor":583},"181.5","top-level keys",[76,649],{"x":587,"y":638,"width":588,"height":639,"rx":80,"fill":95,"stroke":96,"style":604},[99,651,56],{"x":591,"y":652,"fill":96,"style":613,"textAnchor":592},"173.5",[76,654],{"x":596,"y":638,"width":588,"height":639,"rx":80,"fill":95,"stroke":96,"style":604},[99,656,657],{"x":599,"y":652,"fill":96,"style":613,"textAnchor":592},"track top level only",[76,659],{"x":101,"y":660,"width":575,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},"192.0",[99,662,664],{"x":580,"y":663,"fill":96,"style":582,"textAnchor":583},"211.5","Chart, map or editor instance",[76,666],{"x":587,"y":660,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,668,62],{"x":591,"y":663,"fill":96,"style":613,"textAnchor":592},[76,670],{"x":596,"y":660,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,672,673],{"x":599,"y":663,"fill":96,"style":613,"textAnchor":592},"never proxy external objects",[76,675],{"x":101,"y":676,"width":575,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},"222.0",[99,678,680],{"x":580,"y":679,"fill":96,"style":582,"textAnchor":583},"241.5","Derived view of data",[76,682],{"x":587,"y":676,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,684,552],{"x":591,"y":679,"fill":96,"style":613,"textAnchor":592},[76,686],{"x":596,"y":676,"width":588,"height":576,"rx":80,"fill":95,"stroke":96,"style":604},[99,688,689],{"x":599,"y":679,"fill":96,"style":613,"textAnchor":592},"cached until inputs change",[170,691,693],{"id":692},"verification","Verification",[15,695,696],{},"Profile the same interactions: time in reactivity internals should drop, and rendering or update durations should shrink. Check memory in the Memory panel for large datasets. Verify that updates still appear when data changes — particularly any code that previously mutated nested properties.",[170,698,700],{"id":699},"worked-example-a-logistics-dashboard-in-vue","Worked Example: A Logistics Dashboard in Vue",[15,702,703,704,706,707,709],{},"A logistics dashboard held 12,000 shipment records in a Pinia store with deep reactivity, plus a map library instance in reactive state. Filtering took 450ms; panning the map stuttered because the map instance's internal objects were being proxied. The team moved shipments into a ",[30,705,46],{}," updated by replacement, marked the map instance with ",[30,708,62],{},", replaced a deep watcher with a watcher on a filter signature, and virtualized the table. Filtering dropped to 90ms, map interactions became smooth, and memory use fell by 35%. INP p75 for the dashboard went from 380ms to 140ms.",[15,711,712],{},[67,713,74,716,74,719,74,722,74,724,74,727,74,729,74,735,74,741,74,745,74,748,74,750,74,753,74,756,74,759,74,764,74,768,74,770,74,774,74,776,74,779,74,781,74],{"viewBox":714,"width":70,"role":71,"ariaLabel":715,"style":73},"0 0 760 204","Comparison of how deep and shallow refs behave for a large list of records.",[76,717],{"className":718,"x":80,"y":80,"width":70,"height":70,"fill":81},[79],[83,720,721],{},"Deep ref vs shallowRef for a large list",[87,723,715],{},[76,725],{"x":91,"y":91,"width":92,"height":726,"rx":94,"fill":95,"stroke":96,"style":97},"202",[99,728,721],{"x":101,"y":102,"fill":96,"style":103},[76,730],{"x":101,"y":114,"width":731,"height":732,"rx":733,"fill":132,"stroke":133,"style":734},"340.0","126.0","6","fill-opacity:0.24;stroke-opacity:0.9",[99,736,740],{"x":737,"y":738,"fill":96,"style":739},"42.0","82.0","font-size:14px;font-weight:700","ref(rows)",[99,742,744],{"x":737,"y":743,"fill":96,"style":582},"108.0","•",[99,746,747],{"x":114,"y":743,"fill":96,"style":167,"textAnchor":583},"Every row and field proxied on access",[99,749,744],{"x":737,"y":141,"fill":96,"style":582},[99,751,752],{"x":114,"y":141,"fill":96,"style":167,"textAnchor":583},"Nested mutations tracked and trigger updates",[99,754,744],{"x":737,"y":755,"fill":96,"style":582},"156.0",[99,757,758],{"x":114,"y":755,"fill":96,"style":167,"textAnchor":583},"Higher memory and slower reads",[76,760],{"x":761,"y":114,"width":731,"height":732,"rx":733,"fill":762,"stroke":762,"style":763},"392.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[99,765,767],{"x":766,"y":738,"fill":96,"style":739},"406.0","shallowRef(rows)",[99,769,744],{"x":766,"y":743,"fill":96,"style":582},[99,771,773],{"x":772,"y":743,"fill":96,"style":167,"textAnchor":583},"420.0","Only .value assignment is tracked",[99,775,744],{"x":766,"y":141,"fill":96,"style":582},[99,777,778],{"x":772,"y":141,"fill":96,"style":167,"textAnchor":583},"Replace the array (or triggerRef) to update",[99,780,744],{"x":766,"y":755,"fill":96,"style":582},[99,782,783],{"x":772,"y":755,"fill":96,"style":167,"textAnchor":583},"Plain objects inside, faster reads",[170,785,787],{"id":786},"pinia-and-shallow-state","Pinia and Shallow State",[15,789,790,791,793],{},"Pinia store state is reactive by default. For stores holding large datasets, declare the large part as a ",[30,792,46],{}," in setup stores, and expose actions that replace it. Getters (computed) remain useful for derived views. Keep small UI state (selected IDs, filters) as normal reactive state, so components react to it precisely. This split — shallow for big data, deep for small state — gives most of the benefit with minimal code changes.",[256,795,797],{"className":258,"code":796,"language":260,"meta":261,"style":261},"export const useShipments = defineStore('shipments', () => {\n  const items = shallowRef([]);\n  const filter = ref({ status: 'all' });\n  const visible = computed(() => filter.value.status === 'all' ? items.value : items.value.filter((s) => s.status === filter.value.status));\n  async function load() { items.value = await $fetch('\u002Fapi\u002Fshipments'); }\n  return { items, filter, visible, load };\n});\n\u002F\u002F trade-off: components cannot mutate items[i].status and expect updates; route\n\u002F\u002F changes through actions that replace the array.\n",[30,798,799,828,842,863,918,945,953,958,963],{"__ignoreMap":261},[265,800,801,804,807,810,812,815,817,820,823,825],{"class":267,"line":268},[265,802,803],{"class":271},"export",[265,805,806],{"class":271}," const",[265,808,809],{"class":295}," useShipments",[265,811,299],{"class":271},[265,813,814],{"class":302}," defineStore",[265,816,361],{"class":275},[265,818,819],{"class":282},"'shipments'",[265,821,822],{"class":275},", () ",[265,824,403],{"class":271},[265,826,827],{"class":275}," {\n",[265,829,830,833,836,838,840],{"class":267,"line":289},[265,831,832],{"class":271},"  const",[265,834,835],{"class":295}," items",[265,837,299],{"class":271},[265,839,303],{"class":302},[265,841,306],{"class":275},[265,843,844,846,849,851,854,857,860],{"class":267,"line":309},[265,845,832],{"class":271},[265,847,848],{"class":295}," filter",[265,850,299],{"class":271},[265,852,853],{"class":302}," ref",[265,855,856],{"class":275},"({ status: ",[265,858,859],{"class":282},"'all'",[265,861,862],{"class":275}," });\n",[265,864,865,867,870,872,875,877,879,882,884,887,890,893,895,898,901,903,906,908,910,913,915],{"class":267,"line":324},[265,866,832],{"class":271},[265,868,869],{"class":295}," visible",[265,871,299],{"class":271},[265,873,874],{"class":302}," computed",[265,876,510],{"class":275},[265,878,403],{"class":271},[265,880,881],{"class":275}," filter.value.status ",[265,883,409],{"class":271},[265,885,886],{"class":282}," 'all'",[265,888,889],{"class":271}," ?",[265,891,892],{"class":275}," items.value ",[265,894,432],{"class":271},[265,896,897],{"class":275}," items.value.",[265,899,900],{"class":302},"filter",[265,902,389],{"class":275},[265,904,905],{"class":364},"s",[265,907,400],{"class":275},[265,909,403],{"class":271},[265,911,912],{"class":275}," s.status ",[265,914,409],{"class":271},[265,916,917],{"class":275}," filter.value.status));\n",[265,919,920,923,925,927,930,932,934,937,939,942],{"class":267,"line":346},[265,921,922],{"class":271},"  async",[265,924,315],{"class":271},[265,926,318],{"class":302},[265,928,929],{"class":275},"() { items.value ",[265,931,330],{"class":271},[265,933,333],{"class":271},[265,935,936],{"class":302}," $fetch",[265,938,361],{"class":275},[265,940,941],{"class":282},"'\u002Fapi\u002Fshipments'",[265,943,944],{"class":275},"); }\n",[265,946,947,950],{"class":267,"line":352},[265,948,949],{"class":271},"  return",[265,951,952],{"class":275}," { items, filter, visible, load };\n",[265,954,955],{"class":267,"line":376},[265,956,957],{"class":275},"});\n",[265,959,960],{"class":267,"line":441},[265,961,962],{"class":342},"\u002F\u002F trade-off: components cannot mutate items[i].status and expect updates; route\n",[265,964,965],{"class":267,"line":447},[265,966,967],{"class":342},"\u002F\u002F changes through actions that replace the array.\n",[170,969,971],{"id":970},"finding-reactivity-hotspots","Finding Reactivity Hotspots",[15,973,974,975,187,978,187,980,982],{},"Vue DevTools' performance timeline shows component render and update times, and the browser's Performance panel reveals where time goes inside them. Reactivity overhead appears as many small calls in Vue's reactivity package (",[30,976,977],{},"get",[30,979,186],{},[30,981,190],{},") under component renders and computed evaluations. Record the slow interaction, then use the Bottom-Up view grouped by function: if reactivity internals rank highly, look at the data those components read. Large arrays of objects read in templates and computed values are the usual source. Converting just those to shallow references typically removes most of the overhead, without touching the rest of the application's state.",[170,984,986],{"id":985},"common-mistakes","Common Mistakes",[175,988,989,999,1005,1011],{},[178,990,991,994,995,998],{},[42,992,993],{},"Mutating nested properties of a shallowRef."," No update happens; replace or call ",[30,996,997],{},"triggerRef",".",[178,1000,1001,1004],{},[42,1002,1003],{},"Reactive third-party instances."," Slow and sometimes broken.",[178,1006,1007,1010],{},[42,1008,1009],{},"Deep watchers on big data."," Expensive traversal on every change.",[178,1012,1013,1016],{},[42,1014,1015],{},"Using shallow everywhere."," Small state does not need it and becomes harder to update.",[170,1018,1020],{"id":1019},"edge-cases","Edge Cases",[15,1022,1023,1026],{},[42,1024,1025],{},"Forms editing large objects."," Copy the item being edited into normal reactive state, then write it back.",[15,1028,1029,1032],{},[42,1030,1031],{},"SSR payloads."," Shallow refs serialise like normal refs; hydration restores the data.",[15,1034,1035,1038],{},[42,1036,1037],{},"DevTools visibility."," Shallow data is still visible in Vue DevTools, but nested changes are not tracked.",[15,1040,1041,1044],{},[42,1042,1043],{},"Composables."," Return shallowRefs for large data from composables, and document the replace-not-mutate contract.",[170,1046,1048],{"id":1047},"faq","FAQ",[1050,1051,1054,1058],"details",{"className":1052},[1053],"faq-item",[1055,1056,1057],"summary",{},"What does shallowRef do?",[15,1059,1060,1061,1063,1064,1066],{},"It makes only the ",[30,1062,50],{}," assignment reactive. Nested properties are not tracked, so replacing ",[30,1065,50],{}," triggers updates while nested mutations do not.",[1050,1068,1070,1073],{"className":1069},[1053],[1055,1071,1072],{},"When should I use markRaw?",[15,1074,1075],{},"For objects that should never be reactive: third-party library instances, large immutable data, and class instances with internal state.",[1050,1077,1079,1082],{"className":1078},[1053],[1055,1080,1081],{},"Does shallowRef break reactivity in templates?",[15,1083,1084,1085,1087],{},"No. Templates update when ",[30,1086,50],{}," is replaced. Only nested mutations are untracked.",[1050,1089,1091,1094],{"className":1090},[1053],[1055,1092,1093],{},"What is triggerRef?",[15,1095,1096],{},"A function that manually triggers effects depending on a shallowRef, useful after mutating its contents in place.",[1050,1098,1100,1103],{"className":1099},[1053],[1055,1101,1102],{},"Is deep reactivity slow?",[15,1104,1105],{},"Not for normal component state. It becomes measurable on large datasets and complex objects.",[1050,1107,1109,1112],{"className":1108},[1053],[1055,1110,1111],{},"Does Vue 3 create proxies eagerly?",[15,1113,1114],{},"Proxies for nested objects are created lazily on access, but large templates and computed values access many items, so the cost still appears on big lists.",[1050,1116,1118,1121],{"className":1117},[1053],[1055,1119,1120],{},"Can I use shallowRef in Pinia?",[15,1122,1123],{},"Yes, in setup stores. Expose actions that replace the data rather than mutating it.",[1050,1125,1127,1130],{"className":1126},[1053],[1055,1128,1129],{},"Does v-memo help too?",[15,1131,1132,1133,1136],{},"Yes, for large lists where most items do not change between renders: ",[30,1134,1135],{},"v-memo"," skips re-rendering items whose memo dependencies are unchanged.",[1050,1138,1140,1143],{"className":1139},[1053],[1055,1141,1142],{},"Is shallowReactive different from shallowRef?",[15,1144,1145,1147,1148,1150,1151,1153],{},[30,1146,56],{}," makes an object's top-level properties reactive without converting nested values; ",[30,1149,46],{}," tracks only the ",[30,1152,50],{}," assignment. Use whichever matches how the data changes.",[1050,1155,1157,1160],{"className":1156},[1053],[1055,1158,1159],{},"Do computed values add overhead?",[15,1161,1162],{},"Very little. They cache results until dependencies change, which usually saves more than it costs for derived data.",[1050,1164,1166,1169],{"className":1165},[1053],[1055,1167,1168],{},"Does Vapor mode change this?",[15,1170,1171],{},"Compiler-level improvements reduce rendering overhead, but large deep-reactive datasets still cost time and memory; shallow state remains good practice.",[1050,1173,1175,1178],{"className":1174},[1053],[1055,1176,1177],{},"Should props be shallow too?",[15,1179,1180],{},"Props are already shallowly reactive in child components. Avoid deep-mutating props; pass replaced objects instead.",[170,1182,1184],{"id":1183},"related","Related",[175,1186,1187,1194,1201],{},[178,1188,1189,1193],{},[19,1190,1192],{"href":1191},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Freducing-nuxt-payload-size\u002F","Reducing Nuxt payload size"," — less data to make reactive.",[178,1195,1196,1200],{},[19,1197,1199],{"href":1198},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Fvirtualizing-long-lists-in-react\u002F","Virtualizing long lists in React"," — virtualization concepts apply to Vue.",[178,1202,1203,1206],{},[19,1204,1205],{"href":21},"Vue & Nuxt performance"," — the topic overview.",[1208,1209,1211],"script",{"type":1210},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Cut Vue Reactivity Cost with shallowRef\",\n  \"description\": \"How Vue's deep reactivity creates overhead on large data, and how shallowRef, shallowReactive, markRaw and computed values reduce it without losing reactivity where it matters.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Use shallowRef for large, replaced data\",\n      \"text\": \"Use shallowRef for large, replaced data\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Mark third-party instances raw\",\n      \"text\": \"Mark third-party instances raw\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Replace deep watchers\",\n      \"text\": \"Watch specific properties or a computed signature (such as an ID list or version number) instead of { deep: true } on large objects.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Derive with computed\",\n      \"text\": \"Use computed for filtered, sorted or aggregated views; they cache results until dependencies change.\"\n    }\n  ]\n}\n",[1208,1213,1214],{"type":1210},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Cut Vue Reactivity Cost with shallowRef\",\n  \"description\": \"How Vue's deep reactivity creates overhead on large data, and how shallowRef, shallowReactive, markRaw and computed values reduce it without losing reactivity where it matters.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002F\"\n  }\n}\n",[1208,1216,1217],{"type":1210},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Framework Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Vue & Nuxt Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Cutting Vue Reactivity Cost with shallowRef\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002F\"\n    }\n  ]\n}\n",[1219,1220,1221],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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 .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);}",{"title":261,"searchDepth":289,"depth":289,"links":1223},[1224,1225,1226,1232,1233,1234,1235,1236,1237,1238,1239],{"id":172,"depth":289,"text":173},{"id":220,"depth":289,"text":221},{"id":248,"depth":289,"text":249,"children":1227},[1228,1229,1230,1231],{"id":253,"depth":309,"text":254},{"id":464,"depth":309,"text":465},{"id":534,"depth":309,"text":535},{"id":545,"depth":309,"text":546},{"id":692,"depth":289,"text":693},{"id":699,"depth":289,"text":700},{"id":786,"depth":289,"text":787},{"id":970,"depth":289,"text":971},{"id":985,"depth":289,"text":986},{"id":1019,"depth":289,"text":1020},{"id":1047,"depth":289,"text":1048},{"id":1183,"depth":289,"text":1184},"How Vue's deep reactivity creates overhead on large data, and how shallowRef, shallowReactive, markRaw and computed values reduce it without losing reactivity where it matters.","md",{"slug":1243,"type":1244,"breadcrumb":1245,"datePublished":1253,"dateModified":1253},"cutting-vue-reactivity-cost-with-shallowref","article",[1246,1249,1250,1251],{"name":1247,"url":1248},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1252},"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002F","2026-10-06",true,"\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref",{"title":1257,"description":1258},"Cutting Vue Reactivity Cost with shallowRef and markRaw","Deep reactive proxies over large data slow Vue rendering and updates. Use shallowRef, shallowReactive and markRaw for big datasets and third-party objects.","framework-performance\u002Fvue-and-nuxt-performance\u002Fcutting-vue-reactivity-cost-with-shallowref\u002Findex","SkRnbssiJxGH0p7HLvhNrjcr6YeZ-qvWG2H9evP0SbY",[1262,1265],{"title":22,"path":1263,"stem":1264,"children":-1},"\u002Fframework-performance\u002Fvue-and-nuxt-performance","framework-performance\u002Fvue-and-nuxt-performance\u002Findex",{"title":1266,"path":1267,"stem":1268,"children":-1},"Lazy Hydration in Nuxt","\u002Fframework-performance\u002Fvue-and-nuxt-performance\u002Flazy-hydration-in-nuxt","framework-performance\u002Fvue-and-nuxt-performance\u002Flazy-hydration-in-nuxt\u002Findex",1791308076432]