[{"data":1,"prerenderedAt":9276},["ShallowReactive",2],{"blog-post-en-\u002Fblog\u002Fwhy-clean-code-still-matters-in-2025":3,"blog-list-en":1624},{"id":4,"title":5,"body":6,"category":1607,"date":1608,"description":1609,"draft":1610,"extension":1611,"meta":1612,"navigation":300,"path":1613,"seo":1614,"stem":1615,"tags":1616,"translationKey":1621,"videoId":1622,"__hash__":1623},"blog_en\u002Fblog\u002Fwhy-clean-code-still-matters-in-2025.md","Why clean code still matters: eight principles worth keeping",{"type":7,"value":8,"toc":1595},"minimark",[9,14,18,21,24,28,31,99,102,142,145,149,152,197,200,353,356,360,363,490,493,552,556,559,562,582,585,629,637,641,644,647,699,702,890,894,905,908,981,984,1042,1045,1049,1052,1055,1290,1294,1297,1455,1458,1578,1581,1585,1588,1591],[10,11,13],"h2",{"id":12},"ai-and-the-case-for-clean-code","AI and the case for clean code",[15,16,17],"p",{},"AI is now everywhere in this job, from generating snippets to writing whole functions, and it's reasonable to ask whether writing clean code still matters when a model can refactor it for you.",[15,19,20],{},"I've gone back and forth on this myself. AI is a capable assistant, but it isn't a mind reader. It can optimise, refactor and suggest clearer alternatives, and all of that works better when it starts from something sound. Give a good chef excellent ingredients and a chaotic recipe, and the dish still comes out wrong.",[15,22,23],{},"Clean code is about clarity, maintainability and how a project ages. It also happens to make your AI tools more effective. Here are eight principles that hold up.",[10,25,27],{"id":26},"_1-meaningful-names","1. Meaningful names",[15,29,30],{},"Clarity starts here. Imagine a story where every character is called \"a\", \"b\" or \"c\". Code is no different. Variables, functions and classes should say what they do or represent before you read a single line of their body.",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-javascript shiki shiki-themes github-dark-default","let d; \u002F\u002F What is 'd'? Days? Data?\nfunction p(a, b) {\n  \u002F* ... *\u002F\n} \u002F\u002F What does 'p' do? What are 'a' and 'b'?\n","javascript","",[39,40,41,58,84,90],"code",{"__ignoreMap":37},[42,43,46,50,54],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"suJrU","let",[42,51,53],{"class":52},"sZEs4"," d; ",[42,55,57],{"class":56},"sH3jZ","\u002F\u002F What is 'd'? Days? Data?\n",[42,59,61,64,68,71,75,78,81],{"class":44,"line":60},2,[42,62,63],{"class":48},"function",[42,65,67],{"class":66},"sc3cj"," p",[42,69,70],{"class":52},"(",[42,72,74],{"class":73},"sQhOw","a",[42,76,77],{"class":52},", ",[42,79,80],{"class":73},"b",[42,82,83],{"class":52},") {\n",[42,85,87],{"class":44,"line":86},3,[42,88,89],{"class":56},"  \u002F* ... *\u002F\n",[42,91,93,96],{"class":44,"line":92},4,[42,94,95],{"class":52},"} ",[42,97,98],{"class":56},"\u002F\u002F What does 'p' do? What are 'a' and 'b'?\n",[15,100,101],{},"Compare that with:",[32,103,105],{"className":34,"code":104,"language":36,"meta":37,"style":37},"let daysSinceLastLogin;\nfunction calculateTotalPrice(quantity, unitPrice) {\n  \u002F* ... *\u002F\n}\n",[39,106,107,114,133,137],{"__ignoreMap":37},[42,108,109,111],{"class":44,"line":45},[42,110,49],{"class":48},[42,112,113],{"class":52}," daysSinceLastLogin;\n",[42,115,116,118,121,123,126,128,131],{"class":44,"line":60},[42,117,63],{"class":48},[42,119,120],{"class":66}," calculateTotalPrice",[42,122,70],{"class":52},[42,124,125],{"class":73},"quantity",[42,127,77],{"class":52},[42,129,130],{"class":73},"unitPrice",[42,132,83],{"class":52},[42,134,135],{"class":44,"line":86},[42,136,89],{"class":56},[42,138,139],{"class":44,"line":92},[42,140,141],{"class":52},"}\n",[15,143,144],{},"Good names work as documentation inside the code. They cut the mental load of reading and make debugging faster. A model reading your code benefits from the same clarity you do.",[10,146,148],{"id":147},"_2-a-function-should-do-one-thing","2. A function should do one thing",[15,150,151],{},"Every function should have one reason to change. When a function does too much, it gets harder to test, harder to follow, and more likely to break when you touch it.",[32,153,155],{"className":34,"code":154,"language":36,"meta":37,"style":37},"function processOrder(order) {\n  \u002F\u002F Validate order\n  \u002F\u002F Save to database\n  \u002F\u002F Send confirmation email\n  \u002F\u002F Update inventory\n}\n",[39,156,157,171,176,181,186,192],{"__ignoreMap":37},[42,158,159,161,164,166,169],{"class":44,"line":45},[42,160,63],{"class":48},[42,162,163],{"class":66}," processOrder",[42,165,70],{"class":52},[42,167,168],{"class":73},"order",[42,170,83],{"class":52},[42,172,173],{"class":44,"line":60},[42,174,175],{"class":56},"  \u002F\u002F Validate order\n",[42,177,178],{"class":44,"line":86},[42,179,180],{"class":56},"  \u002F\u002F Save to database\n",[42,182,183],{"class":44,"line":92},[42,184,185],{"class":56},"  \u002F\u002F Send confirmation email\n",[42,187,189],{"class":44,"line":188},5,[42,190,191],{"class":56},"  \u002F\u002F Update inventory\n",[42,193,195],{"class":44,"line":194},6,[42,196,141],{"class":52},[15,198,199],{},"Split it up instead:",[32,201,203],{"className":34,"code":202,"language":36,"meta":37,"style":37},"function validateOrder(order) {\n  \u002F* ... *\u002F\n}\nfunction saveOrder(order) {\n  \u002F* ... *\u002F\n}\nfunction sendConfirmationEmail(order) {\n  \u002F* ... *\u002F\n}\nfunction updateInventory(order) {\n  \u002F* ... *\u002F\n}\n\nfunction processOrder(order) {\n  validateOrder(order);\n  saveOrder(order);\n  sendConfirmationEmail(order);\n  updateInventory(order);\n}\n",[39,204,205,218,222,226,239,243,247,261,266,271,285,290,295,302,315,324,332,340,348],{"__ignoreMap":37},[42,206,207,209,212,214,216],{"class":44,"line":45},[42,208,63],{"class":48},[42,210,211],{"class":66}," validateOrder",[42,213,70],{"class":52},[42,215,168],{"class":73},[42,217,83],{"class":52},[42,219,220],{"class":44,"line":60},[42,221,89],{"class":56},[42,223,224],{"class":44,"line":86},[42,225,141],{"class":52},[42,227,228,230,233,235,237],{"class":44,"line":92},[42,229,63],{"class":48},[42,231,232],{"class":66}," saveOrder",[42,234,70],{"class":52},[42,236,168],{"class":73},[42,238,83],{"class":52},[42,240,241],{"class":44,"line":188},[42,242,89],{"class":56},[42,244,245],{"class":44,"line":194},[42,246,141],{"class":52},[42,248,250,252,255,257,259],{"class":44,"line":249},7,[42,251,63],{"class":48},[42,253,254],{"class":66}," sendConfirmationEmail",[42,256,70],{"class":52},[42,258,168],{"class":73},[42,260,83],{"class":52},[42,262,264],{"class":44,"line":263},8,[42,265,89],{"class":56},[42,267,269],{"class":44,"line":268},9,[42,270,141],{"class":52},[42,272,274,276,279,281,283],{"class":44,"line":273},10,[42,275,63],{"class":48},[42,277,278],{"class":66}," updateInventory",[42,280,70],{"class":52},[42,282,168],{"class":73},[42,284,83],{"class":52},[42,286,288],{"class":44,"line":287},11,[42,289,89],{"class":56},[42,291,293],{"class":44,"line":292},12,[42,294,141],{"class":52},[42,296,298],{"class":44,"line":297},13,[42,299,301],{"emptyLinePlaceholder":300},true,"\n",[42,303,305,307,309,311,313],{"class":44,"line":304},14,[42,306,63],{"class":48},[42,308,163],{"class":66},[42,310,70],{"class":52},[42,312,168],{"class":73},[42,314,83],{"class":52},[42,316,318,321],{"class":44,"line":317},15,[42,319,320],{"class":66},"  validateOrder",[42,322,323],{"class":52},"(order);\n",[42,325,327,330],{"class":44,"line":326},16,[42,328,329],{"class":66},"  saveOrder",[42,331,323],{"class":52},[42,333,335,338],{"class":44,"line":334},17,[42,336,337],{"class":66},"  sendConfirmationEmail",[42,339,323],{"class":52},[42,341,343,346],{"class":44,"line":342},18,[42,344,345],{"class":66},"  updateInventory",[42,347,323],{"class":52},[42,349,351],{"class":44,"line":350},19,[42,352,141],{"class":52},[15,354,355],{},"Breaking a complex task into focused pieces makes the code modular and considerably easier to read later.",[10,357,359],{"id":358},"_3-dont-repeat-yourself","3. Don't repeat yourself",[15,361,362],{},"If you're writing the same logic twice, pull it into a function or a reusable component. Duplication quietly destroys maintainability: when a bug needs fixing, you have to fix it in several places, and you will eventually miss one.",[32,364,366],{"className":34,"code":365,"language":36,"meta":37,"style":37},"\u002F\u002F In file A\nfunction calculateDiscountA(price) {\n  if (price > 100) return price * 0.9;\n  return price;\n}\n\n\u002F\u002F In file B (same logic)\nfunction calculateDiscountB(amount) {\n  if (amount > 100) return amount * 0.9;\n  return amount;\n}\n",[39,367,368,373,387,420,428,432,436,441,455,479,486],{"__ignoreMap":37},[42,369,370],{"class":44,"line":45},[42,371,372],{"class":56},"\u002F\u002F In file A\n",[42,374,375,377,380,382,385],{"class":44,"line":60},[42,376,63],{"class":48},[42,378,379],{"class":66}," calculateDiscountA",[42,381,70],{"class":52},[42,383,384],{"class":73},"price",[42,386,83],{"class":52},[42,388,389,392,395,398,402,405,408,411,414,417],{"class":44,"line":86},[42,390,391],{"class":48},"  if",[42,393,394],{"class":52}," (price ",[42,396,397],{"class":48},">",[42,399,401],{"class":400},"sFSAA"," 100",[42,403,404],{"class":52},") ",[42,406,407],{"class":48},"return",[42,409,410],{"class":52}," price ",[42,412,413],{"class":48},"*",[42,415,416],{"class":400}," 0.9",[42,418,419],{"class":52},";\n",[42,421,422,425],{"class":44,"line":92},[42,423,424],{"class":48},"  return",[42,426,427],{"class":52}," price;\n",[42,429,430],{"class":44,"line":188},[42,431,141],{"class":52},[42,433,434],{"class":44,"line":194},[42,435,301],{"emptyLinePlaceholder":300},[42,437,438],{"class":44,"line":249},[42,439,440],{"class":56},"\u002F\u002F In file B (same logic)\n",[42,442,443,445,448,450,453],{"class":44,"line":263},[42,444,63],{"class":48},[42,446,447],{"class":66}," calculateDiscountB",[42,449,70],{"class":52},[42,451,452],{"class":73},"amount",[42,454,83],{"class":52},[42,456,457,459,462,464,466,468,470,473,475,477],{"class":44,"line":268},[42,458,391],{"class":48},[42,460,461],{"class":52}," (amount ",[42,463,397],{"class":48},[42,465,401],{"class":400},[42,467,404],{"class":52},[42,469,407],{"class":48},[42,471,472],{"class":52}," amount ",[42,474,413],{"class":48},[42,476,416],{"class":400},[42,478,419],{"class":52},[42,480,481,483],{"class":44,"line":273},[42,482,424],{"class":48},[42,484,485],{"class":52}," amount;\n",[42,487,488],{"class":44,"line":287},[42,489,141],{"class":52},[15,491,492],{},"One implementation instead:",[32,494,496],{"className":34,"code":495,"language":36,"meta":37,"style":37},"function applyStandardDiscount(price) {\n  if (price > 100) return price * 0.9;\n  return price;\n}\n\n\u002F\u002F Now both A and B can use applyStandardDiscount\n",[39,497,498,511,533,539,543,547],{"__ignoreMap":37},[42,499,500,502,505,507,509],{"class":44,"line":45},[42,501,63],{"class":48},[42,503,504],{"class":66}," applyStandardDiscount",[42,506,70],{"class":52},[42,508,384],{"class":73},[42,510,83],{"class":52},[42,512,513,515,517,519,521,523,525,527,529,531],{"class":44,"line":60},[42,514,391],{"class":48},[42,516,394],{"class":52},[42,518,397],{"class":48},[42,520,401],{"class":400},[42,522,404],{"class":52},[42,524,407],{"class":48},[42,526,410],{"class":52},[42,528,413],{"class":48},[42,530,416],{"class":400},[42,532,419],{"class":52},[42,534,535,537],{"class":44,"line":86},[42,536,424],{"class":48},[42,538,427],{"class":52},[42,540,541],{"class":44,"line":92},[42,542,141],{"class":52},[42,544,545],{"class":44,"line":188},[42,546,301],{"emptyLinePlaceholder":300},[42,548,549],{"class":44,"line":194},[42,550,551],{"class":56},"\u002F\u002F Now both A and B can use applyStandardDiscount\n",[10,553,555],{"id":554},"_4-comment-why-not-what","4. Comment why, not what",[15,557,558],{},"You may have heard that good code needs no comments, and that's partly true. Clear code with meaningful names makes a lot of comments redundant.",[15,560,561],{},"What you do need to record is why something was done a particular way, or why a workaround exists at all. That kind of comment is worth a lot.",[32,563,565],{"className":34,"code":564,"language":36,"meta":37,"style":37},"\u002F\u002F Increment the counter by 1\ncounter++;\n",[39,566,567,572],{"__ignoreMap":37},[42,568,569],{"class":44,"line":45},[42,570,571],{"class":56},"\u002F\u002F Increment the counter by 1\n",[42,573,574,577,580],{"class":44,"line":60},[42,575,576],{"class":52},"counter",[42,578,579],{"class":48},"++",[42,581,419],{"class":52},[15,583,584],{},"That one adds nothing. This one does:",[32,586,588],{"className":34,"code":587,"language":36,"meta":37,"style":37},"\u002F\u002F This specific regex is used to handle legacy user IDs\n\u002F\u002F which sometimes contain leading zeros and special characters.\nconst userIdRegex = \u002F^[0-9a-zA-Z\\-_]+$\u002F;\n",[39,589,590,595,600],{"__ignoreMap":37},[42,591,592],{"class":44,"line":45},[42,593,594],{"class":56},"\u002F\u002F This specific regex is used to handle legacy user IDs\n",[42,596,597],{"class":44,"line":60},[42,598,599],{"class":56},"\u002F\u002F which sometimes contain leading zeros and special characters.\n",[42,601,602,605,608,611,615,618,621,624,627],{"class":44,"line":86},[42,603,604],{"class":48},"const",[42,606,607],{"class":400}," userIdRegex",[42,609,610],{"class":48}," =",[42,612,614],{"class":613},"s9uIt"," \u002F",[42,616,617],{"class":48},"^",[42,619,620],{"class":400},"[0-9a-zA-Z\\-_]",[42,622,623],{"class":48},"+$",[42,625,626],{"class":613},"\u002F",[42,628,419],{"class":52},[15,630,631,632,636],{},"If a comment is needed to explain ",[633,634,635],"em",{},"what"," the code does, that's usually a sign the code could be clearer.",[10,638,640],{"id":639},"_5-handle-errors","5. Handle errors",[15,642,643],{},"Networks fail, users type nonsense, APIs return something unexpected. Ignoring that leads to unpredictable behaviour and users who can't tell what went wrong.",[15,645,646],{},"How much error handling is enough depends on context, but a reasonable rule is to handle errors where you can either recover or say something useful to the user or the system.",[32,648,650],{"className":34,"code":649,"language":36,"meta":37,"style":37},"function getUserData(userId) {\n  const data = api.fetch(userId); \u002F\u002F What if api.fetch fails?\n  return data.name;\n}\n",[39,651,652,666,688,695],{"__ignoreMap":37},[42,653,654,656,659,661,664],{"class":44,"line":45},[42,655,63],{"class":48},[42,657,658],{"class":66}," getUserData",[42,660,70],{"class":52},[42,662,663],{"class":73},"userId",[42,665,83],{"class":52},[42,667,668,671,674,676,679,682,685],{"class":44,"line":60},[42,669,670],{"class":48},"  const",[42,672,673],{"class":400}," data",[42,675,610],{"class":48},[42,677,678],{"class":52}," api.",[42,680,681],{"class":66},"fetch",[42,683,684],{"class":52},"(userId); ",[42,686,687],{"class":56},"\u002F\u002F What if api.fetch fails?\n",[42,689,690,692],{"class":44,"line":86},[42,691,424],{"class":48},[42,693,694],{"class":52}," data.name;\n",[42,696,697],{"class":44,"line":92},[42,698,141],{"class":52},[15,700,701],{},"Something closer to this:",[32,703,705],{"className":34,"code":704,"language":36,"meta":37,"style":37},"async function getUserData(userId) {\n  try {\n    const response = await api.fetch(userId);\n    if (!response.ok) {\n      throw new Error(`Failed to fetch user data: ${response.statusText}`);\n    }\n    const data = await response.json();\n    return data.name;\n  } catch (error) {\n    console.error(`Error fetching user ${userId}:`, error);\n    \u002F\u002F Potentially return a default value, or re-throw a custom error\n    throw new CustomApplicationError(\"User data unavailable\");\n  }\n}\n",[39,706,707,723,731,751,765,796,801,820,827,838,859,864,881,886],{"__ignoreMap":37},[42,708,709,712,715,717,719,721],{"class":44,"line":45},[42,710,711],{"class":48},"async",[42,713,714],{"class":48}," function",[42,716,658],{"class":66},[42,718,70],{"class":52},[42,720,663],{"class":73},[42,722,83],{"class":52},[42,724,725,728],{"class":44,"line":60},[42,726,727],{"class":48},"  try",[42,729,730],{"class":52}," {\n",[42,732,733,736,739,741,744,746,748],{"class":44,"line":86},[42,734,735],{"class":48},"    const",[42,737,738],{"class":400}," response",[42,740,610],{"class":48},[42,742,743],{"class":48}," await",[42,745,678],{"class":52},[42,747,681],{"class":66},[42,749,750],{"class":52},"(userId);\n",[42,752,753,756,759,762],{"class":44,"line":92},[42,754,755],{"class":48},"    if",[42,757,758],{"class":52}," (",[42,760,761],{"class":48},"!",[42,763,764],{"class":52},"response.ok) {\n",[42,766,767,770,773,776,778,781,784,787,790,793],{"class":44,"line":188},[42,768,769],{"class":48},"      throw",[42,771,772],{"class":48}," new",[42,774,775],{"class":66}," Error",[42,777,70],{"class":52},[42,779,780],{"class":613},"`Failed to fetch user data: ${",[42,782,783],{"class":52},"response",[42,785,786],{"class":613},".",[42,788,789],{"class":52},"statusText",[42,791,792],{"class":613},"}`",[42,794,795],{"class":52},");\n",[42,797,798],{"class":44,"line":194},[42,799,800],{"class":52},"    }\n",[42,802,803,805,807,809,811,814,817],{"class":44,"line":249},[42,804,735],{"class":48},[42,806,673],{"class":400},[42,808,610],{"class":48},[42,810,743],{"class":48},[42,812,813],{"class":52}," response.",[42,815,816],{"class":66},"json",[42,818,819],{"class":52},"();\n",[42,821,822,825],{"class":44,"line":263},[42,823,824],{"class":48},"    return",[42,826,694],{"class":52},[42,828,829,832,835],{"class":44,"line":268},[42,830,831],{"class":52},"  } ",[42,833,834],{"class":48},"catch",[42,836,837],{"class":52}," (error) {\n",[42,839,840,843,846,848,851,853,856],{"class":44,"line":273},[42,841,842],{"class":52},"    console.",[42,844,845],{"class":66},"error",[42,847,70],{"class":52},[42,849,850],{"class":613},"`Error fetching user ${",[42,852,663],{"class":52},[42,854,855],{"class":613},"}:`",[42,857,858],{"class":52},", error);\n",[42,860,861],{"class":44,"line":287},[42,862,863],{"class":56},"    \u002F\u002F Potentially return a default value, or re-throw a custom error\n",[42,865,866,869,871,874,876,879],{"class":44,"line":292},[42,867,868],{"class":48},"    throw",[42,870,772],{"class":48},[42,872,873],{"class":66}," CustomApplicationError",[42,875,70],{"class":52},[42,877,878],{"class":613},"\"User data unavailable\"",[42,880,795],{"class":52},[42,882,883],{"class":44,"line":297},[42,884,885],{"class":52},"  }\n",[42,887,888],{"class":44,"line":304},[42,889,141],{"class":52},[10,891,893],{"id":892},"_6-stay-consistent","6. Stay consistent",[15,895,896,897,900,901,904],{},"Consistency matters most on larger teams: the same naming conventions, formatting, architectural patterns and design choices across the codebase. When one part uses ",[39,898,899],{},"camelCase"," and another uses ",[39,902,903],{},"snake_case",", reading gets harder for no reason.",[15,906,907],{},"Linters and formatters like ESLint and Prettier handle most of this automatically.",[32,909,911],{"className":34,"code":910,"language":36,"meta":37,"style":37},"let firstName;\nconst user_id = 123;\nfunction getProducts() {\n  \u002F* ... *\u002F\n}\nconst fetchOrders = async () => {\n  \u002F* ... *\u002F\n};\n",[39,912,913,920,934,944,948,952,972,976],{"__ignoreMap":37},[42,914,915,917],{"class":44,"line":45},[42,916,49],{"class":48},[42,918,919],{"class":52}," firstName;\n",[42,921,922,924,927,929,932],{"class":44,"line":60},[42,923,604],{"class":48},[42,925,926],{"class":400}," user_id",[42,928,610],{"class":48},[42,930,931],{"class":400}," 123",[42,933,419],{"class":52},[42,935,936,938,941],{"class":44,"line":86},[42,937,63],{"class":48},[42,939,940],{"class":66}," getProducts",[42,942,943],{"class":52},"() {\n",[42,945,946],{"class":44,"line":92},[42,947,89],{"class":56},[42,949,950],{"class":44,"line":188},[42,951,141],{"class":52},[42,953,954,956,959,961,964,967,970],{"class":44,"line":194},[42,955,604],{"class":48},[42,957,958],{"class":66}," fetchOrders",[42,960,610],{"class":48},[42,962,963],{"class":48}," async",[42,965,966],{"class":52}," () ",[42,968,969],{"class":48},"=>",[42,971,730],{"class":52},[42,973,974],{"class":44,"line":249},[42,975,89],{"class":56},[42,977,978],{"class":44,"line":263},[42,979,980],{"class":52},"};\n",[15,982,983],{},"Versus:",[32,985,987],{"className":34,"code":986,"language":36,"meta":37,"style":37},"let firstName;\nconst userId = 123;\nfunction getProducts() {\n  \u002F* ... *\u002F\n}\nasync function fetchOrders() {\n  \u002F* ... *\u002F\n}\n",[39,988,989,995,1008,1016,1020,1024,1034,1038],{"__ignoreMap":37},[42,990,991,993],{"class":44,"line":45},[42,992,49],{"class":48},[42,994,919],{"class":52},[42,996,997,999,1002,1004,1006],{"class":44,"line":60},[42,998,604],{"class":48},[42,1000,1001],{"class":400}," userId",[42,1003,610],{"class":48},[42,1005,931],{"class":400},[42,1007,419],{"class":52},[42,1009,1010,1012,1014],{"class":44,"line":86},[42,1011,63],{"class":48},[42,1013,940],{"class":66},[42,1015,943],{"class":52},[42,1017,1018],{"class":44,"line":92},[42,1019,89],{"class":56},[42,1021,1022],{"class":44,"line":188},[42,1023,141],{"class":52},[42,1025,1026,1028,1030,1032],{"class":44,"line":194},[42,1027,711],{"class":48},[42,1029,714],{"class":48},[42,1031,958],{"class":66},[42,1033,943],{"class":52},[42,1035,1036],{"class":44,"line":249},[42,1037,89],{"class":56},[42,1039,1040],{"class":44,"line":263},[42,1041,141],{"class":52},[15,1043,1044],{},"A consistent codebase reads as though one person wrote it, even when fifty people did. That cuts the overhead of moving between files.",[10,1046,1048],{"id":1047},"_7-write-tests","7. Write tests",[15,1050,1051],{},"Testing can feel like a chore. It's also what lets you refactor and ship without holding your breath. With AI-assisted coding this matters more, not less: tests are how you find out whether generated code integrates correctly and hasn't broken something elsewhere.",[15,1053,1054],{},"Picture making a small change and watching an unrelated part of the application fall over. Without tests, that's a long debugging session. With tests, the failing case points at the problem.",[32,1056,1058],{"className":34,"code":1057,"language":36,"meta":37,"style":37},"\u002F\u002F function to test\nfunction add(a, b) {\n  return a + b;\n}\n\n\u002F\u002F test file\ndescribe(\"add function\", () => {\n  test(\"should add two positive numbers correctly\", () => {\n    expect(add(1, 2)).toBe(3);\n  });\n\n  test(\"should handle negative numbers\", () => {\n    expect(add(-1, 5)).toBe(4);\n  });\n\n  test(\"should return zero when adding opposite numbers\", () => {\n    expect(add(-3, 3)).toBe(0);\n  });\n});\n",[39,1059,1060,1065,1082,1095,1099,1103,1108,1125,1141,1174,1179,1183,1198,1229,1233,1237,1252,1281,1285],{"__ignoreMap":37},[42,1061,1062],{"class":44,"line":45},[42,1063,1064],{"class":56},"\u002F\u002F function to test\n",[42,1066,1067,1069,1072,1074,1076,1078,1080],{"class":44,"line":60},[42,1068,63],{"class":48},[42,1070,1071],{"class":66}," add",[42,1073,70],{"class":52},[42,1075,74],{"class":73},[42,1077,77],{"class":52},[42,1079,80],{"class":73},[42,1081,83],{"class":52},[42,1083,1084,1086,1089,1092],{"class":44,"line":86},[42,1085,424],{"class":48},[42,1087,1088],{"class":52}," a ",[42,1090,1091],{"class":48},"+",[42,1093,1094],{"class":52}," b;\n",[42,1096,1097],{"class":44,"line":92},[42,1098,141],{"class":52},[42,1100,1101],{"class":44,"line":188},[42,1102,301],{"emptyLinePlaceholder":300},[42,1104,1105],{"class":44,"line":194},[42,1106,1107],{"class":56},"\u002F\u002F test file\n",[42,1109,1110,1113,1115,1118,1121,1123],{"class":44,"line":249},[42,1111,1112],{"class":66},"describe",[42,1114,70],{"class":52},[42,1116,1117],{"class":613},"\"add function\"",[42,1119,1120],{"class":52},", () ",[42,1122,969],{"class":48},[42,1124,730],{"class":52},[42,1126,1127,1130,1132,1135,1137,1139],{"class":44,"line":263},[42,1128,1129],{"class":66},"  test",[42,1131,70],{"class":52},[42,1133,1134],{"class":613},"\"should add two positive numbers correctly\"",[42,1136,1120],{"class":52},[42,1138,969],{"class":48},[42,1140,730],{"class":52},[42,1142,1143,1146,1148,1151,1153,1156,1158,1161,1164,1167,1169,1172],{"class":44,"line":268},[42,1144,1145],{"class":66},"    expect",[42,1147,70],{"class":52},[42,1149,1150],{"class":66},"add",[42,1152,70],{"class":52},[42,1154,1155],{"class":400},"1",[42,1157,77],{"class":52},[42,1159,1160],{"class":400},"2",[42,1162,1163],{"class":52},")).",[42,1165,1166],{"class":66},"toBe",[42,1168,70],{"class":52},[42,1170,1171],{"class":400},"3",[42,1173,795],{"class":52},[42,1175,1176],{"class":44,"line":273},[42,1177,1178],{"class":52},"  });\n",[42,1180,1181],{"class":44,"line":287},[42,1182,301],{"emptyLinePlaceholder":300},[42,1184,1185,1187,1189,1192,1194,1196],{"class":44,"line":292},[42,1186,1129],{"class":66},[42,1188,70],{"class":52},[42,1190,1191],{"class":613},"\"should handle negative numbers\"",[42,1193,1120],{"class":52},[42,1195,969],{"class":48},[42,1197,730],{"class":52},[42,1199,1200,1202,1204,1206,1208,1211,1213,1215,1218,1220,1222,1224,1227],{"class":44,"line":297},[42,1201,1145],{"class":66},[42,1203,70],{"class":52},[42,1205,1150],{"class":66},[42,1207,70],{"class":52},[42,1209,1210],{"class":48},"-",[42,1212,1155],{"class":400},[42,1214,77],{"class":52},[42,1216,1217],{"class":400},"5",[42,1219,1163],{"class":52},[42,1221,1166],{"class":66},[42,1223,70],{"class":52},[42,1225,1226],{"class":400},"4",[42,1228,795],{"class":52},[42,1230,1231],{"class":44,"line":304},[42,1232,1178],{"class":52},[42,1234,1235],{"class":44,"line":317},[42,1236,301],{"emptyLinePlaceholder":300},[42,1238,1239,1241,1243,1246,1248,1250],{"class":44,"line":326},[42,1240,1129],{"class":66},[42,1242,70],{"class":52},[42,1244,1245],{"class":613},"\"should return zero when adding opposite numbers\"",[42,1247,1120],{"class":52},[42,1249,969],{"class":48},[42,1251,730],{"class":52},[42,1253,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1279],{"class":44,"line":334},[42,1255,1145],{"class":66},[42,1257,70],{"class":52},[42,1259,1150],{"class":66},[42,1261,70],{"class":52},[42,1263,1210],{"class":48},[42,1265,1171],{"class":400},[42,1267,77],{"class":52},[42,1269,1171],{"class":400},[42,1271,1163],{"class":52},[42,1273,1166],{"class":66},[42,1275,70],{"class":52},[42,1277,1278],{"class":400},"0",[42,1280,795],{"class":52},[42,1282,1283],{"class":44,"line":342},[42,1284,1178],{"class":52},[42,1286,1287],{"class":44,"line":350},[42,1288,1289],{"class":52},"});\n",[10,1291,1293],{"id":1292},"_8-simplicity","8. Simplicity",[15,1295,1296],{},"All of the above reduces to writing code that is simple and easy to read. Convoluted code attracts bugs and resists maintenance. Aim for the simplest solution that meets the requirement.",[32,1298,1300],{"className":34,"code":1299,"language":36,"meta":37,"style":37},"function calculateTotal(items) {\n  let total = 0;\n  for (let i = 0; i \u003C items.length; i++) {\n    let item = items[i];\n    if (item.type === \"premium\") {\n      total += item.price * 1.1; \u002F\u002F 10% premium fee\n    } else {\n      total += item.price;\n    }\n  }\n  return total;\n}\n",[39,1301,1302,1316,1332,1367,1380,1395,1417,1427,1436,1440,1444,1451],{"__ignoreMap":37},[42,1303,1304,1306,1309,1311,1314],{"class":44,"line":45},[42,1305,63],{"class":48},[42,1307,1308],{"class":66}," calculateTotal",[42,1310,70],{"class":52},[42,1312,1313],{"class":73},"items",[42,1315,83],{"class":52},[42,1317,1318,1321,1324,1327,1330],{"class":44,"line":60},[42,1319,1320],{"class":48},"  let",[42,1322,1323],{"class":52}," total ",[42,1325,1326],{"class":48},"=",[42,1328,1329],{"class":400}," 0",[42,1331,419],{"class":52},[42,1333,1334,1337,1339,1341,1344,1346,1348,1351,1354,1357,1360,1363,1365],{"class":44,"line":86},[42,1335,1336],{"class":48},"  for",[42,1338,758],{"class":52},[42,1340,49],{"class":48},[42,1342,1343],{"class":52}," i ",[42,1345,1326],{"class":48},[42,1347,1329],{"class":400},[42,1349,1350],{"class":52},"; i ",[42,1352,1353],{"class":48},"\u003C",[42,1355,1356],{"class":52}," items.",[42,1358,1359],{"class":400},"length",[42,1361,1362],{"class":52},"; i",[42,1364,579],{"class":48},[42,1366,83],{"class":52},[42,1368,1369,1372,1375,1377],{"class":44,"line":92},[42,1370,1371],{"class":48},"    let",[42,1373,1374],{"class":52}," item ",[42,1376,1326],{"class":48},[42,1378,1379],{"class":52}," items[i];\n",[42,1381,1382,1384,1387,1390,1393],{"class":44,"line":188},[42,1383,755],{"class":48},[42,1385,1386],{"class":52}," (item.type ",[42,1388,1389],{"class":48},"===",[42,1391,1392],{"class":613}," \"premium\"",[42,1394,83],{"class":52},[42,1396,1397,1400,1403,1406,1408,1411,1414],{"class":44,"line":194},[42,1398,1399],{"class":52},"      total ",[42,1401,1402],{"class":48},"+=",[42,1404,1405],{"class":52}," item.price ",[42,1407,413],{"class":48},[42,1409,1410],{"class":400}," 1.1",[42,1412,1413],{"class":52},"; ",[42,1415,1416],{"class":56},"\u002F\u002F 10% premium fee\n",[42,1418,1419,1422,1425],{"class":44,"line":249},[42,1420,1421],{"class":52},"    } ",[42,1423,1424],{"class":48},"else",[42,1426,730],{"class":52},[42,1428,1429,1431,1433],{"class":44,"line":263},[42,1430,1399],{"class":52},[42,1432,1402],{"class":48},[42,1434,1435],{"class":52}," item.price;\n",[42,1437,1438],{"class":44,"line":268},[42,1439,800],{"class":52},[42,1441,1442],{"class":44,"line":273},[42,1443,885],{"class":52},[42,1445,1446,1448],{"class":44,"line":287},[42,1447,424],{"class":48},[42,1449,1450],{"class":52}," total;\n",[42,1452,1453],{"class":44,"line":292},[42,1454,141],{"class":52},[15,1456,1457],{},"The same thing, easier to follow:",[32,1459,1461],{"className":34,"code":1460,"language":36,"meta":37,"style":37},"const calculateItemPrice = (item) => {\n  if (item.type === \"premium\") {\n    return item.price * 1.1;\n  }\n  return item.price;\n};\n\nfunction calculateTotal(items) {\n  return items.reduce((acc, item) => acc + calculateItemPrice(item), 0);\n}\n",[39,1462,1463,1483,1495,1507,1511,1517,1521,1525,1537,1574],{"__ignoreMap":37},[42,1464,1465,1467,1470,1472,1474,1477,1479,1481],{"class":44,"line":45},[42,1466,604],{"class":48},[42,1468,1469],{"class":66}," calculateItemPrice",[42,1471,610],{"class":48},[42,1473,758],{"class":52},[42,1475,1476],{"class":73},"item",[42,1478,404],{"class":52},[42,1480,969],{"class":48},[42,1482,730],{"class":52},[42,1484,1485,1487,1489,1491,1493],{"class":44,"line":60},[42,1486,391],{"class":48},[42,1488,1386],{"class":52},[42,1490,1389],{"class":48},[42,1492,1392],{"class":613},[42,1494,83],{"class":52},[42,1496,1497,1499,1501,1503,1505],{"class":44,"line":86},[42,1498,824],{"class":48},[42,1500,1405],{"class":52},[42,1502,413],{"class":48},[42,1504,1410],{"class":400},[42,1506,419],{"class":52},[42,1508,1509],{"class":44,"line":92},[42,1510,885],{"class":52},[42,1512,1513,1515],{"class":44,"line":188},[42,1514,424],{"class":48},[42,1516,1435],{"class":52},[42,1518,1519],{"class":44,"line":194},[42,1520,980],{"class":52},[42,1522,1523],{"class":44,"line":249},[42,1524,301],{"emptyLinePlaceholder":300},[42,1526,1527,1529,1531,1533,1535],{"class":44,"line":263},[42,1528,63],{"class":48},[42,1530,1308],{"class":66},[42,1532,70],{"class":52},[42,1534,1313],{"class":73},[42,1536,83],{"class":52},[42,1538,1539,1541,1543,1546,1549,1552,1554,1556,1558,1560,1563,1565,1567,1570,1572],{"class":44,"line":268},[42,1540,424],{"class":48},[42,1542,1356],{"class":52},[42,1544,1545],{"class":66},"reduce",[42,1547,1548],{"class":52},"((",[42,1550,1551],{"class":73},"acc",[42,1553,77],{"class":52},[42,1555,1476],{"class":73},[42,1557,404],{"class":52},[42,1559,969],{"class":48},[42,1561,1562],{"class":52}," acc ",[42,1564,1091],{"class":48},[42,1566,1469],{"class":66},[42,1568,1569],{"class":52},"(item), ",[42,1571,1278],{"class":400},[42,1573,795],{"class":52},[42,1575,1576],{"class":44,"line":273},[42,1577,141],{"class":52},[15,1579,1580],{},"Simplicity isn't about dumbing code down. It's about writing something you'll still understand in a year.",[10,1582,1584],{"id":1583},"craft-doesnt-go-away","Craft doesn't go away",[15,1586,1587],{},"New tools keep arriving and AI has changed a lot, but it amplifies what you already do rather than removing the need to do it well.",[15,1589,1590],{},"Clean code is closer to a habit than a rulebook. It's consideration for your colleagues, your users and the version of you who opens this file eighteen months from now.",[1592,1593,1594],"style",{},"html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}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 .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}",{"title":37,"searchDepth":60,"depth":60,"links":1596},[1597,1598,1599,1600,1601,1602,1603,1604,1605,1606],{"id":12,"depth":60,"text":13},{"id":26,"depth":60,"text":27},{"id":147,"depth":60,"text":148},{"id":358,"depth":60,"text":359},{"id":554,"depth":60,"text":555},{"id":639,"depth":60,"text":640},{"id":892,"depth":60,"text":893},{"id":1047,"depth":60,"text":1048},{"id":1292,"depth":60,"text":1293},{"id":1583,"depth":60,"text":1584},"PRODUCT_ENG","2025-09-16","AI can refactor for you, which makes the foundations more useful rather than less. Eight practices that keep a codebase workable.",false,"md",{},"\u002Fblog\u002Fwhy-clean-code-still-matters-in-2025",{"title":5,"description":1609},"blog\u002Fwhy-clean-code-still-matters-in-2025",[1617,1618,1619,1620],"clean code","software engineering","best practices","programming","why-clean-code-still-matters-in-2025",null,"KuIIvqTZ3KB3YJsXZdRmvkfJI0AkQqJD4tTY8RnCc1Q",[1625,1745,1853,2025,2105,2268,2380,5787,5923,7316,7692,7868,9172],{"id":1626,"title":1627,"body":1628,"category":1730,"date":1731,"description":1732,"draft":1610,"extension":1611,"meta":1733,"navigation":300,"path":1734,"seo":1735,"stem":1736,"tags":1737,"translationKey":1743,"videoId":1622,"__hash__":1744},"blog_en\u002Fblog\u002F48-laws-for-developers.md","The 48 Laws of Power for developers",{"type":7,"value":1629,"toc":1722},[1630,1634,1637,1640,1644,1647,1658,1661,1664,1668,1671,1674,1677,1680,1684,1687,1690,1693,1696,1700,1703,1706,1709,1712,1716,1719],[10,1631,1633],{"id":1632},"power-in-the-codebase","Power in the codebase",[15,1635,1636],{},"You've probably heard of \"The 48 Laws of Power\", Robert Greene's controversial book. It's usually read as a guide to social dynamics in high-stakes environments. A lot of those laws, written for courts and kingdoms, turn out to apply to the tech world too. Even in a field that likes to think of itself as a meritocracy, reading the power dynamics is part of how people build influence and end up leading.",[15,1638,1639],{},"I'm not suggesting you run Machiavellian schemes in your next stand-up. This is about noticing the forces already at play in any organisation and being deliberate about them. Here are four of the laws, reread for people who spend their days in a codebase.",[10,1641,1643],{"id":1642},"law-1-never-outshine-the-master-or-your-tech-lead","Law 1: Never outshine the master, or your tech lead",[15,1645,1646],{},"In our world the \"master\" is your tech lead, your manager or a senior architect. Wanting to show off a clever solution is natural, but there are moments when it's better to let the people above you have the win.",[15,1648,1649,1650,1653,1654,1657],{},"You might think, \"but I ",[633,1651,1652],{},"am"," good at this, why hide it?\" It isn't about hiding your talent, it's about ",[633,1655,1656],{},"how"," you present it. If you keep pointing out your lead's mistakes in public, or ship a solution that quietly bypasses their input, you aren't building allies. You're building rivals.",[15,1659,1660],{},"In practice that means offering solutions instead of only finding faults. \"Your code is inefficient\" lands very differently from \"I've been looking at an optimisation for this function that could improve performance.\" It also means crediting people when a suggestion of theirs unlocked your work, and supporting your lead's direction during a big project rather than trying to overshadow it.",[15,1662,1663],{},"A secure leader will value your contributions. An insecure one may read them as a threat. Telling the two apart and adjusting is a skill worth having.",[10,1665,1667],{"id":1666},"law-7-get-others-to-do-the-work-but-always-take-the-credit","Law 7: Get others to do the work, but always take the credit",[15,1669,1670],{},"This one sounds harsh out of context. Reread for developers, it's about delegation, mentoring and influence: getting more done by enabling other people rather than personally writing every line.",[15,1672,1673],{},"As you move into senior and lead roles, you can't scale by coding more hours. Mentoring is where the leverage is. When you guide a junior developer through a problem and they deliver, you get credit for building a strong team and they get the experience. The same goes for code reviews: instead of fixing the bug yourself, walk the author to the fix. Their skills grow and the improved code reflects the team's work.",[15,1675,1676],{},"It also applies to architecture. You lay out the shape of a complex system, your team implements the modules, and when it ships the direction you set is visible in the result.",[15,1678,1679],{},"The point isn't to be lazy. It's to be a force multiplier. The credit that lasts comes from ambitious projects finished by a well-directed team.",[10,1681,1683],{"id":1682},"law-11-learn-to-keep-people-dependent-on-you","Law 11: Learn to keep people dependent on you",[15,1685,1686],{},"This sounds possessive, but in a professional context it means making yourself genuinely valuable and hard to replace: skills, knowledge or relationships that turn you into a linchpin.",[15,1688,1689],{},"Partly that's job security. It's also influence. When people rely on your expertise, your opinions carry weight.",[15,1691,1692],{},"There are a few ways this happens. You become the person for a critical or unfashionable system that others avoid. You understand the business logic behind the code better than anyone, so when someone needs to know why a thing works the way it does, they come to you. You end up holding tribal knowledge about how legacy systems connect, the quirks of an old API, the reasoning behind an architectural decision. Or you become the bridge between development, QA, product and operations, and your ability to translate between them makes cross-team projects work.",[15,1694,1695],{},"The distinction that matters: don't hoard information. Become the person who solves the problems others can't, or solves them faster.",[10,1697,1699],{"id":1698},"law-18-do-not-build-fortresses-to-protect-yourself","Law 18: Do not build fortresses to protect yourself",[15,1701,1702],{},"It's easy to disappear into your IDE with headphones on. Deep work is good. Total isolation is not, at least not for your influence or your growth.",[15,1704,1705],{},"\"But I just want to write code\" is a fair position. If you only ever interact with your immediate team, though, you miss opportunities and insights, and your reputation stops at the edge of that team.",[15,1707,1708],{},"Volunteering for projects that span teams widens your network and shows you can work across boundaries. Internal guilds, tech talks and meetups let you share what you know and meet people. Code reviews are a social surface too: ask clarifying questions, offer help, use them to see other parts of the codebase. Being approachable when someone needs help, even outside your own task, compounds over time.",[15,1710,1711],{},"Staying connected is how you hear about opportunities, gain allies and make sure your work is visible beyond your immediate circle.",[10,1713,1715],{"id":1714},"code-and-context","Code and context",[15,1717,1718],{},"\"The 48 Laws of Power\" is an unlikely source of career advice for developers, but strip away the historical setting and much of it describes ordinary organisational dynamics. Technical skill matters most, and it's rarely enough on its own to reach the levels where decisions get made.",[15,1720,1721],{},"Understanding how influence works in the everyday interactions of your team makes you more effective. Not playing games, just seeing the board.",{"title":37,"searchDepth":60,"depth":60,"links":1723},[1724,1725,1726,1727,1728,1729],{"id":1632,"depth":60,"text":1633},{"id":1642,"depth":60,"text":1643},{"id":1666,"depth":60,"text":1667},{"id":1682,"depth":60,"text":1683},{"id":1698,"depth":60,"text":1699},{"id":1714,"depth":60,"text":1715},"CAREER","2025-05-27","Lessons from Robert Greene's book, reread for software engineers who want more influence and respect at work.",{},"\u002Fblog\u002F48-laws-for-developers",{"title":1627,"description":1732},"blog\u002F48-laws-for-developers",[1738,1739,1740,1741,1742],"career","software development","power","influence","leadership","48-laws-for-developers","UxbMGXd4dzz5KT-IETTeivFJ-lgnzr3aHP-U7gDK02Y",{"id":1746,"title":1747,"body":1748,"category":1730,"date":1608,"description":1841,"draft":1610,"extension":1611,"meta":1842,"navigation":300,"path":1843,"seo":1844,"stem":1845,"tags":1846,"translationKey":1851,"videoId":1622,"__hash__":1852},"blog_en\u002Fblog\u002Fai-powered-learning.md","AI-powered learning and the tools changing how we study",{"type":7,"value":1749,"toc":1830},[1750,1754,1757,1760,1764,1767,1774,1779,1782,1786,1793,1797,1800,1804,1807,1811,1814,1817,1820,1824,1827],[10,1751,1753],{"id":1752},"remember-how-we-used-to-study","Remember how we used to study?",[15,1755,1756],{},"We've all been there: staring at a textbook, highlighter in hand, trying to cram information in before an exam. Or trying to pick up a new skill and getting lost in an endless stream of tutorials. It was usually a lonely process with the same question running underneath it: \"am I even doing this right?\"",[15,1758,1759],{},"There's a far more personalised way to learn now.",[10,1761,1763],{"id":1762},"what-ai-powered-learning-actually-means","What AI-powered learning actually means",[15,1765,1766],{},"It's fair to wonder how AI fits into a study routine. For years it felt like something out of a science fiction film, but it's part of daily life now, including how we learn.",[15,1768,1769,1770,1773],{},"AI-powered learning means adapting the educational experience to ",[633,1771,1772],{},"you",". Think of a tutor available at any hour who knows your strengths, your gaps and the way you learn, and builds a path around that. No lectures pitched at everyone and no generic exercises.",[1775,1776,1778],"h3",{"id":1777},"personalised-learning-paths","Personalised learning paths",[15,1780,1781],{},"Say you're learning a new programming language. Instead of following a rigid curriculum, the tool assesses what you already know, finds where you need more practice and suggests resources and exercises to match. If loops are giving you trouble, you get more loop problems. If you're comfortable with variables, it moves you on.",[1775,1783,1785],{"id":1784},"instant-feedback-and-practice-that-adapts","Instant feedback and practice that adapts",[15,1787,1788,1789,1792],{},"Remember waiting days for homework to come back marked? These tools respond immediately, explain ",[633,1790,1791],{},"why"," something is wrong and guide you to the correct solution. That immediate correction makes concepts stick much faster. The practice adapts too: if you keep making the same mistake, it will present similar problems in different forms until it clicks.",[1775,1794,1796],{"id":1795},"content-curation-and-recommendation","Content curation and recommendation",[15,1798,1799],{},"The internet is an ocean of information, and finding material that is reliable, relevant and understandable can be a job in itself. AI acts as a personal librarian here, filtering articles, videos and courses to recommend what you need at a given moment. It learns your preferences and refines its suggestions as it goes.",[1775,1801,1803],{"id":1802},"spaced-repetition-and-retention","Spaced repetition and retention",[15,1805,1806],{},"One of the hardest parts of learning is remembering what you learned. Many of these tools use spaced repetition algorithms. The term sounds technical, but it means something simple: the AI reminds you to review a concept just before you're about to forget it. A reminder system for your memory, so what you learn lasts.",[10,1808,1810],{"id":1809},"one-example-skillmate-ai","One example: SkillMate AI",[15,1812,1813],{},"SkillMate AI, one of the tools I've used, fits that description: a learning coach that goes along with you.",[15,1815,1816],{},"Rather than dumping information on you, it engages with you, works out how you learn and guides you accordingly. It helps you practise concepts, explains difficult topics in simpler terms and generates quizzes tailored to you so you can check your understanding.",[15,1818,1819],{},"When you get stuck on a concept you can ask, and it will explain it another way, perhaps with an analogy that lands for you. If you need more practice in a specific skill, it generates exercises focused on that area. Before an exam, it helps you recall the important parts and quizzes you on them.",[10,1821,1823],{"id":1822},"where-this-is-going","Where this is going",[15,1825,1826],{},"AI entering education isn't a passing trend but a real change in how we pick up knowledge and skills. It lets people take charge of their own learning, move at their own pace and get the specific support they need.",[15,1828,1829],{},"Whether you're a student preparing for exams, a professional adding skills or just curious about something new, next time you open a textbook you might try bringing a tool like this along.",{"title":37,"searchDepth":60,"depth":60,"links":1831},[1832,1833,1839,1840],{"id":1752,"depth":60,"text":1753},{"id":1762,"depth":60,"text":1763,"children":1834},[1835,1836,1837,1838],{"id":1777,"depth":86,"text":1778},{"id":1784,"depth":86,"text":1785},{"id":1795,"depth":86,"text":1796},{"id":1802,"depth":86,"text":1803},{"id":1809,"depth":60,"text":1810},{"id":1822,"depth":60,"text":1823},"How personalised AI tutors are changing education, skill growth and how much of what we learn actually sticks.",{},"\u002Fblog\u002Fai-powered-learning",{"title":1747,"description":1841},"blog\u002Fai-powered-learning",[1847,1848,1849,1850],"AI","Education","Learning","SkillMate AI","ai-powered-learning","YcK3RMKgysjvlXKeOtGKMKOPV0SkPUskK3XU0e9sC80",{"id":1854,"title":1855,"body":1856,"category":1730,"date":1608,"description":2012,"draft":1610,"extension":1611,"meta":2013,"navigation":300,"path":2014,"seo":2015,"stem":2016,"tags":2017,"translationKey":2023,"videoId":1622,"__hash__":2024},"blog_en\u002Fblog\u002Fatomic-habits-for-developers.md","Atomic habits for developers: six daily routines worth trying",{"type":7,"value":1857,"toc":2002},[1858,1861,1867,1874,1877,1881,1884,1887,1891,1894,1897,1904,1908,1911,1914,1925,1929,1932,1935,1942,1946,1949,1952,1959,1963,1966,1969,1972,1976,1979,1986,1989,1993,1996,1999],[15,1859,1860],{},"We've all had the version of the day where you stare at a blank screen with a deadline approaching and a mind running a thousand processes, none of them useful. Or you start a project with real enthusiasm and watch that energy fade faster than a free trial.",[15,1862,1863,1864,786],{},"My own productivity used to work like a rollercoaster. Some days I'd get through everything, other days I'd wonder whether I still knew how to write a ",[39,1865,1866],{},"console.log",[15,1868,1869,1870,1873],{},"That's what pushed me into atomic habits. James Clear's ",[633,1871,1872],{},"Atomic Habits"," is built on one idea: tiny changes compound into large results. It's not far from how a Git history works, where a lot of small commits eventually add up to a feature.",[15,1875,1876],{},"The principles fit developers well. We already work in systems and iterative improvements, which is what these habits are made of. Here are six small ones worth building.",[10,1878,1880],{"id":1879},"why-small-habits-work-here","Why small habits work here",[15,1882,1883],{},"You might ask why not just work harder. The problem is that willpower runs out. Attempt one enormous change and your brain treats it as a threat to its comfort and pushes back. Tiny habits slip under that resistance because they're almost invisible.",[15,1885,1886],{},"For developers that matters, because we're constantly learning and holding complex problems in our heads. A set of small, consistent habits helps manage that load without burning out. Think of it as refactoring your own workflow.",[10,1888,1890],{"id":1889},"_1-a-fifteen-minute-deep-work-sprint","1. A fifteen minute deep work sprint",[15,1892,1893],{},"Give the first fifteen minutes of your workday, or the time right before a big task, to focused uninterrupted work on the most important thing on your list. Open your editor, close the other tabs, silence notifications, set a timer. No email, no Slack.",[15,1895,1896],{},"It starts the day with momentum and trains your brain to reach focus quickly. Even if fifteen minutes is all you get, you've broken the inertia, and often those fifteen turn into thirty and then an hour once you're in flow.",[15,1898,1899,1900,1903],{},"For example: today's important task is a new authentication flow. Instead of circling it, I spend fifteen minutes writing the basic ",[39,1901,1902],{},"AuthService"," interface and ignore everything else.",[10,1905,1907],{"id":1906},"_2-review-your-own-code-at-the-end-of-the-day","2. Review your own code at the end of the day",[15,1909,1910],{},"Before you finish, spend five or ten minutes scrolling through what you wrote that day. Look for likely bugs, places worth refactoring, names that could be clearer. You don't have to fix anything then, the point is to see it.",[15,1912,1913],{},"It's a small retrospective folded into the day. Code quality goes up, errors surface earlier, and seeing your own commits stack up is motivating.",[15,1915,1916,1917,1920,1921,1924],{},"For example: I finish a component and, before pushing, scroll the file and notice a variable called ",[39,1918,1919],{},"data"," that would be clearer as ",[39,1922,1923],{},"userData",". I leave a note to change it tomorrow.",[10,1926,1928],{"id":1927},"_3-learn-one-small-thing","3. Learn one small thing",[15,1930,1931],{},"Give ten minutes a day to learning one small thing related to your work. A short blog post about a JavaScript feature, a five minute video on a CSS technique, a new editor extension, a couple of pages of a technical book.",[15,1933,1934],{},"The field moves quickly and keeping up matters, but trying to keep up all at once is overwhelming. Ten minutes makes it consistent instead, and the small pieces add up.",[15,1936,1937,1938,1941],{},"For example: I see a mention of something in Vue 3's Composition API, open the docs and spend ten minutes reading about ",[39,1939,1940],{},"defineExpose"," and where it's useful.",[10,1943,1945],{"id":1944},"_4-plan-tomorrows-first-task-tonight","4. Plan tomorrow's first task tonight",[15,1947,1948],{},"Before you log off, spend five minutes deciding the single first task for tomorrow morning. Write it down. Open the relevant file in your editor so it's waiting for you.",[15,1950,1951],{},"This removes decision fatigue and the cold start problem. You wake up knowing what to do, which cuts procrastination considerably.",[15,1953,1954,1955,1958],{},"For example: before closing the laptop I see the next task is integrating a webhook handler, write it on a sticky note and leave ",[39,1956,1957],{},"stripe-webhook.ts"," open in the editor.",[10,1960,1962],{"id":1961},"_5-two-minutes-of-digital-tidying","5. Two minutes of digital tidying",[15,1964,1965],{},"Once a day, spend two minutes closing tabs you don't need, tidying the desktop or clearing the downloads folder. Pick one small mess, not all of them.",[15,1967,1968],{},"A cluttered digital environment tends to produce a cluttered head. Even a small tidy reduces the overhead of finding things.",[15,1970,1971],{},"For example: the browser has thirty tabs open, so I take two minutes and close five I definitely don't need.",[10,1973,1975],{"id":1974},"_6-a-one-minute-physical-reset","6. A one minute physical reset",[15,1977,1978],{},"Every hour, stand up, stretch and step away from the screen for sixty seconds. Set a gentle reminder or use a Pomodoro timer, and when it goes off, don't ignore it. Stand, roll your shoulders, look out of a window, walk to the kitchen.",[15,1980,1981,1982,1985],{},"We spend hours hunched over keyboards. A minute an hour helps with physical strain, blood flow and eye fatigue. It's also true that a fair number of debugging ideas arrive when you're ",[633,1983,1984],{},"not"," looking at the code.",[15,1987,1988],{},"For example: the timer goes off, I stand up, roll my shoulders and look at the trees outside for a minute before sitting back down.",[10,1990,1992],{"id":1991},"trying-it-for-a-month","Trying it for a month",[15,1994,1995],{},"None of these are monumental changes, which is the point. Pick one or two and run them consistently for about thirty days.",[15,1997,1998],{},"Clear's four rules help here. Make it obvious, with a sticky note on the monitor for the deep work sprint. Make it attractive by pairing it with something you enjoy afterwards. Make it easy by starting genuinely small, and if fifteen minutes of deep work is too much, do five. Make it satisfying by tracking it, even with a checkbox on paper, because watching the streak grow is its own motivation.",[15,2000,2001],{},"Small consistent actions compound into real differences in focus and consistency. That's most of what changed for me.",{"title":37,"searchDepth":60,"depth":60,"links":2003},[2004,2005,2006,2007,2008,2009,2010,2011],{"id":1879,"depth":60,"text":1880},{"id":1889,"depth":60,"text":1890},{"id":1906,"depth":60,"text":1907},{"id":1927,"depth":60,"text":1928},{"id":1944,"depth":60,"text":1945},{"id":1961,"depth":60,"text":1962},{"id":1974,"depth":60,"text":1975},{"id":1991,"depth":60,"text":1992},"Six small, practical habits developers can build to improve focus and consistency over about a month.",{},"\u002Fblog\u002Fatomic-habits-for-developers",{"title":1855,"description":2012},"blog\u002Fatomic-habits-for-developers",[2018,2019,2020,2021,2022],"productivity","habits","developers","focus","consistency","atomic-habits-for-developers","xD_27YRviTA3EWl3HFlLJjKATILpfzrM4lt6ipz7pMs",{"id":2026,"title":2027,"body":2028,"category":1607,"date":2093,"description":2094,"draft":1610,"extension":1611,"meta":2095,"navigation":300,"path":2096,"seo":2097,"stem":2098,"tags":2099,"translationKey":2103,"videoId":1622,"__hash__":2104},"blog_en\u002Fblog\u002Fconnect-namecheap-domain-to-vercel.md","How to connect your Namecheap domain to an app deployed on Vercel",{"type":7,"value":2029,"toc":2087},[2030,2033,2037,2040,2044,2047,2050,2054,2057,2064,2071,2077,2081,2084],[15,2031,2032],{},"If you bought a domain from Namecheap and want to connect it to your Vercel app, there are a few steps to follow.",[10,2034,2036],{"id":2035},"_1-add-the-domain-in-vercel","1. Add the domain in Vercel",[15,2038,2039],{},"Log in to your Vercel account and open your app dashboard. Go to \"Settings\" and then \"Domains\". Click \"Add Domain\", enter your domain name and confirm with \"Add\".",[10,2041,2043],{"id":2042},"_2-copy-the-dns-records-from-vercel","2. Copy the DNS records from Vercel",[15,2045,2046],{},"Once the domain is added, you need the DNS records Vercel gives you. Go back to the \"Domains\" section, click the domain you just added, then open \"DNS Records\".",[15,2048,2049],{},"You'll see the list of records to create in Namecheap: an A record, a CNAME record and a TXT record.",[10,2051,2053],{"id":2052},"_3-create-the-records-in-namecheap","3. Create the records in Namecheap",[15,2055,2056],{},"Log in to Namecheap and open your domain dashboard. Go to \"Advanced DNS\" and click \"Add New Record\".",[15,2058,2059,2060,2063],{},"Start with the A record. In the \"Type\" dropdown, select \"A (Address)\". In the \"Host\" field, enter ",[39,2061,2062],{},"@"," without the quotes. In \"Value\", enter the IP address from Vercel.",[15,2065,2066,2067,2070],{},"Next comes the CNAME. In \"Type\", select \"CNAME (Alias)\", in \"Host\" enter ",[39,2068,2069],{},"www",", and in \"Value\" enter the value shown in Vercel.",[15,2072,2073,2074,2076],{},"Finish with the TXT record. In \"Type\", select \"TXT (Text)\", in \"Host\" enter ",[39,2075,2062],{}," again, and in \"Value\" enter the value from Vercel.",[10,2078,2080],{"id":2079},"_4-verify-the-records","4. Verify the records",[15,2082,2083],{},"Once all three are created, check that they're correct. Go back to your Vercel app dashboard, click the domain and then \"Verify DNS Configuration\". Vercel will check whether the records are set up as expected.",[15,2085,2086],{},"DNS records take time to propagate, so be patient. When propagation finishes, Vercel verifies them and your domain is connected to the app. If you get stuck, write to Vercel support.",{"title":37,"searchDepth":60,"depth":60,"links":2088},[2089,2090,2091,2092],{"id":2035,"depth":60,"text":2036},{"id":2042,"depth":60,"text":2043},{"id":2052,"depth":60,"text":2053},{"id":2079,"depth":60,"text":2080},"2024-09-13","The steps for pointing a domain bought on Namecheap at your application deployed on Vercel.",{},"\u002Fblog\u002Fconnect-namecheap-domain-to-vercel",{"title":2027,"description":2094},"blog\u002Fconnect-namecheap-domain-to-vercel",[2100,2101,2102],"namecheap","vercel","deployment","connect-namecheap-domain-to-vercel","imGe3j_Lnmwkq-oqHlE6RrZDHG9G5n8sSWtzhrAzLps",{"id":2106,"title":2107,"body":2108,"category":2255,"date":2256,"description":2257,"draft":1610,"extension":1611,"meta":2258,"navigation":300,"path":2259,"seo":2260,"stem":2261,"tags":2262,"translationKey":2266,"videoId":1622,"__hash__":2267},"blog_en\u002Fblog\u002Ffix-tailwindcss-intellisense-in-nuxt-3.md","How to fix TailwindCSS Intellisense in a Nuxt 3 project",{"type":7,"value":2109,"toc":2250},[2110,2114,2124,2128,2141,2145,2151,2176,2244,2247],[10,2111,2113],{"id":2112},"the-problem","The problem",[15,2115,2116,2117,2123],{},"I had a Nuxt 3 and TailwindCSS project open in VS Code, and Tailwind Intellisense wasn't working properly. The first thing I tried was reinstalling the extension, which didn't help. After some digging I found ",[74,2118,2122],{"href":2119,"rel":2120},"https:\u002F\u002Fgithub.com\u002Ftailwindlabs\u002Ftailwindcss-intellisense\u002Fissues\u002F663#issuecomment-1316788128",[2121],"nofollow","this workaround",", and that's what I'm sharing here.",[10,2125,2127],{"id":2126},"why-it-isnt-working","Why it isn't working",[15,2129,2130,2131,2134,2135,2138,2139,786],{},"A Nuxt project has a ",[39,2132,2133],{},".nuxt"," directory. Nuxt uses it during development to generate your Vue application. If you look inside, you'll find a file called ",[39,2136,2137],{},".nuxt\u002Ftailwind.config.cjs",". So Tailwind finds two config files in the same project: one in the root directory and one inside ",[39,2140,2133],{},[10,2142,2144],{"id":2143},"the-workaround","The workaround",[15,2146,2147,2148,2150],{},"You can tell the extension to ignore the ",[39,2149,2133],{}," directory. To exclude it from your workspace:",[2152,2153,2154,2162,2173],"ul",{},[2155,2156,2157,2158,2161],"li",{},"Create a ",[39,2159,2160],{},"\u002F.vscode"," folder at the root of your project.",[2155,2163,2164,2165,2168,2169,2172],{},"Inside ",[39,2166,2167],{},".vscode",", add a ",[39,2170,2171],{},"settings.json"," file.",[2155,2174,2175],{},"Copy the code below into that file.",[32,2177,2180],{"className":2178,"code":2179,"language":816,"meta":37,"style":37},"language-json shiki shiki-themes github-dark-default","\u002F\u002F \u002F.vscode\u002Fsettings.json\n{\n  \"tailwindCSS.files.exclude\": [\n    \"**\u002F.git\u002F**\",\n    \"**\u002Fnode_modules\u002F**\",\n    \"**\u002F.hg\u002F**\",\n    \"**\u002F.svn\u002F**\",\n    \"**\u002F.nuxt\u002F**\"\n  ]\n}\n",[39,2181,2182,2187,2192,2201,2209,2216,2223,2230,2235,2240],{"__ignoreMap":37},[42,2183,2184],{"class":44,"line":45},[42,2185,2186],{"class":56},"\u002F\u002F \u002F.vscode\u002Fsettings.json\n",[42,2188,2189],{"class":44,"line":60},[42,2190,2191],{"class":52},"{\n",[42,2193,2194,2198],{"class":44,"line":86},[42,2195,2197],{"class":2196},"sPWt5","  \"tailwindCSS.files.exclude\"",[42,2199,2200],{"class":52},": [\n",[42,2202,2203,2206],{"class":44,"line":92},[42,2204,2205],{"class":613},"    \"**\u002F.git\u002F**\"",[42,2207,2208],{"class":52},",\n",[42,2210,2211,2214],{"class":44,"line":188},[42,2212,2213],{"class":613},"    \"**\u002Fnode_modules\u002F**\"",[42,2215,2208],{"class":52},[42,2217,2218,2221],{"class":44,"line":194},[42,2219,2220],{"class":613},"    \"**\u002F.hg\u002F**\"",[42,2222,2208],{"class":52},[42,2224,2225,2228],{"class":44,"line":249},[42,2226,2227],{"class":613},"    \"**\u002F.svn\u002F**\"",[42,2229,2208],{"class":52},[42,2231,2232],{"class":44,"line":263},[42,2233,2234],{"class":613},"    \"**\u002F.nuxt\u002F**\"\n",[42,2236,2237],{"class":44,"line":268},[42,2238,2239],{"class":52},"  ]\n",[42,2241,2242],{"class":44,"line":273},[42,2243,141],{"class":52},[15,2245,2246],{},"Tailwind Intellisense should start working again from there.",[1592,2248,2249],{},"html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}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);}",{"title":37,"searchDepth":60,"depth":60,"links":2251},[2252,2253,2254],{"id":2112,"depth":60,"text":2113},{"id":2126,"depth":60,"text":2127},{"id":2143,"depth":60,"text":2144},"FRONTEND","2024-11-12","TailwindCSS Intellisense stops working in Nuxt 3 projects. Here is the cause and a simple workaround.",{},"\u002Fblog\u002Ffix-tailwindcss-intellisense-in-nuxt-3",{"title":2107,"description":2257},"blog\u002Ffix-tailwindcss-intellisense-in-nuxt-3",[2263,2264,2265],"nuxt","tailwindcss","vscode","fix-tailwindcss-intellisense-in-nuxt-3","C1Csn66a_b8ZkAno2NGREg6e1mVA0tsrMMMOdLlNYb8",{"id":2269,"title":2270,"body":2271,"category":2368,"date":2369,"description":2370,"draft":1610,"extension":1611,"meta":2371,"navigation":300,"path":2372,"seo":2373,"stem":2374,"tags":2375,"translationKey":2378,"videoId":1622,"__hash__":2379},"blog_en\u002Fblog\u002Fhow-ai-is-transforming-bug-fixing.md","How AI is changing bug fixing",{"type":7,"value":2272,"toc":2358},[2273,2277,2284,2287,2290,2294,2297,2300,2304,2307,2314,2318,2321,2325,2338,2341,2345,2348,2352,2355],[10,2274,2276],{"id":2275},"the-battle-that-never-ends","The battle that never ends",[15,2278,2279,2280,2283],{},"Anyone who has written even a little code knows the sequence: you write a few lines that look fine, you run the program, and an error appears. Or worse, the program runs but doesn't do what you ",[633,2281,2282],{},"thought"," you told it to do.",[15,2285,2286],{},"Finding and fixing those digital gremlins has always been one of the slowest parts of software development, and one of the most frustrating. It's detective work where the clues are cryptic error messages and the suspects are millions of lines of code.",[15,2288,2289],{},"There's a new helper that makes that battle considerably easier: artificial intelligence.",[10,2291,2293],{"id":2292},"where-ai-comes-into-debugging","Where AI comes into debugging",[15,2295,2296],{},"You might ask what AI can do about something as fine-grained as a misplaced semicolon or a flaw in your logic. AI isn't only about robots and self-driving cars; it's also changing how we write, test and fix code.",[15,2298,2299],{},"Think of it as a tireless assistant that can scan huge amounts of code and data far faster than a person can. A reviewer available at any hour.",[1775,2301,2303],{"id":2302},"predicting-bugs-before-you-run-the-code","Predicting bugs before you run the code",[15,2305,2306],{},"Imagine your editor telling you that the line you just wrote might cause a problem later. That's predictive bug detection, roughly. Models are trained on enormous code datasets that include millions of bugs alongside their fixes, so they learn the patterns that go with common errors.",[15,2308,2309,2310,2313],{},"While you write, the model analyses your code and can flag potential problems ",[633,2311,2312],{},"before"," you run the program. It resembles a spell checker, but for logical errors and performance bottlenecks.",[1775,2315,2317],{"id":2316},"triaging-bug-reports-automatically","Triaging bug reports automatically",[15,2319,2320],{},"On larger projects a bug report isn't only about fixing code: you have to understand its impact, assign it to the right person and decide how critical it is. AI can read the reports, compare them against past issues, categorise them, suggest a severity level, and even point to the team member whose previous work makes them a good fit. That saves a lot of time and helps critical bugs get attention first.",[1775,2322,2324],{"id":2323},"fix-suggestions-sometimes-the-whole-fix","Fix suggestions, sometimes the whole fix",[15,2326,2327,2328,2331,2332,2334,2335,2337],{},"This is where it gets interesting. Some tools go past ",[633,2329,2330],{},"finding"," the bug and propose how to fix it. Picture a warning along the lines of \"there's an error on this line, did you mean ",[39,2333,1389],{}," instead of ",[39,2336,1326],{},"?\".",[15,2339,2340],{},"More advanced models generate code that addresses the bug directly. They do it by understanding the context of the error and drawing on patterns from fixes that worked in similar situations. They don't always get it right and a human still has to review and approve, but they speed up the initial investigation and the search for a solution.",[1775,2342,2344],{"id":2343},"better-test-cases","Better test cases",[15,2346,2347],{},"Bugs often slip through because our tests aren't thorough enough. AI can analyse your code and your test suite and suggest new cases that expose edge conditions you hadn't considered. It can also generate new test data or scenarios.",[10,2349,2351],{"id":2350},"does-this-replace-developers","Does this replace developers?",[15,2353,2354],{},"No. Designing and genuinely understanding a complex system still needs human intuition, creativity and judgement. What AI does is take over the repetitive, slow part of debugging.",[15,2356,2357],{},"That leaves more time for designing new features and solving bigger problems instead of chasing one stray semicolon. Next time you're hunting a bug, you'll notice how much these tools already do in the background.",{"title":37,"searchDepth":60,"depth":60,"links":2359},[2360,2361,2367],{"id":2275,"depth":60,"text":2276},{"id":2292,"depth":60,"text":2293,"children":2362},[2363,2364,2365,2366],{"id":2302,"depth":86,"text":2303},{"id":2316,"depth":86,"text":2317},{"id":2323,"depth":86,"text":2324},{"id":2343,"depth":86,"text":2344},{"id":2350,"depth":60,"text":2351},"AI_LAB","2025-09-18","AI-driven tools catch mistakes earlier, cut debugging time and raise code quality.",{},"\u002Fblog\u002Fhow-ai-is-transforming-bug-fixing",{"title":2270,"description":2370},"blog\u002Fhow-ai-is-transforming-bug-fixing",[1847,2376,1739,2377],"bug fixing","code quality","how-ai-is-transforming-bug-fixing","EGmFULX4bZpzYbstFLyapUjpFoR1vxhaYvNX6-YeWEo",{"id":2381,"title":2382,"body":2383,"category":2255,"date":5775,"description":5776,"draft":1610,"extension":1611,"meta":5777,"navigation":300,"path":5778,"seo":5779,"stem":5780,"tags":5781,"translationKey":5785,"videoId":1622,"__hash__":5786},"blog_en\u002Fblog\u002Fmastering-vue3-in-2025.md","Seven advanced Vue 3 techniques worth knowing",{"type":7,"value":2384,"toc":5765},[2385,2388,2395,2401,2404,2407,2715,2718,2935,2939,2950,2953,3027,3609,3620,3624,3631,3634,3830,3836,4042,4051,4055,4068,4192,4195,4244,4261,4265,4268,4271,4444,4447,4608,4612,4615,4618,4859,5156,5168,5172,5175,5190,5312,5319,5397,5404,5608,5748,5759,5762],[15,2386,2387],{},"Vue 3 has been part of everyday frontend work for a while now. It's intuitive and pleasant to build with, and the basics get you a long way. These are seven techniques past that point, all of which I've found useful in my own projects.",[10,2389,2391,2392],{"id":2390},"_1-the-composition-api-past-setup","1. The Composition API past ",[39,2393,2394],{},"setup()",[15,2396,2397,2398,2400],{},"You might object that the Composition API isn't advanced anymore, and that's fair. Most of us are comfortable with ",[39,2399,2394],{}," and reactive references. The part that often goes unused is extracting logic into composables.",[15,2402,2403],{},"Think of a composable as a function that encapsulates stateful logic. Components get leaner because they only handle rendering, logic is grouped by feature rather than by option, and the same logic can be shared without the problems mixins used to cause.",[15,2405,2406],{},"Say you have a feature that fetches data and tracks loading and error states. Instead of repeating that in every component:",[32,2408,2410],{"className":34,"code":2409,"language":36,"meta":37,"style":37},"\u002F\u002F composables\u002FuseFetch.js\nimport { ref, onMounted } from \"vue\";\n\nexport function useFetch(url) {\n  const data = ref(null);\n  const error = ref(null);\n  const loading = ref(true);\n\n  async function fetchData() {\n    loading.value = true;\n    error.value = null;\n    try {\n      const res = await fetch(url);\n      if (!res.ok) {\n        throw new Error(`HTTP error! status: ${res.status}`);\n      }\n      data.value = await res.json();\n    } catch (e) {\n      error.value = e;\n    } finally {\n      loading.value = false;\n    }\n  }\n\n  onMounted(fetchData);\n\n  return { data, error, loading, fetchData };\n}\n",[39,2411,2412,2417,2433,2437,2454,2472,2489,2507,2511,2523,2535,2547,2554,2572,2584,2610,2615,2631,2640,2650,2660,2673,2678,2683,2688,2697,2702,2710],{"__ignoreMap":37},[42,2413,2414],{"class":44,"line":45},[42,2415,2416],{"class":56},"\u002F\u002F composables\u002FuseFetch.js\n",[42,2418,2419,2422,2425,2428,2431],{"class":44,"line":60},[42,2420,2421],{"class":48},"import",[42,2423,2424],{"class":52}," { ref, onMounted } ",[42,2426,2427],{"class":48},"from",[42,2429,2430],{"class":613}," \"vue\"",[42,2432,419],{"class":52},[42,2434,2435],{"class":44,"line":86},[42,2436,301],{"emptyLinePlaceholder":300},[42,2438,2439,2442,2444,2447,2449,2452],{"class":44,"line":92},[42,2440,2441],{"class":48},"export",[42,2443,714],{"class":48},[42,2445,2446],{"class":66}," useFetch",[42,2448,70],{"class":52},[42,2450,2451],{"class":73},"url",[42,2453,83],{"class":52},[42,2455,2456,2458,2460,2462,2465,2467,2470],{"class":44,"line":188},[42,2457,670],{"class":48},[42,2459,673],{"class":400},[42,2461,610],{"class":48},[42,2463,2464],{"class":66}," ref",[42,2466,70],{"class":52},[42,2468,2469],{"class":400},"null",[42,2471,795],{"class":52},[42,2473,2474,2476,2479,2481,2483,2485,2487],{"class":44,"line":194},[42,2475,670],{"class":48},[42,2477,2478],{"class":400}," error",[42,2480,610],{"class":48},[42,2482,2464],{"class":66},[42,2484,70],{"class":52},[42,2486,2469],{"class":400},[42,2488,795],{"class":52},[42,2490,2491,2493,2496,2498,2500,2502,2505],{"class":44,"line":249},[42,2492,670],{"class":48},[42,2494,2495],{"class":400}," loading",[42,2497,610],{"class":48},[42,2499,2464],{"class":66},[42,2501,70],{"class":52},[42,2503,2504],{"class":400},"true",[42,2506,795],{"class":52},[42,2508,2509],{"class":44,"line":263},[42,2510,301],{"emptyLinePlaceholder":300},[42,2512,2513,2516,2518,2521],{"class":44,"line":268},[42,2514,2515],{"class":48},"  async",[42,2517,714],{"class":48},[42,2519,2520],{"class":66}," fetchData",[42,2522,943],{"class":52},[42,2524,2525,2528,2530,2533],{"class":44,"line":273},[42,2526,2527],{"class":52},"    loading.value ",[42,2529,1326],{"class":48},[42,2531,2532],{"class":400}," true",[42,2534,419],{"class":52},[42,2536,2537,2540,2542,2545],{"class":44,"line":287},[42,2538,2539],{"class":52},"    error.value ",[42,2541,1326],{"class":48},[42,2543,2544],{"class":400}," null",[42,2546,419],{"class":52},[42,2548,2549,2552],{"class":44,"line":292},[42,2550,2551],{"class":48},"    try",[42,2553,730],{"class":52},[42,2555,2556,2559,2562,2564,2566,2569],{"class":44,"line":297},[42,2557,2558],{"class":48},"      const",[42,2560,2561],{"class":400}," res",[42,2563,610],{"class":48},[42,2565,743],{"class":48},[42,2567,2568],{"class":66}," fetch",[42,2570,2571],{"class":52},"(url);\n",[42,2573,2574,2577,2579,2581],{"class":44,"line":304},[42,2575,2576],{"class":48},"      if",[42,2578,758],{"class":52},[42,2580,761],{"class":48},[42,2582,2583],{"class":52},"res.ok) {\n",[42,2585,2586,2589,2591,2593,2595,2598,2601,2603,2606,2608],{"class":44,"line":317},[42,2587,2588],{"class":48},"        throw",[42,2590,772],{"class":48},[42,2592,775],{"class":66},[42,2594,70],{"class":52},[42,2596,2597],{"class":613},"`HTTP error! status: ${",[42,2599,2600],{"class":52},"res",[42,2602,786],{"class":613},[42,2604,2605],{"class":52},"status",[42,2607,792],{"class":613},[42,2609,795],{"class":52},[42,2611,2612],{"class":44,"line":326},[42,2613,2614],{"class":52},"      }\n",[42,2616,2617,2620,2622,2624,2627,2629],{"class":44,"line":334},[42,2618,2619],{"class":52},"      data.value ",[42,2621,1326],{"class":48},[42,2623,743],{"class":48},[42,2625,2626],{"class":52}," res.",[42,2628,816],{"class":66},[42,2630,819],{"class":52},[42,2632,2633,2635,2637],{"class":44,"line":342},[42,2634,1421],{"class":52},[42,2636,834],{"class":48},[42,2638,2639],{"class":52}," (e) {\n",[42,2641,2642,2645,2647],{"class":44,"line":350},[42,2643,2644],{"class":52},"      error.value ",[42,2646,1326],{"class":48},[42,2648,2649],{"class":52}," e;\n",[42,2651,2653,2655,2658],{"class":44,"line":2652},20,[42,2654,1421],{"class":52},[42,2656,2657],{"class":48},"finally",[42,2659,730],{"class":52},[42,2661,2663,2666,2668,2671],{"class":44,"line":2662},21,[42,2664,2665],{"class":52},"      loading.value ",[42,2667,1326],{"class":48},[42,2669,2670],{"class":400}," false",[42,2672,419],{"class":52},[42,2674,2676],{"class":44,"line":2675},22,[42,2677,800],{"class":52},[42,2679,2681],{"class":44,"line":2680},23,[42,2682,885],{"class":52},[42,2684,2686],{"class":44,"line":2685},24,[42,2687,301],{"emptyLinePlaceholder":300},[42,2689,2691,2694],{"class":44,"line":2690},25,[42,2692,2693],{"class":66},"  onMounted",[42,2695,2696],{"class":52},"(fetchData);\n",[42,2698,2700],{"class":44,"line":2699},26,[42,2701,301],{"emptyLinePlaceholder":300},[42,2703,2705,2707],{"class":44,"line":2704},27,[42,2706,424],{"class":48},[42,2708,2709],{"class":52}," { data, error, loading, fetchData };\n",[42,2711,2713],{"class":44,"line":2712},28,[42,2714,141],{"class":52},[15,2716,2717],{},"Any component can then use it:",[32,2719,2723],{"className":2720,"code":2721,"language":2722,"meta":37,"style":37},"language-vue shiki shiki-themes github-dark-default","\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cdiv v-if=\"loading\">Loading posts...\u003C\u002Fdiv>\n    \u003Cdiv v-else-if=\"error\">Error: {{ error.message }}\u003C\u002Fdiv>\n    \u003Cul v-else>\n      \u003Cli v-for=\"post in data\" :key=\"post.id\">{{ post.title }}\u003C\u002Fli>\n    \u003C\u002Ful>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { useFetch } from \"@\u002Fcomposables\u002FuseFetch\";\n\nconst { data, error, loading } = useFetch(\n  \"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\"\n);\n\u003C\u002Fscript>\n","vue",[39,2724,2725,2735,2745,2767,2788,2799,2829,2838,2847,2856,2860,2872,2886,2890,2918,2923,2927],{"__ignoreMap":37},[42,2726,2727,2729,2732],{"class":44,"line":45},[42,2728,1353],{"class":52},[42,2730,2731],{"class":2196},"template",[42,2733,2734],{"class":52},">\n",[42,2736,2737,2740,2743],{"class":44,"line":60},[42,2738,2739],{"class":52},"  \u003C",[42,2741,2742],{"class":2196},"div",[42,2744,2734],{"class":52},[42,2746,2747,2750,2752,2755,2757,2760,2763,2765],{"class":44,"line":86},[42,2748,2749],{"class":52},"    \u003C",[42,2751,2742],{"class":2196},[42,2753,2754],{"class":400}," v-if",[42,2756,1326],{"class":52},[42,2758,2759],{"class":613},"\"loading\"",[42,2761,2762],{"class":52},">Loading posts...\u003C\u002F",[42,2764,2742],{"class":2196},[42,2766,2734],{"class":52},[42,2768,2769,2771,2773,2776,2778,2781,2784,2786],{"class":44,"line":92},[42,2770,2749],{"class":52},[42,2772,2742],{"class":2196},[42,2774,2775],{"class":400}," v-else-if",[42,2777,1326],{"class":52},[42,2779,2780],{"class":613},"\"error\"",[42,2782,2783],{"class":52},">Error: {{ error.message }}\u003C\u002F",[42,2785,2742],{"class":2196},[42,2787,2734],{"class":52},[42,2789,2790,2792,2794,2797],{"class":44,"line":188},[42,2791,2749],{"class":52},[42,2793,2152],{"class":2196},[42,2795,2796],{"class":400}," v-else",[42,2798,2734],{"class":52},[42,2800,2801,2804,2806,2809,2811,2814,2817,2819,2822,2825,2827],{"class":44,"line":194},[42,2802,2803],{"class":52},"      \u003C",[42,2805,2155],{"class":2196},[42,2807,2808],{"class":400}," v-for",[42,2810,1326],{"class":52},[42,2812,2813],{"class":613},"\"post in data\"",[42,2815,2816],{"class":400}," :key",[42,2818,1326],{"class":52},[42,2820,2821],{"class":613},"\"post.id\"",[42,2823,2824],{"class":52},">{{ post.title }}\u003C\u002F",[42,2826,2155],{"class":2196},[42,2828,2734],{"class":52},[42,2830,2831,2834,2836],{"class":44,"line":249},[42,2832,2833],{"class":52},"    \u003C\u002F",[42,2835,2152],{"class":2196},[42,2837,2734],{"class":52},[42,2839,2840,2843,2845],{"class":44,"line":263},[42,2841,2842],{"class":52},"  \u003C\u002F",[42,2844,2742],{"class":2196},[42,2846,2734],{"class":52},[42,2848,2849,2852,2854],{"class":44,"line":268},[42,2850,2851],{"class":52},"\u003C\u002F",[42,2853,2731],{"class":2196},[42,2855,2734],{"class":52},[42,2857,2858],{"class":44,"line":273},[42,2859,301],{"emptyLinePlaceholder":300},[42,2861,2862,2864,2867,2870],{"class":44,"line":287},[42,2863,1353],{"class":52},[42,2865,2866],{"class":2196},"script",[42,2868,2869],{"class":400}," setup",[42,2871,2734],{"class":52},[42,2873,2874,2876,2879,2881,2884],{"class":44,"line":292},[42,2875,2421],{"class":48},[42,2877,2878],{"class":52}," { useFetch } ",[42,2880,2427],{"class":48},[42,2882,2883],{"class":613}," \"@\u002Fcomposables\u002FuseFetch\"",[42,2885,419],{"class":52},[42,2887,2888],{"class":44,"line":297},[42,2889,301],{"emptyLinePlaceholder":300},[42,2891,2892,2894,2897,2899,2901,2903,2905,2908,2911,2913,2915],{"class":44,"line":304},[42,2893,604],{"class":48},[42,2895,2896],{"class":52}," { ",[42,2898,1919],{"class":400},[42,2900,77],{"class":52},[42,2902,845],{"class":400},[42,2904,77],{"class":52},[42,2906,2907],{"class":400},"loading",[42,2909,2910],{"class":52}," } ",[42,2912,1326],{"class":48},[42,2914,2446],{"class":66},[42,2916,2917],{"class":52},"(\n",[42,2919,2920],{"class":44,"line":317},[42,2921,2922],{"class":613},"  \"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Fposts\"\n",[42,2924,2925],{"class":44,"line":326},[42,2926,795],{"class":52},[42,2928,2929,2931,2933],{"class":44,"line":334},[42,2930,2851],{"class":52},[42,2932,2866],{"class":2196},[42,2934,2734],{"class":52},[10,2936,2938],{"id":2937},"_2-teleport-for-elements-that-need-to-escape","2. Teleport, for elements that need to escape",[15,2940,2941,2942,2945,2946,2949],{},"If you've fought ",[39,2943,2944],{},"z-index"," wars while trying to render modals, tooltips or notifications from inside a deeply nested component, ",[39,2947,2948],{},"Teleport"," is the answer. It renders part of your template somewhere else in the DOM, outside the component hierarchy, while keeping reactivity with your component's state.",[15,2951,2952],{},"It fits modals and dialogs, toasts and notifications, tooltips and dropdowns.",[32,2954,2958],{"className":2955,"code":2956,"language":2957,"meta":37,"style":37},"language-html shiki shiki-themes github-dark-default","\u003C!-- In your App.vue or main layout file -->\n\u003Cbody>\n  \u003Cdiv id=\"app\">\u003C\u002Fdiv>\n  \u003Cdiv id=\"modals-container\">\u003C\u002Fdiv>\n  \u003C!-- This is our teleport target -->\n\u003C\u002Fbody>\n","html",[39,2959,2960,2965,2974,2995,3014,3019],{"__ignoreMap":37},[42,2961,2962],{"class":44,"line":45},[42,2963,2964],{"class":56},"\u003C!-- In your App.vue or main layout file -->\n",[42,2966,2967,2969,2972],{"class":44,"line":60},[42,2968,1353],{"class":52},[42,2970,2971],{"class":2196},"body",[42,2973,2734],{"class":52},[42,2975,2976,2978,2980,2983,2985,2988,2991,2993],{"class":44,"line":86},[42,2977,2739],{"class":52},[42,2979,2742],{"class":2196},[42,2981,2982],{"class":400}," id",[42,2984,1326],{"class":52},[42,2986,2987],{"class":613},"\"app\"",[42,2989,2990],{"class":52},">\u003C\u002F",[42,2992,2742],{"class":2196},[42,2994,2734],{"class":52},[42,2996,2997,2999,3001,3003,3005,3008,3010,3012],{"class":44,"line":92},[42,2998,2739],{"class":52},[42,3000,2742],{"class":2196},[42,3002,2982],{"class":400},[42,3004,1326],{"class":52},[42,3006,3007],{"class":613},"\"modals-container\"",[42,3009,2990],{"class":52},[42,3011,2742],{"class":2196},[42,3013,2734],{"class":52},[42,3015,3016],{"class":44,"line":188},[42,3017,3018],{"class":56},"  \u003C!-- This is our teleport target -->\n",[42,3020,3021,3023,3025],{"class":44,"line":194},[42,3022,2851],{"class":52},[42,3024,2971],{"class":2196},[42,3026,2734],{"class":52},[32,3028,3030],{"className":2720,"code":3029,"language":2722,"meta":37,"style":37},"\u003C!-- components\u002FMyModal.vue -->\n\u003Ctemplate>\n  \u003CTeleport to=\"#modals-container\">\n    \u003Cdiv v-if=\"isOpen\" class=\"modal-backdrop\" @click=\"close\">\n      \u003Cdiv class=\"modal-content\" @click.stop>\n        \u003Ch3>My Awesome Modal\u003C\u002Fh3>\n        \u003Cp>This modal content is rendered outside the component!\u003C\u002Fp>\n        \u003Cbutton @click=\"close\">Close\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from \"vue\";\n\nconst isOpen = ref(false);\n\nconst open = () => (isOpen.value = true);\nconst close = () => (isOpen.value = false);\n\n\u002F\u002F Expose open\u002Fclose for parent component to use\ndefineExpose({ open, close });\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.modal-backdrop {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100vw;\n  height: 100vh;\n  background-color: rgba(0, 0, 0, 0.5);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  z-index: 1000;\n}\n.modal-content {\n  background: white;\n  padding: 20px;\n  border-radius: 8px;\n  min-width: 300px;\n  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n}\n\u003C\u002Fstyle>\n",[39,3031,3032,3037,3045,3061,3092,3110,3124,3137,3157,3166,3174,3182,3190,3194,3204,3217,3221,3239,3243,3265,3286,3290,3295,3302,3310,3314,3325,3332,3345,3357,3369,3385,3400,3430,3443,3456,3468,3481,3486,3494,3507,3523,3538,3553,3595,3600],{"__ignoreMap":37},[42,3033,3034],{"class":44,"line":45},[42,3035,3036],{"class":56},"\u003C!-- components\u002FMyModal.vue -->\n",[42,3038,3039,3041,3043],{"class":44,"line":60},[42,3040,1353],{"class":52},[42,3042,2731],{"class":2196},[42,3044,2734],{"class":52},[42,3046,3047,3049,3051,3054,3056,3059],{"class":44,"line":86},[42,3048,2739],{"class":52},[42,3050,2948],{"class":2196},[42,3052,3053],{"class":400}," to",[42,3055,1326],{"class":52},[42,3057,3058],{"class":613},"\"#modals-container\"",[42,3060,2734],{"class":52},[42,3062,3063,3065,3067,3069,3071,3074,3077,3079,3082,3085,3087,3090],{"class":44,"line":92},[42,3064,2749],{"class":52},[42,3066,2742],{"class":2196},[42,3068,2754],{"class":400},[42,3070,1326],{"class":52},[42,3072,3073],{"class":613},"\"isOpen\"",[42,3075,3076],{"class":400}," class",[42,3078,1326],{"class":52},[42,3080,3081],{"class":613},"\"modal-backdrop\"",[42,3083,3084],{"class":400}," @click",[42,3086,1326],{"class":52},[42,3088,3089],{"class":613},"\"close\"",[42,3091,2734],{"class":52},[42,3093,3094,3096,3098,3100,3102,3105,3108],{"class":44,"line":188},[42,3095,2803],{"class":52},[42,3097,2742],{"class":2196},[42,3099,3076],{"class":400},[42,3101,1326],{"class":52},[42,3103,3104],{"class":613},"\"modal-content\"",[42,3106,3107],{"class":400}," @click.stop",[42,3109,2734],{"class":52},[42,3111,3112,3115,3117,3120,3122],{"class":44,"line":194},[42,3113,3114],{"class":52},"        \u003C",[42,3116,1775],{"class":2196},[42,3118,3119],{"class":52},">My Awesome Modal\u003C\u002F",[42,3121,1775],{"class":2196},[42,3123,2734],{"class":52},[42,3125,3126,3128,3130,3133,3135],{"class":44,"line":249},[42,3127,3114],{"class":52},[42,3129,15],{"class":2196},[42,3131,3132],{"class":52},">This modal content is rendered outside the component!\u003C\u002F",[42,3134,15],{"class":2196},[42,3136,2734],{"class":52},[42,3138,3139,3141,3144,3146,3148,3150,3153,3155],{"class":44,"line":263},[42,3140,3114],{"class":52},[42,3142,3143],{"class":2196},"button",[42,3145,3084],{"class":400},[42,3147,1326],{"class":52},[42,3149,3089],{"class":613},[42,3151,3152],{"class":52},">Close\u003C\u002F",[42,3154,3143],{"class":2196},[42,3156,2734],{"class":52},[42,3158,3159,3162,3164],{"class":44,"line":268},[42,3160,3161],{"class":52},"      \u003C\u002F",[42,3163,2742],{"class":2196},[42,3165,2734],{"class":52},[42,3167,3168,3170,3172],{"class":44,"line":273},[42,3169,2833],{"class":52},[42,3171,2742],{"class":2196},[42,3173,2734],{"class":52},[42,3175,3176,3178,3180],{"class":44,"line":287},[42,3177,2842],{"class":52},[42,3179,2948],{"class":2196},[42,3181,2734],{"class":52},[42,3183,3184,3186,3188],{"class":44,"line":292},[42,3185,2851],{"class":52},[42,3187,2731],{"class":2196},[42,3189,2734],{"class":52},[42,3191,3192],{"class":44,"line":297},[42,3193,301],{"emptyLinePlaceholder":300},[42,3195,3196,3198,3200,3202],{"class":44,"line":304},[42,3197,1353],{"class":52},[42,3199,2866],{"class":2196},[42,3201,2869],{"class":400},[42,3203,2734],{"class":52},[42,3205,3206,3208,3211,3213,3215],{"class":44,"line":317},[42,3207,2421],{"class":48},[42,3209,3210],{"class":52}," { ref } ",[42,3212,2427],{"class":48},[42,3214,2430],{"class":613},[42,3216,419],{"class":52},[42,3218,3219],{"class":44,"line":326},[42,3220,301],{"emptyLinePlaceholder":300},[42,3222,3223,3225,3228,3230,3232,3234,3237],{"class":44,"line":334},[42,3224,604],{"class":48},[42,3226,3227],{"class":400}," isOpen",[42,3229,610],{"class":48},[42,3231,2464],{"class":66},[42,3233,70],{"class":52},[42,3235,3236],{"class":400},"false",[42,3238,795],{"class":52},[42,3240,3241],{"class":44,"line":342},[42,3242,301],{"emptyLinePlaceholder":300},[42,3244,3245,3247,3250,3252,3254,3256,3259,3261,3263],{"class":44,"line":350},[42,3246,604],{"class":48},[42,3248,3249],{"class":66}," open",[42,3251,610],{"class":48},[42,3253,966],{"class":52},[42,3255,969],{"class":48},[42,3257,3258],{"class":52}," (isOpen.value ",[42,3260,1326],{"class":48},[42,3262,2532],{"class":400},[42,3264,795],{"class":52},[42,3266,3267,3269,3272,3274,3276,3278,3280,3282,3284],{"class":44,"line":2652},[42,3268,604],{"class":48},[42,3270,3271],{"class":66}," close",[42,3273,610],{"class":48},[42,3275,966],{"class":52},[42,3277,969],{"class":48},[42,3279,3258],{"class":52},[42,3281,1326],{"class":48},[42,3283,2670],{"class":400},[42,3285,795],{"class":52},[42,3287,3288],{"class":44,"line":2662},[42,3289,301],{"emptyLinePlaceholder":300},[42,3291,3292],{"class":44,"line":2675},[42,3293,3294],{"class":56},"\u002F\u002F Expose open\u002Fclose for parent component to use\n",[42,3296,3297,3299],{"class":44,"line":2680},[42,3298,1940],{"class":66},[42,3300,3301],{"class":52},"({ open, close });\n",[42,3303,3304,3306,3308],{"class":44,"line":2685},[42,3305,2851],{"class":52},[42,3307,2866],{"class":2196},[42,3309,2734],{"class":52},[42,3311,3312],{"class":44,"line":2690},[42,3313,301],{"emptyLinePlaceholder":300},[42,3315,3316,3318,3320,3323],{"class":44,"line":2699},[42,3317,1353],{"class":52},[42,3319,1592],{"class":2196},[42,3321,3322],{"class":400}," scoped",[42,3324,2734],{"class":52},[42,3326,3327,3330],{"class":44,"line":2704},[42,3328,3329],{"class":400},".modal-backdrop",[42,3331,730],{"class":52},[42,3333,3334,3337,3340,3343],{"class":44,"line":2712},[42,3335,3336],{"class":400},"  position",[42,3338,3339],{"class":52},": ",[42,3341,3342],{"class":400},"fixed",[42,3344,419],{"class":52},[42,3346,3348,3351,3353,3355],{"class":44,"line":3347},29,[42,3349,3350],{"class":400},"  top",[42,3352,3339],{"class":52},[42,3354,1278],{"class":400},[42,3356,419],{"class":52},[42,3358,3360,3363,3365,3367],{"class":44,"line":3359},30,[42,3361,3362],{"class":400},"  left",[42,3364,3339],{"class":52},[42,3366,1278],{"class":400},[42,3368,419],{"class":52},[42,3370,3372,3375,3377,3380,3383],{"class":44,"line":3371},31,[42,3373,3374],{"class":400},"  width",[42,3376,3339],{"class":52},[42,3378,3379],{"class":400},"100",[42,3381,3382],{"class":48},"vw",[42,3384,419],{"class":52},[42,3386,3388,3391,3393,3395,3398],{"class":44,"line":3387},32,[42,3389,3390],{"class":400},"  height",[42,3392,3339],{"class":52},[42,3394,3379],{"class":400},[42,3396,3397],{"class":48},"vh",[42,3399,419],{"class":52},[42,3401,3403,3406,3408,3411,3413,3415,3417,3419,3421,3423,3425,3428],{"class":44,"line":3402},33,[42,3404,3405],{"class":400},"  background-color",[42,3407,3339],{"class":52},[42,3409,3410],{"class":400},"rgba",[42,3412,70],{"class":52},[42,3414,1278],{"class":400},[42,3416,77],{"class":52},[42,3418,1278],{"class":400},[42,3420,77],{"class":52},[42,3422,1278],{"class":400},[42,3424,77],{"class":52},[42,3426,3427],{"class":400},"0.5",[42,3429,795],{"class":52},[42,3431,3433,3436,3438,3441],{"class":44,"line":3432},34,[42,3434,3435],{"class":400},"  display",[42,3437,3339],{"class":52},[42,3439,3440],{"class":400},"flex",[42,3442,419],{"class":52},[42,3444,3446,3449,3451,3454],{"class":44,"line":3445},35,[42,3447,3448],{"class":400},"  justify-content",[42,3450,3339],{"class":52},[42,3452,3453],{"class":400},"center",[42,3455,419],{"class":52},[42,3457,3459,3462,3464,3466],{"class":44,"line":3458},36,[42,3460,3461],{"class":400},"  align-items",[42,3463,3339],{"class":52},[42,3465,3453],{"class":400},[42,3467,419],{"class":52},[42,3469,3471,3474,3476,3479],{"class":44,"line":3470},37,[42,3472,3473],{"class":400},"  z-index",[42,3475,3339],{"class":52},[42,3477,3478],{"class":400},"1000",[42,3480,419],{"class":52},[42,3482,3484],{"class":44,"line":3483},38,[42,3485,141],{"class":52},[42,3487,3489,3492],{"class":44,"line":3488},39,[42,3490,3491],{"class":400},".modal-content",[42,3493,730],{"class":52},[42,3495,3497,3500,3502,3505],{"class":44,"line":3496},40,[42,3498,3499],{"class":400},"  background",[42,3501,3339],{"class":52},[42,3503,3504],{"class":400},"white",[42,3506,419],{"class":52},[42,3508,3510,3513,3515,3518,3521],{"class":44,"line":3509},41,[42,3511,3512],{"class":400},"  padding",[42,3514,3339],{"class":52},[42,3516,3517],{"class":400},"20",[42,3519,3520],{"class":48},"px",[42,3522,419],{"class":52},[42,3524,3526,3529,3531,3534,3536],{"class":44,"line":3525},42,[42,3527,3528],{"class":400},"  border-radius",[42,3530,3339],{"class":52},[42,3532,3533],{"class":400},"8",[42,3535,3520],{"class":48},[42,3537,419],{"class":52},[42,3539,3541,3544,3546,3549,3551],{"class":44,"line":3540},43,[42,3542,3543],{"class":400},"  min-width",[42,3545,3339],{"class":52},[42,3547,3548],{"class":400},"300",[42,3550,3520],{"class":48},[42,3552,419],{"class":52},[42,3554,3556,3559,3561,3563,3566,3568,3571,3573,3576,3578,3580,3582,3584,3586,3588,3590,3593],{"class":44,"line":3555},44,[42,3557,3558],{"class":400},"  box-shadow",[42,3560,3339],{"class":52},[42,3562,1278],{"class":400},[42,3564,3565],{"class":400}," 4",[42,3567,3520],{"class":48},[42,3569,3570],{"class":400}," 6",[42,3572,3520],{"class":48},[42,3574,3575],{"class":400}," rgba",[42,3577,70],{"class":52},[42,3579,1278],{"class":400},[42,3581,77],{"class":52},[42,3583,1278],{"class":400},[42,3585,77],{"class":52},[42,3587,1278],{"class":400},[42,3589,77],{"class":52},[42,3591,3592],{"class":400},"0.1",[42,3594,795],{"class":52},[42,3596,3598],{"class":44,"line":3597},45,[42,3599,141],{"class":52},[42,3601,3603,3605,3607],{"class":44,"line":3602},46,[42,3604,2851],{"class":52},[42,3606,1592],{"class":2196},[42,3608,2734],{"class":52},[15,3610,3611,3612,3615,3616,3619],{},"Wherever ",[39,3613,3614],{},"MyModal"," is used, its content lands in ",[39,3617,3618],{},"#modals-container",", which keeps styling conflicts out of the picture.",[10,3621,3623],{"id":3622},"_3-suspense-for-async-loading-states","3. Suspense for async loading states",[15,3625,3626,3627,3630],{},"Waiting on data or asynchronously loaded components makes a UI feel sluggish. ",[39,3628,3629],{},"Suspense"," coordinates async dependencies in a component tree and shows a fallback while they resolve, so users see a loading state rather than a blank area or a broken layout.",[15,3632,3633],{},"A component that fetches on mount:",[32,3635,3637],{"className":2720,"code":3636,"language":2722,"meta":37,"style":37},"\u003C!-- components\u002FAsyncDataDisplay.vue -->\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Ch2>Data from API:\u003C\u002Fh2>\n    \u003Cp>{{ data.title }}\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from \"vue\";\n\nconst data = ref(null);\n\n\u002F\u002F Simulate an async data fetch\nawait new Promise((resolve) => setTimeout(resolve, 2000));\nconst res = await fetch(\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Ftodos\u002F1\");\ndata.value = await res.json();\n\u003C\u002Fscript>\n",[39,3638,3639,3644,3652,3660,3673,3686,3694,3702,3706,3716,3728,3732,3748,3752,3757,3788,3807,3822],{"__ignoreMap":37},[42,3640,3641],{"class":44,"line":45},[42,3642,3643],{"class":56},"\u003C!-- components\u002FAsyncDataDisplay.vue -->\n",[42,3645,3646,3648,3650],{"class":44,"line":60},[42,3647,1353],{"class":52},[42,3649,2731],{"class":2196},[42,3651,2734],{"class":52},[42,3653,3654,3656,3658],{"class":44,"line":86},[42,3655,2739],{"class":52},[42,3657,2742],{"class":2196},[42,3659,2734],{"class":52},[42,3661,3662,3664,3666,3669,3671],{"class":44,"line":92},[42,3663,2749],{"class":52},[42,3665,10],{"class":2196},[42,3667,3668],{"class":52},">Data from API:\u003C\u002F",[42,3670,10],{"class":2196},[42,3672,2734],{"class":52},[42,3674,3675,3677,3679,3682,3684],{"class":44,"line":188},[42,3676,2749],{"class":52},[42,3678,15],{"class":2196},[42,3680,3681],{"class":52},">{{ data.title }}\u003C\u002F",[42,3683,15],{"class":2196},[42,3685,2734],{"class":52},[42,3687,3688,3690,3692],{"class":44,"line":194},[42,3689,2842],{"class":52},[42,3691,2742],{"class":2196},[42,3693,2734],{"class":52},[42,3695,3696,3698,3700],{"class":44,"line":249},[42,3697,2851],{"class":52},[42,3699,2731],{"class":2196},[42,3701,2734],{"class":52},[42,3703,3704],{"class":44,"line":263},[42,3705,301],{"emptyLinePlaceholder":300},[42,3707,3708,3710,3712,3714],{"class":44,"line":268},[42,3709,1353],{"class":52},[42,3711,2866],{"class":2196},[42,3713,2869],{"class":400},[42,3715,2734],{"class":52},[42,3717,3718,3720,3722,3724,3726],{"class":44,"line":273},[42,3719,2421],{"class":48},[42,3721,3210],{"class":52},[42,3723,2427],{"class":48},[42,3725,2430],{"class":613},[42,3727,419],{"class":52},[42,3729,3730],{"class":44,"line":287},[42,3731,301],{"emptyLinePlaceholder":300},[42,3733,3734,3736,3738,3740,3742,3744,3746],{"class":44,"line":292},[42,3735,604],{"class":48},[42,3737,673],{"class":400},[42,3739,610],{"class":48},[42,3741,2464],{"class":66},[42,3743,70],{"class":52},[42,3745,2469],{"class":400},[42,3747,795],{"class":52},[42,3749,3750],{"class":44,"line":297},[42,3751,301],{"emptyLinePlaceholder":300},[42,3753,3754],{"class":44,"line":304},[42,3755,3756],{"class":56},"\u002F\u002F Simulate an async data fetch\n",[42,3758,3759,3762,3764,3767,3769,3772,3774,3776,3779,3782,3785],{"class":44,"line":317},[42,3760,3761],{"class":48},"await",[42,3763,772],{"class":48},[42,3765,3766],{"class":400}," Promise",[42,3768,1548],{"class":52},[42,3770,3771],{"class":73},"resolve",[42,3773,404],{"class":52},[42,3775,969],{"class":48},[42,3777,3778],{"class":66}," setTimeout",[42,3780,3781],{"class":52},"(resolve, ",[42,3783,3784],{"class":400},"2000",[42,3786,3787],{"class":52},"));\n",[42,3789,3790,3792,3794,3796,3798,3800,3802,3805],{"class":44,"line":326},[42,3791,604],{"class":48},[42,3793,2561],{"class":400},[42,3795,610],{"class":48},[42,3797,743],{"class":48},[42,3799,2568],{"class":66},[42,3801,70],{"class":52},[42,3803,3804],{"class":613},"\"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Ftodos\u002F1\"",[42,3806,795],{"class":52},[42,3808,3809,3812,3814,3816,3818,3820],{"class":44,"line":334},[42,3810,3811],{"class":52},"data.value ",[42,3813,1326],{"class":48},[42,3815,743],{"class":48},[42,3817,2626],{"class":52},[42,3819,816],{"class":66},[42,3821,819],{"class":52},[42,3823,3824,3826,3828],{"class":44,"line":342},[42,3825,2851],{"class":52},[42,3827,2866],{"class":2196},[42,3829,2734],{"class":52},[15,3831,3832,3833,3835],{},"Wrapped in ",[39,3834,3629],{},":",[32,3837,3839],{"className":2720,"code":3838,"language":2722,"meta":37,"style":37},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Ch1>My App\u003C\u002Fh1>\n    \u003CSuspense>\n      \u003C!-- Main content that might be async -->\n      \u003CAsyncDataDisplay \u002F>\n\n      \u003C!-- Fallback content while async components are loading -->\n      \u003Ctemplate #fallback>\n        \u003Cdiv>Loading awesome data...\u003C\u002Fdiv>\n      \u003C\u002Ftemplate>\n    \u003C\u002FSuspense>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { defineAsyncComponent } from \"vue\";\n\n\u002F\u002F Define an async component\nconst AsyncDataDisplay = defineAsyncComponent(\n  () => import(\".\u002Fcomponents\u002FAsyncDataDisplay.vue\")\n);\n\u003C\u002Fscript>\n",[39,3840,3841,3849,3857,3871,3879,3884,3894,3898,3903,3917,3930,3938,3946,3954,3962,3966,3976,3989,3993,3998,4012,4030,4034],{"__ignoreMap":37},[42,3842,3843,3845,3847],{"class":44,"line":45},[42,3844,1353],{"class":52},[42,3846,2731],{"class":2196},[42,3848,2734],{"class":52},[42,3850,3851,3853,3855],{"class":44,"line":60},[42,3852,2739],{"class":52},[42,3854,2742],{"class":2196},[42,3856,2734],{"class":52},[42,3858,3859,3861,3864,3867,3869],{"class":44,"line":86},[42,3860,2749],{"class":52},[42,3862,3863],{"class":2196},"h1",[42,3865,3866],{"class":52},">My App\u003C\u002F",[42,3868,3863],{"class":2196},[42,3870,2734],{"class":52},[42,3872,3873,3875,3877],{"class":44,"line":92},[42,3874,2749],{"class":52},[42,3876,3629],{"class":2196},[42,3878,2734],{"class":52},[42,3880,3881],{"class":44,"line":188},[42,3882,3883],{"class":56},"      \u003C!-- Main content that might be async -->\n",[42,3885,3886,3888,3891],{"class":44,"line":194},[42,3887,2803],{"class":52},[42,3889,3890],{"class":2196},"AsyncDataDisplay",[42,3892,3893],{"class":52}," \u002F>\n",[42,3895,3896],{"class":44,"line":249},[42,3897,301],{"emptyLinePlaceholder":300},[42,3899,3900],{"class":44,"line":263},[42,3901,3902],{"class":56},"      \u003C!-- Fallback content while async components are loading -->\n",[42,3904,3905,3907,3909,3912,3915],{"class":44,"line":268},[42,3906,2803],{"class":52},[42,3908,2731],{"class":2196},[42,3910,3911],{"class":52}," #",[42,3913,3914],{"class":400},"fallback",[42,3916,2734],{"class":52},[42,3918,3919,3921,3923,3926,3928],{"class":44,"line":273},[42,3920,3114],{"class":52},[42,3922,2742],{"class":2196},[42,3924,3925],{"class":52},">Loading awesome data...\u003C\u002F",[42,3927,2742],{"class":2196},[42,3929,2734],{"class":52},[42,3931,3932,3934,3936],{"class":44,"line":287},[42,3933,3161],{"class":52},[42,3935,2731],{"class":2196},[42,3937,2734],{"class":52},[42,3939,3940,3942,3944],{"class":44,"line":292},[42,3941,2833],{"class":52},[42,3943,3629],{"class":2196},[42,3945,2734],{"class":52},[42,3947,3948,3950,3952],{"class":44,"line":297},[42,3949,2842],{"class":52},[42,3951,2742],{"class":2196},[42,3953,2734],{"class":52},[42,3955,3956,3958,3960],{"class":44,"line":304},[42,3957,2851],{"class":52},[42,3959,2731],{"class":2196},[42,3961,2734],{"class":52},[42,3963,3964],{"class":44,"line":317},[42,3965,301],{"emptyLinePlaceholder":300},[42,3967,3968,3970,3972,3974],{"class":44,"line":326},[42,3969,1353],{"class":52},[42,3971,2866],{"class":2196},[42,3973,2869],{"class":400},[42,3975,2734],{"class":52},[42,3977,3978,3980,3983,3985,3987],{"class":44,"line":334},[42,3979,2421],{"class":48},[42,3981,3982],{"class":52}," { defineAsyncComponent } ",[42,3984,2427],{"class":48},[42,3986,2430],{"class":613},[42,3988,419],{"class":52},[42,3990,3991],{"class":44,"line":342},[42,3992,301],{"emptyLinePlaceholder":300},[42,3994,3995],{"class":44,"line":350},[42,3996,3997],{"class":56},"\u002F\u002F Define an async component\n",[42,3999,4000,4002,4005,4007,4010],{"class":44,"line":2652},[42,4001,604],{"class":48},[42,4003,4004],{"class":400}," AsyncDataDisplay",[42,4006,610],{"class":48},[42,4008,4009],{"class":66}," defineAsyncComponent",[42,4011,2917],{"class":52},[42,4013,4014,4017,4019,4022,4024,4027],{"class":44,"line":2662},[42,4015,4016],{"class":52},"  () ",[42,4018,969],{"class":48},[42,4020,4021],{"class":48}," import",[42,4023,70],{"class":52},[42,4025,4026],{"class":613},"\".\u002Fcomponents\u002FAsyncDataDisplay.vue\"",[42,4028,4029],{"class":52},")\n",[42,4031,4032],{"class":44,"line":2675},[42,4033,795],{"class":52},[42,4035,4036,4038,4040],{"class":44,"line":2680},[42,4037,2851],{"class":52},[42,4039,2866],{"class":2196},[42,4041,2734],{"class":52},[15,4043,4044,4045,4047,4048,4050],{},"While ",[39,4046,3890],{}," resolves the ",[39,4049,3761],{}," in its setup block, the fallback shows. When it finishes, the real content replaces it.",[10,4052,4054],{"id":4053},"_4-custom-directives-for-dom-work","4. Custom directives for DOM work",[15,4056,4057,4058,77,4061,4064,4065,786],{},"Sometimes a component isn't the right tool. For low-level DOM manipulation, or reusable behaviour that isn't tied to a specific component, custom directives give you direct access to the element they're bound to. Things like ",[39,4059,4060],{},"v-focus",[39,4062,4063],{},"v-tooltip"," or ",[39,4066,4067],{},"v-lazy-load",[32,4069,4071],{"className":34,"code":4070,"language":36,"meta":37,"style":37},"\u002F\u002F main.js or a dedicated directives file\nimport { createApp } from \"vue\";\nimport App from \".\u002FApp.vue\";\n\nconst app = createApp(App);\n\napp.directive(\"focus\", {\n  mounted(el) {\n    el.focus();\n  },\n});\n\napp.mount(\"#app\");\n",[39,4072,4073,4078,4091,4105,4109,4124,4128,4144,4156,4165,4170,4174,4178],{"__ignoreMap":37},[42,4074,4075],{"class":44,"line":45},[42,4076,4077],{"class":56},"\u002F\u002F main.js or a dedicated directives file\n",[42,4079,4080,4082,4085,4087,4089],{"class":44,"line":60},[42,4081,2421],{"class":48},[42,4083,4084],{"class":52}," { createApp } ",[42,4086,2427],{"class":48},[42,4088,2430],{"class":613},[42,4090,419],{"class":52},[42,4092,4093,4095,4098,4100,4103],{"class":44,"line":86},[42,4094,2421],{"class":48},[42,4096,4097],{"class":52}," App ",[42,4099,2427],{"class":48},[42,4101,4102],{"class":613}," \".\u002FApp.vue\"",[42,4104,419],{"class":52},[42,4106,4107],{"class":44,"line":92},[42,4108,301],{"emptyLinePlaceholder":300},[42,4110,4111,4113,4116,4118,4121],{"class":44,"line":188},[42,4112,604],{"class":48},[42,4114,4115],{"class":400}," app",[42,4117,610],{"class":48},[42,4119,4120],{"class":66}," createApp",[42,4122,4123],{"class":52},"(App);\n",[42,4125,4126],{"class":44,"line":194},[42,4127,301],{"emptyLinePlaceholder":300},[42,4129,4130,4133,4136,4138,4141],{"class":44,"line":249},[42,4131,4132],{"class":52},"app.",[42,4134,4135],{"class":66},"directive",[42,4137,70],{"class":52},[42,4139,4140],{"class":613},"\"focus\"",[42,4142,4143],{"class":52},", {\n",[42,4145,4146,4149,4151,4154],{"class":44,"line":263},[42,4147,4148],{"class":66},"  mounted",[42,4150,70],{"class":52},[42,4152,4153],{"class":73},"el",[42,4155,83],{"class":52},[42,4157,4158,4161,4163],{"class":44,"line":268},[42,4159,4160],{"class":52},"    el.",[42,4162,2021],{"class":66},[42,4164,819],{"class":52},[42,4166,4167],{"class":44,"line":273},[42,4168,4169],{"class":52},"  },\n",[42,4171,4172],{"class":44,"line":287},[42,4173,1289],{"class":52},[42,4175,4176],{"class":44,"line":292},[42,4177,301],{"emptyLinePlaceholder":300},[42,4179,4180,4182,4185,4187,4190],{"class":44,"line":297},[42,4181,4132],{"class":52},[42,4183,4184],{"class":66},"mount",[42,4186,70],{"class":52},[42,4188,4189],{"class":613},"\"#app\"",[42,4191,795],{"class":52},[15,4193,4194],{},"Used like this:",[32,4196,4198],{"className":2720,"code":4197,"language":2722,"meta":37,"style":37},"\u003Ctemplate>\n  \u003Cinput v-focus type=\"text\" placeholder=\"I will be focused on mount\" \u002F>\n\u003C\u002Ftemplate>\n",[39,4199,4200,4208,4236],{"__ignoreMap":37},[42,4201,4202,4204,4206],{"class":44,"line":45},[42,4203,1353],{"class":52},[42,4205,2731],{"class":2196},[42,4207,2734],{"class":52},[42,4209,4210,4212,4215,4218,4221,4223,4226,4229,4231,4234],{"class":44,"line":60},[42,4211,2739],{"class":52},[42,4213,4214],{"class":2196},"input",[42,4216,4217],{"class":400}," v-focus",[42,4219,4220],{"class":400}," type",[42,4222,1326],{"class":52},[42,4224,4225],{"class":613},"\"text\"",[42,4227,4228],{"class":400}," placeholder",[42,4230,1326],{"class":52},[42,4232,4233],{"class":613},"\"I will be focused on mount\"",[42,4235,3893],{"class":52},[42,4237,4238,4240,4242],{"class":44,"line":86},[42,4239,2851],{"class":52},[42,4241,2731],{"class":2196},[42,4243,2734],{"class":52},[15,4245,4246,4247,77,4250,77,4253,4256,4257,4260],{},"The input gains focus when the component mounts. Directives can hook into ",[39,4248,4249],{},"created",[39,4251,4252],{},"mounted",[39,4254,4255],{},"updated"," and ",[39,4258,4259],{},"unmounted"," to control behaviour across the element's lifecycle.",[10,4262,4264],{"id":4263},"_5-render-functions-and-jsx","5. Render functions and JSX",[15,4266,4267],{},"For almost everything, Vue's template syntax is the right choice: declarative, readable and compiled efficiently. Render functions and JSX are for the rare cases where you need programmatic control over rendering, such as a highly dynamic table generator or a library component whose structure changes with complex props.",[15,4269,4270],{},"You probably won't reach for this unless the structure varies a lot, you're writing a UI library and need maximum flexibility, or you'd rather write components entirely in JavaScript or TypeScript.",[32,4272,4274],{"className":34,"code":4273,"language":36,"meta":37,"style":37},"\u002F\u002F components\u002FDynamicHeading.vue\nimport { h } from \"vue\";\n\nexport default {\n  props: {\n    level: {\n      type: Number,\n      required: true,\n      validator: (val) => val >= 1 && val \u003C= 6,\n    },\n  },\n  setup(props) {\n    return () => h(`h${props.level}`, `This is a level ${props.level} heading`);\n  },\n};\n",[39,4275,4276,4281,4294,4298,4307,4312,4317,4322,4331,4367,4372,4379,4389,4431,4437],{"__ignoreMap":37},[42,4277,4278],{"class":44,"line":45},[42,4279,4280],{"class":56},"\u002F\u002F components\u002FDynamicHeading.vue\n",[42,4282,4283,4285,4288,4290,4292],{"class":44,"line":60},[42,4284,2421],{"class":48},[42,4286,4287],{"class":52}," { h } ",[42,4289,2427],{"class":48},[42,4291,2430],{"class":613},[42,4293,419],{"class":52},[42,4295,4296],{"class":44,"line":86},[42,4297,301],{"emptyLinePlaceholder":300},[42,4299,4300,4302,4305],{"class":44,"line":92},[42,4301,2441],{"class":48},[42,4303,4304],{"class":48}," default",[42,4306,730],{"class":73},[42,4308,4309],{"class":44,"line":188},[42,4310,4311],{"class":52},"  props: {\n",[42,4313,4314],{"class":44,"line":194},[42,4315,4316],{"class":52},"    level: {\n",[42,4318,4319],{"class":44,"line":249},[42,4320,4321],{"class":52},"      type: Number,\n",[42,4323,4324,4327,4329],{"class":44,"line":263},[42,4325,4326],{"class":52},"      required: ",[42,4328,2504],{"class":400},[42,4330,2208],{"class":52},[42,4332,4333,4336,4339,4342,4344,4346,4349,4352,4355,4358,4360,4363,4365],{"class":44,"line":268},[42,4334,4335],{"class":66},"      validator",[42,4337,4338],{"class":52},": (",[42,4340,4341],{"class":73},"val",[42,4343,404],{"class":52},[42,4345,969],{"class":48},[42,4347,4348],{"class":52}," val ",[42,4350,4351],{"class":48},">=",[42,4353,4354],{"class":400}," 1",[42,4356,4357],{"class":48}," &&",[42,4359,4348],{"class":52},[42,4361,4362],{"class":48},"\u003C=",[42,4364,3570],{"class":400},[42,4366,2208],{"class":52},[42,4368,4369],{"class":44,"line":273},[42,4370,4371],{"class":52},"    },\n",[42,4373,4374,4377],{"class":44,"line":287},[42,4375,4376],{"class":52},"  }",[42,4378,2208],{"class":73},[42,4380,4381,4384,4387],{"class":44,"line":292},[42,4382,4383],{"class":66},"  setup",[42,4385,4386],{"class":73},"(props) ",[42,4388,2191],{"class":52},[42,4390,4391,4393,4395,4397,4400,4402,4405,4408,4410,4413,4415,4417,4420,4422,4424,4426,4429],{"class":44,"line":297},[42,4392,824],{"class":48},[42,4394,966],{"class":52},[42,4396,969],{"class":48},[42,4398,4399],{"class":66}," h",[42,4401,70],{"class":52},[42,4403,4404],{"class":613},"`h${",[42,4406,4407],{"class":52},"props",[42,4409,786],{"class":613},[42,4411,4412],{"class":52},"level",[42,4414,792],{"class":613},[42,4416,77],{"class":52},[42,4418,4419],{"class":613},"`This is a level ${",[42,4421,4407],{"class":52},[42,4423,786],{"class":613},[42,4425,4412],{"class":52},[42,4427,4428],{"class":613},"} heading`",[42,4430,795],{"class":52},[42,4432,4433,4435],{"class":44,"line":304},[42,4434,4376],{"class":52},[42,4436,2208],{"class":73},[42,4438,4439,4442],{"class":44,"line":317},[42,4440,4441],{"class":73},"}",[42,4443,419],{"class":52},[15,4445,4446],{},"The same thing with JSX, which needs the Vue JSX Babel setup:",[32,4448,4452],{"className":4449,"code":4450,"language":4451,"meta":37,"style":37},"language-jsx shiki shiki-themes github-dark-default","\u002F\u002F components\u002FDynamicHeadingJSX.vue\nexport default {\n  props: {\n    level: {\n      type: Number,\n      required: true,\n      validator: (val) => val >= 1 && val \u003C= 6,\n    },\n  },\n  setup(props) {\n    const HeadingTag = `h${props.level}`;\n    return () => (\n      \u003CHeadingTag>This is a level {props.level} heading (with JSX)\u003C\u002FHeadingTag>\n    );\n  },\n};\n","jsx",[39,4453,4454,4459,4467,4471,4475,4479,4487,4515,4519,4525,4533,4555,4566,4591,4596,4602],{"__ignoreMap":37},[42,4455,4456],{"class":44,"line":45},[42,4457,4458],{"class":56},"\u002F\u002F components\u002FDynamicHeadingJSX.vue\n",[42,4460,4461,4463,4465],{"class":44,"line":60},[42,4462,2441],{"class":48},[42,4464,4304],{"class":48},[42,4466,730],{"class":73},[42,4468,4469],{"class":44,"line":86},[42,4470,4311],{"class":52},[42,4472,4473],{"class":44,"line":92},[42,4474,4316],{"class":52},[42,4476,4477],{"class":44,"line":188},[42,4478,4321],{"class":52},[42,4480,4481,4483,4485],{"class":44,"line":194},[42,4482,4326],{"class":52},[42,4484,2504],{"class":400},[42,4486,2208],{"class":52},[42,4488,4489,4491,4493,4495,4497,4499,4501,4503,4505,4507,4509,4511,4513],{"class":44,"line":249},[42,4490,4335],{"class":66},[42,4492,4338],{"class":52},[42,4494,4341],{"class":73},[42,4496,404],{"class":52},[42,4498,969],{"class":48},[42,4500,4348],{"class":52},[42,4502,4351],{"class":48},[42,4504,4354],{"class":400},[42,4506,4357],{"class":48},[42,4508,4348],{"class":52},[42,4510,4362],{"class":48},[42,4512,3570],{"class":400},[42,4514,2208],{"class":52},[42,4516,4517],{"class":44,"line":263},[42,4518,4371],{"class":52},[42,4520,4521,4523],{"class":44,"line":268},[42,4522,4376],{"class":52},[42,4524,2208],{"class":73},[42,4526,4527,4529,4531],{"class":44,"line":273},[42,4528,4383],{"class":66},[42,4530,4386],{"class":73},[42,4532,2191],{"class":52},[42,4534,4535,4537,4540,4542,4545,4547,4549,4551,4553],{"class":44,"line":287},[42,4536,735],{"class":48},[42,4538,4539],{"class":400}," HeadingTag",[42,4541,610],{"class":48},[42,4543,4544],{"class":613}," `h${",[42,4546,4407],{"class":52},[42,4548,786],{"class":613},[42,4550,4412],{"class":52},[42,4552,792],{"class":613},[42,4554,419],{"class":52},[42,4556,4557,4559,4561,4563],{"class":44,"line":292},[42,4558,824],{"class":48},[42,4560,966],{"class":52},[42,4562,969],{"class":48},[42,4564,4565],{"class":52}," (\n",[42,4567,4568,4570,4573,4576,4579,4582,4584,4587,4589],{"class":44,"line":297},[42,4569,2803],{"class":52},[42,4571,4572],{"class":2196},"HeadingTag",[42,4574,4575],{"class":52},">This is a level ",[42,4577,4578],{"class":48},"{",[42,4580,4581],{"class":52},"props.level",[42,4583,4441],{"class":48},[42,4585,4586],{"class":52}," heading (with JSX)\u003C\u002F",[42,4588,4572],{"class":2196},[42,4590,2734],{"class":52},[42,4592,4593],{"class":44,"line":304},[42,4594,4595],{"class":52},"    );\n",[42,4597,4598,4600],{"class":44,"line":317},[42,4599,4376],{"class":52},[42,4601,2208],{"class":73},[42,4603,4604,4606],{"class":44,"line":326},[42,4605,4441],{"class":73},[42,4607,419],{"class":52},[10,4609,4611],{"id":4610},"_6-provide-and-inject","6. Provide and inject",[15,4613,4614],{},"For genuinely global state, Pinia is the answer. Provide and inject are for the middle case: data or utilities that need to reach deep into a component tree without passing props through every level.",[15,4616,4617],{},"They suit theme information, user preferences, utility functions, and data that rarely changes but is relevant to a whole subtree.",[32,4619,4621],{"className":2720,"code":4620,"language":2722,"meta":37,"style":37},"\u003C!-- components\u002FGrandparentComponent.vue -->\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Ch1>Grandparent\u003C\u002Fh1>\n    \u003Cbutton @click=\"toggleTheme\">Toggle Theme\u003C\u002Fbutton>\n    \u003CChildComponent \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { provide, ref } from \"vue\";\nimport ChildComponent from \".\u002FChildComponent.vue\";\n\nconst theme = ref(\"light\");\n\nconst toggleTheme = () => {\n  theme.value = theme.value === \"light\" ? \"dark\" : \"light\";\n};\n\nprovide(\"appTheme\", theme); \u002F\u002F Provide the reactive theme\nprovide(\"toggleThemeFunction\", toggleTheme); \u002F\u002F Provide a function too!\n\u003C\u002Fscript>\n",[39,4622,4623,4628,4636,4644,4657,4677,4686,4694,4702,4706,4716,4729,4743,4747,4765,4769,4784,4812,4816,4820,4836,4851],{"__ignoreMap":37},[42,4624,4625],{"class":44,"line":45},[42,4626,4627],{"class":56},"\u003C!-- components\u002FGrandparentComponent.vue -->\n",[42,4629,4630,4632,4634],{"class":44,"line":60},[42,4631,1353],{"class":52},[42,4633,2731],{"class":2196},[42,4635,2734],{"class":52},[42,4637,4638,4640,4642],{"class":44,"line":86},[42,4639,2739],{"class":52},[42,4641,2742],{"class":2196},[42,4643,2734],{"class":52},[42,4645,4646,4648,4650,4653,4655],{"class":44,"line":92},[42,4647,2749],{"class":52},[42,4649,3863],{"class":2196},[42,4651,4652],{"class":52},">Grandparent\u003C\u002F",[42,4654,3863],{"class":2196},[42,4656,2734],{"class":52},[42,4658,4659,4661,4663,4665,4667,4670,4673,4675],{"class":44,"line":188},[42,4660,2749],{"class":52},[42,4662,3143],{"class":2196},[42,4664,3084],{"class":400},[42,4666,1326],{"class":52},[42,4668,4669],{"class":613},"\"toggleTheme\"",[42,4671,4672],{"class":52},">Toggle Theme\u003C\u002F",[42,4674,3143],{"class":2196},[42,4676,2734],{"class":52},[42,4678,4679,4681,4684],{"class":44,"line":194},[42,4680,2749],{"class":52},[42,4682,4683],{"class":2196},"ChildComponent",[42,4685,3893],{"class":52},[42,4687,4688,4690,4692],{"class":44,"line":249},[42,4689,2842],{"class":52},[42,4691,2742],{"class":2196},[42,4693,2734],{"class":52},[42,4695,4696,4698,4700],{"class":44,"line":263},[42,4697,2851],{"class":52},[42,4699,2731],{"class":2196},[42,4701,2734],{"class":52},[42,4703,4704],{"class":44,"line":268},[42,4705,301],{"emptyLinePlaceholder":300},[42,4707,4708,4710,4712,4714],{"class":44,"line":273},[42,4709,1353],{"class":52},[42,4711,2866],{"class":2196},[42,4713,2869],{"class":400},[42,4715,2734],{"class":52},[42,4717,4718,4720,4723,4725,4727],{"class":44,"line":287},[42,4719,2421],{"class":48},[42,4721,4722],{"class":52}," { provide, ref } ",[42,4724,2427],{"class":48},[42,4726,2430],{"class":613},[42,4728,419],{"class":52},[42,4730,4731,4733,4736,4738,4741],{"class":44,"line":292},[42,4732,2421],{"class":48},[42,4734,4735],{"class":52}," ChildComponent ",[42,4737,2427],{"class":48},[42,4739,4740],{"class":613}," \".\u002FChildComponent.vue\"",[42,4742,419],{"class":52},[42,4744,4745],{"class":44,"line":297},[42,4746,301],{"emptyLinePlaceholder":300},[42,4748,4749,4751,4754,4756,4758,4760,4763],{"class":44,"line":304},[42,4750,604],{"class":48},[42,4752,4753],{"class":400}," theme",[42,4755,610],{"class":48},[42,4757,2464],{"class":66},[42,4759,70],{"class":52},[42,4761,4762],{"class":613},"\"light\"",[42,4764,795],{"class":52},[42,4766,4767],{"class":44,"line":317},[42,4768,301],{"emptyLinePlaceholder":300},[42,4770,4771,4773,4776,4778,4780,4782],{"class":44,"line":326},[42,4772,604],{"class":48},[42,4774,4775],{"class":66}," toggleTheme",[42,4777,610],{"class":48},[42,4779,966],{"class":52},[42,4781,969],{"class":48},[42,4783,730],{"class":52},[42,4785,4786,4789,4791,4794,4796,4799,4802,4805,4808,4810],{"class":44,"line":334},[42,4787,4788],{"class":52},"  theme.value ",[42,4790,1326],{"class":48},[42,4792,4793],{"class":52}," theme.value ",[42,4795,1389],{"class":48},[42,4797,4798],{"class":613}," \"light\"",[42,4800,4801],{"class":48}," ?",[42,4803,4804],{"class":613}," \"dark\"",[42,4806,4807],{"class":48}," :",[42,4809,4798],{"class":613},[42,4811,419],{"class":52},[42,4813,4814],{"class":44,"line":342},[42,4815,980],{"class":52},[42,4817,4818],{"class":44,"line":350},[42,4819,301],{"emptyLinePlaceholder":300},[42,4821,4822,4825,4827,4830,4833],{"class":44,"line":2652},[42,4823,4824],{"class":66},"provide",[42,4826,70],{"class":52},[42,4828,4829],{"class":613},"\"appTheme\"",[42,4831,4832],{"class":52},", theme); ",[42,4834,4835],{"class":56},"\u002F\u002F Provide the reactive theme\n",[42,4837,4838,4840,4842,4845,4848],{"class":44,"line":2662},[42,4839,4824],{"class":66},[42,4841,70],{"class":52},[42,4843,4844],{"class":613},"\"toggleThemeFunction\"",[42,4846,4847],{"class":52},", toggleTheme); ",[42,4849,4850],{"class":56},"\u002F\u002F Provide a function too!\n",[42,4852,4853,4855,4857],{"class":44,"line":2675},[42,4854,2851],{"class":52},[42,4856,2866],{"class":2196},[42,4858,2734],{"class":52},[32,4860,4862],{"className":2720,"code":4861,"language":2722,"meta":37,"style":37},"\u003C!-- components\u002FDeeplyNestedComponent.vue -->\n\u003Ctemplate>\n  \u003Cdiv :class=\"['card', appTheme === 'dark' ? 'dark-mode' : '']\">\n    \u003Cp>Current theme: {{ appTheme }}\u003C\u002Fp>\n    \u003Cbutton @click=\"toggleThemeFunction\">Toggle Theme (from deep)\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { inject } from \"vue\";\n\nconst appTheme = inject(\"appTheme\");\nconst toggleThemeFunction = inject(\"toggleThemeFunction\"); \u002F\u002F Inject the function\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.card {\n  border: 1px solid #ccc;\n  padding: 15px;\n  margin-top: 10px;\n  border-radius: 5px;\n}\n.dark-mode {\n  background-color: #333;\n  color: #eee;\n  border-color: #555;\n}\n\u003C\u002Fstyle>\n",[39,4863,4864,4869,4877,4893,4906,4925,4933,4941,4945,4955,4968,4972,4990,5011,5019,5023,5033,5040,5059,5072,5086,5098,5102,5109,5120,5132,5144,5148],{"__ignoreMap":37},[42,4865,4866],{"class":44,"line":45},[42,4867,4868],{"class":56},"\u003C!-- components\u002FDeeplyNestedComponent.vue -->\n",[42,4870,4871,4873,4875],{"class":44,"line":60},[42,4872,1353],{"class":52},[42,4874,2731],{"class":2196},[42,4876,2734],{"class":52},[42,4878,4879,4881,4883,4886,4888,4891],{"class":44,"line":86},[42,4880,2739],{"class":52},[42,4882,2742],{"class":2196},[42,4884,4885],{"class":400}," :class",[42,4887,1326],{"class":52},[42,4889,4890],{"class":613},"\"['card', appTheme === 'dark' ? 'dark-mode' : '']\"",[42,4892,2734],{"class":52},[42,4894,4895,4897,4899,4902,4904],{"class":44,"line":92},[42,4896,2749],{"class":52},[42,4898,15],{"class":2196},[42,4900,4901],{"class":52},">Current theme: {{ appTheme }}\u003C\u002F",[42,4903,15],{"class":2196},[42,4905,2734],{"class":52},[42,4907,4908,4910,4912,4914,4916,4918,4921,4923],{"class":44,"line":188},[42,4909,2749],{"class":52},[42,4911,3143],{"class":2196},[42,4913,3084],{"class":400},[42,4915,1326],{"class":52},[42,4917,4844],{"class":613},[42,4919,4920],{"class":52},">Toggle Theme (from deep)\u003C\u002F",[42,4922,3143],{"class":2196},[42,4924,2734],{"class":52},[42,4926,4927,4929,4931],{"class":44,"line":194},[42,4928,2842],{"class":52},[42,4930,2742],{"class":2196},[42,4932,2734],{"class":52},[42,4934,4935,4937,4939],{"class":44,"line":249},[42,4936,2851],{"class":52},[42,4938,2731],{"class":2196},[42,4940,2734],{"class":52},[42,4942,4943],{"class":44,"line":263},[42,4944,301],{"emptyLinePlaceholder":300},[42,4946,4947,4949,4951,4953],{"class":44,"line":268},[42,4948,1353],{"class":52},[42,4950,2866],{"class":2196},[42,4952,2869],{"class":400},[42,4954,2734],{"class":52},[42,4956,4957,4959,4962,4964,4966],{"class":44,"line":273},[42,4958,2421],{"class":48},[42,4960,4961],{"class":52}," { inject } ",[42,4963,2427],{"class":48},[42,4965,2430],{"class":613},[42,4967,419],{"class":52},[42,4969,4970],{"class":44,"line":287},[42,4971,301],{"emptyLinePlaceholder":300},[42,4973,4974,4976,4979,4981,4984,4986,4988],{"class":44,"line":292},[42,4975,604],{"class":48},[42,4977,4978],{"class":400}," appTheme",[42,4980,610],{"class":48},[42,4982,4983],{"class":66}," inject",[42,4985,70],{"class":52},[42,4987,4829],{"class":613},[42,4989,795],{"class":52},[42,4991,4992,4994,4997,4999,5001,5003,5005,5008],{"class":44,"line":297},[42,4993,604],{"class":48},[42,4995,4996],{"class":400}," toggleThemeFunction",[42,4998,610],{"class":48},[42,5000,4983],{"class":66},[42,5002,70],{"class":52},[42,5004,4844],{"class":613},[42,5006,5007],{"class":52},"); ",[42,5009,5010],{"class":56},"\u002F\u002F Inject the function\n",[42,5012,5013,5015,5017],{"class":44,"line":304},[42,5014,2851],{"class":52},[42,5016,2866],{"class":2196},[42,5018,2734],{"class":52},[42,5020,5021],{"class":44,"line":317},[42,5022,301],{"emptyLinePlaceholder":300},[42,5024,5025,5027,5029,5031],{"class":44,"line":326},[42,5026,1353],{"class":52},[42,5028,1592],{"class":2196},[42,5030,3322],{"class":400},[42,5032,2734],{"class":52},[42,5034,5035,5038],{"class":44,"line":334},[42,5036,5037],{"class":400},".card",[42,5039,730],{"class":52},[42,5041,5042,5045,5047,5049,5051,5054,5057],{"class":44,"line":342},[42,5043,5044],{"class":400},"  border",[42,5046,3339],{"class":52},[42,5048,1155],{"class":400},[42,5050,3520],{"class":48},[42,5052,5053],{"class":400}," solid",[42,5055,5056],{"class":400}," #ccc",[42,5058,419],{"class":52},[42,5060,5061,5063,5065,5068,5070],{"class":44,"line":350},[42,5062,3512],{"class":400},[42,5064,3339],{"class":52},[42,5066,5067],{"class":400},"15",[42,5069,3520],{"class":48},[42,5071,419],{"class":52},[42,5073,5074,5077,5079,5082,5084],{"class":44,"line":2652},[42,5075,5076],{"class":400},"  margin-top",[42,5078,3339],{"class":52},[42,5080,5081],{"class":400},"10",[42,5083,3520],{"class":48},[42,5085,419],{"class":52},[42,5087,5088,5090,5092,5094,5096],{"class":44,"line":2662},[42,5089,3528],{"class":400},[42,5091,3339],{"class":52},[42,5093,1217],{"class":400},[42,5095,3520],{"class":48},[42,5097,419],{"class":52},[42,5099,5100],{"class":44,"line":2675},[42,5101,141],{"class":52},[42,5103,5104,5107],{"class":44,"line":2680},[42,5105,5106],{"class":400},".dark-mode",[42,5108,730],{"class":52},[42,5110,5111,5113,5115,5118],{"class":44,"line":2685},[42,5112,3405],{"class":400},[42,5114,3339],{"class":52},[42,5116,5117],{"class":400},"#333",[42,5119,419],{"class":52},[42,5121,5122,5125,5127,5130],{"class":44,"line":2690},[42,5123,5124],{"class":400},"  color",[42,5126,3339],{"class":52},[42,5128,5129],{"class":400},"#eee",[42,5131,419],{"class":52},[42,5133,5134,5137,5139,5142],{"class":44,"line":2699},[42,5135,5136],{"class":400},"  border-color",[42,5138,3339],{"class":52},[42,5140,5141],{"class":400},"#555",[42,5143,419],{"class":52},[42,5145,5146],{"class":44,"line":2704},[42,5147,141],{"class":52},[42,5149,5150,5152,5154],{"class":44,"line":2712},[42,5151,2851],{"class":52},[42,5153,1592],{"class":2196},[42,5155,2734],{"class":52},[15,5157,5158,5161,5162,5165,5166,786],{},[39,5159,5160],{},"DeeplyNestedComponent"," gets both the theme and the function without ",[39,5163,5164],{},"GrandparentComponent"," passing anything through ",[39,5167,4683],{},[10,5169,5171],{"id":5170},"_7-global-properties-and-plugins","7. Global properties and plugins",[15,5173,5174],{},"Sometimes something needs to be available across the whole application: an HTTP instance, a translation function, a shared utility.",[15,5176,5177,5178,5181,5182,5185,5186,5189],{},"The simplest route is ",[39,5179,5180],{},"app.config.globalProperties",", which attaches to ",[39,5183,5184],{},"this"," in the Options API and is reachable through ",[39,5187,5188],{},"getCurrentInstance()"," in the Composition API.",[32,5191,5193],{"className":34,"code":5192,"language":36,"meta":37,"style":37},"\u002F\u002F main.js\nimport { createApp } from \"vue\";\nimport App from \".\u002FApp.vue\";\n\nconst app = createApp(App);\n\n\u002F\u002F Attach a global property\napp.config.globalProperties.$myGlobalUtil = {\n  sayHello: () => console.log(\"Hello from global util!\"),\n  version: \"1.0.0\",\n};\n\napp.mount(\"#app\");\n",[39,5194,5195,5200,5212,5224,5228,5240,5244,5249,5258,5282,5292,5296,5300],{"__ignoreMap":37},[42,5196,5197],{"class":44,"line":45},[42,5198,5199],{"class":56},"\u002F\u002F main.js\n",[42,5201,5202,5204,5206,5208,5210],{"class":44,"line":60},[42,5203,2421],{"class":48},[42,5205,4084],{"class":52},[42,5207,2427],{"class":48},[42,5209,2430],{"class":613},[42,5211,419],{"class":52},[42,5213,5214,5216,5218,5220,5222],{"class":44,"line":86},[42,5215,2421],{"class":48},[42,5217,4097],{"class":52},[42,5219,2427],{"class":48},[42,5221,4102],{"class":613},[42,5223,419],{"class":52},[42,5225,5226],{"class":44,"line":92},[42,5227,301],{"emptyLinePlaceholder":300},[42,5229,5230,5232,5234,5236,5238],{"class":44,"line":188},[42,5231,604],{"class":48},[42,5233,4115],{"class":400},[42,5235,610],{"class":48},[42,5237,4120],{"class":66},[42,5239,4123],{"class":52},[42,5241,5242],{"class":44,"line":194},[42,5243,301],{"emptyLinePlaceholder":300},[42,5245,5246],{"class":44,"line":249},[42,5247,5248],{"class":56},"\u002F\u002F Attach a global property\n",[42,5250,5251,5254,5256],{"class":44,"line":263},[42,5252,5253],{"class":52},"app.config.globalProperties.$myGlobalUtil ",[42,5255,1326],{"class":48},[42,5257,730],{"class":52},[42,5259,5260,5263,5266,5268,5271,5274,5276,5279],{"class":44,"line":268},[42,5261,5262],{"class":66},"  sayHello",[42,5264,5265],{"class":52},": () ",[42,5267,969],{"class":48},[42,5269,5270],{"class":52}," console.",[42,5272,5273],{"class":66},"log",[42,5275,70],{"class":52},[42,5277,5278],{"class":613},"\"Hello from global util!\"",[42,5280,5281],{"class":52},"),\n",[42,5283,5284,5287,5290],{"class":44,"line":273},[42,5285,5286],{"class":52},"  version: ",[42,5288,5289],{"class":613},"\"1.0.0\"",[42,5291,2208],{"class":52},[42,5293,5294],{"class":44,"line":287},[42,5295,980],{"class":52},[42,5297,5298],{"class":44,"line":292},[42,5299,301],{"emptyLinePlaceholder":300},[42,5301,5302,5304,5306,5308,5310],{"class":44,"line":297},[42,5303,4132],{"class":52},[42,5305,4184],{"class":66},[42,5307,70],{"class":52},[42,5309,4189],{"class":613},[42,5311,795],{"class":52},[15,5313,5314,5315,5318],{},"In a template that's ",[39,5316,5317],{},"\u003Cbutton @click=\"$myGlobalUtil.sayHello()\">Say Hello\u003C\u002Fbutton>",", and in a script setup block:",[32,5320,5322],{"className":2720,"code":5321,"language":2722,"meta":37,"style":37},"\u003Cscript setup>\nimport { getCurrentInstance } from \"vue\";\nconst { proxy } = getCurrentInstance();\n\nproxy.$myGlobalUtil.sayHello();\nconsole.log(proxy.$myGlobalUtil.version);\n\u003C\u002Fscript>\n",[39,5323,5324,5334,5347,5365,5369,5379,5389],{"__ignoreMap":37},[42,5325,5326,5328,5330,5332],{"class":44,"line":45},[42,5327,1353],{"class":52},[42,5329,2866],{"class":2196},[42,5331,2869],{"class":400},[42,5333,2734],{"class":52},[42,5335,5336,5338,5341,5343,5345],{"class":44,"line":60},[42,5337,2421],{"class":48},[42,5339,5340],{"class":52}," { getCurrentInstance } ",[42,5342,2427],{"class":48},[42,5344,2430],{"class":613},[42,5346,419],{"class":52},[42,5348,5349,5351,5353,5356,5358,5360,5363],{"class":44,"line":86},[42,5350,604],{"class":48},[42,5352,2896],{"class":52},[42,5354,5355],{"class":400},"proxy",[42,5357,2910],{"class":52},[42,5359,1326],{"class":48},[42,5361,5362],{"class":66}," getCurrentInstance",[42,5364,819],{"class":52},[42,5366,5367],{"class":44,"line":92},[42,5368,301],{"emptyLinePlaceholder":300},[42,5370,5371,5374,5377],{"class":44,"line":188},[42,5372,5373],{"class":52},"proxy.$myGlobalUtil.",[42,5375,5376],{"class":66},"sayHello",[42,5378,819],{"class":52},[42,5380,5381,5384,5386],{"class":44,"line":194},[42,5382,5383],{"class":52},"console.",[42,5385,5273],{"class":66},[42,5387,5388],{"class":52},"(proxy.$myGlobalUtil.version);\n",[42,5390,5391,5393,5395],{"class":44,"line":249},[42,5392,2851],{"class":52},[42,5394,2866],{"class":2196},[42,5396,2734],{"class":52},[15,5398,5399,5400,5403],{},"For anything larger, involving several global properties, directives, components or routing logic, write a plugin. A plugin is a function that receives the ",[39,5401,5402],{},"app"," instance and an optional options object.",[32,5405,5407],{"className":34,"code":5406,"language":36,"meta":37,"style":37},"\u002F\u002F plugins\u002FmyPlugin.js\nexport default {\n  install: (app, options) => {\n    \u002F\u002F Make a global method available\n    app.config.globalProperties.$translate = (key) => {\n      return key\n        .split(\".\")\n        .reduce((o, i) => (o ? o[i] : undefined), options.translations);\n    };\n\n    \u002F\u002F Provide a global composable\n    app.provide(\"globalStore\", {\n      user: \"John Doe\",\n      settings: { theme: \"dark\" },\n    });\n\n    \u002F\u002F Or register a global component\n    \u002F\u002F app.component('MyGlobalComponent', MyGlobalComponent)\n  },\n};\n",[39,5408,5409,5414,5422,5442,5447,5468,5476,5491,5528,5533,5537,5542,5556,5566,5577,5582,5586,5591,5596,5602],{"__ignoreMap":37},[42,5410,5411],{"class":44,"line":45},[42,5412,5413],{"class":56},"\u002F\u002F plugins\u002FmyPlugin.js\n",[42,5415,5416,5418,5420],{"class":44,"line":60},[42,5417,2441],{"class":48},[42,5419,4304],{"class":48},[42,5421,730],{"class":73},[42,5423,5424,5427,5429,5431,5433,5436,5438,5440],{"class":44,"line":86},[42,5425,5426],{"class":66},"  install",[42,5428,4338],{"class":52},[42,5430,5402],{"class":73},[42,5432,77],{"class":52},[42,5434,5435],{"class":73},"options",[42,5437,404],{"class":52},[42,5439,969],{"class":48},[42,5441,730],{"class":52},[42,5443,5444],{"class":44,"line":92},[42,5445,5446],{"class":56},"    \u002F\u002F Make a global method available\n",[42,5448,5449,5452,5455,5457,5459,5462,5464,5466],{"class":44,"line":188},[42,5450,5451],{"class":52},"    app.config.globalProperties.",[42,5453,5454],{"class":66},"$translate",[42,5456,610],{"class":48},[42,5458,758],{"class":52},[42,5460,5461],{"class":73},"key",[42,5463,404],{"class":52},[42,5465,969],{"class":48},[42,5467,730],{"class":52},[42,5469,5470,5473],{"class":44,"line":194},[42,5471,5472],{"class":48},"      return",[42,5474,5475],{"class":52}," key\n",[42,5477,5478,5481,5484,5486,5489],{"class":44,"line":249},[42,5479,5480],{"class":52},"        .",[42,5482,5483],{"class":66},"split",[42,5485,70],{"class":52},[42,5487,5488],{"class":613},"\".\"",[42,5490,4029],{"class":52},[42,5492,5493,5495,5497,5499,5502,5504,5507,5509,5511,5514,5517,5520,5522,5525],{"class":44,"line":263},[42,5494,5480],{"class":52},[42,5496,1545],{"class":66},[42,5498,1548],{"class":52},[42,5500,5501],{"class":73},"o",[42,5503,77],{"class":52},[42,5505,5506],{"class":73},"i",[42,5508,404],{"class":52},[42,5510,969],{"class":48},[42,5512,5513],{"class":52}," (o ",[42,5515,5516],{"class":48},"?",[42,5518,5519],{"class":52}," o[i] ",[42,5521,3835],{"class":48},[42,5523,5524],{"class":400}," undefined",[42,5526,5527],{"class":52},"), options.translations);\n",[42,5529,5530],{"class":44,"line":268},[42,5531,5532],{"class":52},"    };\n",[42,5534,5535],{"class":44,"line":273},[42,5536,301],{"emptyLinePlaceholder":300},[42,5538,5539],{"class":44,"line":287},[42,5540,5541],{"class":56},"    \u002F\u002F Provide a global composable\n",[42,5543,5544,5547,5549,5551,5554],{"class":44,"line":292},[42,5545,5546],{"class":52},"    app.",[42,5548,4824],{"class":66},[42,5550,70],{"class":52},[42,5552,5553],{"class":613},"\"globalStore\"",[42,5555,4143],{"class":52},[42,5557,5558,5561,5564],{"class":44,"line":297},[42,5559,5560],{"class":52},"      user: ",[42,5562,5563],{"class":613},"\"John Doe\"",[42,5565,2208],{"class":52},[42,5567,5568,5571,5574],{"class":44,"line":304},[42,5569,5570],{"class":52},"      settings: { theme: ",[42,5572,5573],{"class":613},"\"dark\"",[42,5575,5576],{"class":52}," },\n",[42,5578,5579],{"class":44,"line":317},[42,5580,5581],{"class":52},"    });\n",[42,5583,5584],{"class":44,"line":326},[42,5585,301],{"emptyLinePlaceholder":300},[42,5587,5588],{"class":44,"line":334},[42,5589,5590],{"class":56},"    \u002F\u002F Or register a global component\n",[42,5592,5593],{"class":44,"line":342},[42,5594,5595],{"class":56},"    \u002F\u002F app.component('MyGlobalComponent', MyGlobalComponent)\n",[42,5597,5598,5600],{"class":44,"line":350},[42,5599,4376],{"class":52},[42,5601,2208],{"class":73},[42,5603,5604,5606],{"class":44,"line":2652},[42,5605,4441],{"class":73},[42,5607,419],{"class":52},[32,5609,5611],{"className":34,"code":5610,"language":36,"meta":37,"style":37},"\u002F\u002F main.js\nimport { createApp } from \"vue\";\nimport App from \".\u002FApp.vue\";\nimport MyPlugin from \".\u002Fplugins\u002FmyPlugin\";\n\nconst app = createApp(App);\n\nconst translations = {\n  hello: \"Bonjour\",\n  welcome: {\n    message: \"Bienvenue!\",\n  },\n};\n\napp.use(MyPlugin, { translations }); \u002F\u002F Use the plugin with options\napp.mount(\"#app\");\n",[39,5612,5613,5617,5629,5641,5655,5659,5671,5675,5686,5696,5701,5711,5715,5719,5723,5736],{"__ignoreMap":37},[42,5614,5615],{"class":44,"line":45},[42,5616,5199],{"class":56},[42,5618,5619,5621,5623,5625,5627],{"class":44,"line":60},[42,5620,2421],{"class":48},[42,5622,4084],{"class":52},[42,5624,2427],{"class":48},[42,5626,2430],{"class":613},[42,5628,419],{"class":52},[42,5630,5631,5633,5635,5637,5639],{"class":44,"line":86},[42,5632,2421],{"class":48},[42,5634,4097],{"class":52},[42,5636,2427],{"class":48},[42,5638,4102],{"class":613},[42,5640,419],{"class":52},[42,5642,5643,5645,5648,5650,5653],{"class":44,"line":92},[42,5644,2421],{"class":48},[42,5646,5647],{"class":52}," MyPlugin ",[42,5649,2427],{"class":48},[42,5651,5652],{"class":613}," \".\u002Fplugins\u002FmyPlugin\"",[42,5654,419],{"class":52},[42,5656,5657],{"class":44,"line":188},[42,5658,301],{"emptyLinePlaceholder":300},[42,5660,5661,5663,5665,5667,5669],{"class":44,"line":194},[42,5662,604],{"class":48},[42,5664,4115],{"class":400},[42,5666,610],{"class":48},[42,5668,4120],{"class":66},[42,5670,4123],{"class":52},[42,5672,5673],{"class":44,"line":249},[42,5674,301],{"emptyLinePlaceholder":300},[42,5676,5677,5679,5682,5684],{"class":44,"line":263},[42,5678,604],{"class":48},[42,5680,5681],{"class":400}," translations",[42,5683,610],{"class":48},[42,5685,730],{"class":52},[42,5687,5688,5691,5694],{"class":44,"line":268},[42,5689,5690],{"class":52},"  hello: ",[42,5692,5693],{"class":613},"\"Bonjour\"",[42,5695,2208],{"class":52},[42,5697,5698],{"class":44,"line":273},[42,5699,5700],{"class":52},"  welcome: {\n",[42,5702,5703,5706,5709],{"class":44,"line":287},[42,5704,5705],{"class":52},"    message: ",[42,5707,5708],{"class":613},"\"Bienvenue!\"",[42,5710,2208],{"class":52},[42,5712,5713],{"class":44,"line":292},[42,5714,4169],{"class":52},[42,5716,5717],{"class":44,"line":297},[42,5718,980],{"class":52},[42,5720,5721],{"class":44,"line":304},[42,5722,301],{"emptyLinePlaceholder":300},[42,5724,5725,5727,5730,5733],{"class":44,"line":317},[42,5726,4132],{"class":52},[42,5728,5729],{"class":66},"use",[42,5731,5732],{"class":52},"(MyPlugin, { translations }); ",[42,5734,5735],{"class":56},"\u002F\u002F Use the plugin with options\n",[42,5737,5738,5740,5742,5744,5746],{"class":44,"line":326},[42,5739,4132],{"class":52},[42,5741,4184],{"class":66},[42,5743,70],{"class":52},[42,5745,4189],{"class":613},[42,5747,795],{"class":52},[15,5749,5750,5751,5754,5755,5758],{},"Any component can now call ",[39,5752,5753],{},"$translate('welcome.message')"," or inject ",[39,5756,5757],{},"globalStore",". This is the same mechanism Vue Router and Pinia use to integrate into an application.",[15,5760,5761],{},"These seven patterns cover most of the situations where the basics stop being enough. They feel more natural the more you reach for them.",[1592,5763,5764],{},"html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}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 .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}",{"title":37,"searchDepth":60,"depth":60,"links":5766},[5767,5769,5770,5771,5772,5773,5774],{"id":2390,"depth":60,"text":5768},"1. The Composition API past setup()",{"id":2937,"depth":60,"text":2938},{"id":3622,"depth":60,"text":3623},{"id":4053,"depth":60,"text":4054},{"id":4263,"depth":60,"text":4264},{"id":4610,"depth":60,"text":4611},{"id":5170,"depth":60,"text":5171},"2025-09-15","Composables, Teleport, Suspense, custom directives, render functions, provide\u002Finject and plugins, with the code for each.",{},"\u002Fblog\u002Fmastering-vue3-in-2025",{"title":2382,"description":5776},"blog\u002Fmastering-vue3-in-2025",[2722,5782,5783,36,5784],"vue3","frontend","webdev","mastering-vue3-in-2025","7BJSu3dwNLkMNi8DXgN_La5mYn2IFBcsZ1Cl1KB0zr4",{"id":5788,"title":5789,"body":5790,"category":1730,"date":5910,"description":5911,"draft":1610,"extension":1611,"meta":5912,"navigation":300,"path":5913,"seo":5914,"stem":5915,"tags":5916,"translationKey":5921,"videoId":1622,"__hash__":5922},"blog_en\u002Fblog\u002Fpassive-income-strategies-for-developers.md","Five passive income strategies for developers",{"type":7,"value":5791,"toc":5902},[5792,5795,5798,5805,5808,5812,5815,5822,5828,5834,5837,5841,5844,5847,5850,5854,5857,5860,5863,5867,5870,5873,5876,5879,5883,5886,5889,5892,5896,5899],[15,5793,5794],{},"Have you ever wanted your code to keep working while you're asleep or, you know, actually living? Passive income, money that arrives with little ongoing effort, is an appealing idea for people who build things for a living. If we build for others all day, we may as well build something that keeps paying us back.",[15,5796,5797],{},"Our skills line up well with this. No pyramid schemes and no becoming a property mogul: the material is what we already do, which is building digital products, automating things and sharing what we know.",[15,5799,5800,5801,5804],{},"You're right to be sceptical if this sounds like a myth, especially with a day job eating most of your week. It isn't ",[633,5802,5803],{},"zero"," effort. It's effort spent up front that pays out over time, and once something is set up the upkeep is usually far smaller than the build.",[15,5806,5807],{},"Here are five strategies developers are actually using, and how each one gets toward something like a thousand dollars a month.",[10,5809,5811],{"id":5810},"_1-digital-products-ebooks-courses-and-templates","1. Digital products: ebooks, courses and templates",[15,5813,5814],{},"Probably the most direct route. You have knowledge and practical skills that other people want, so package them.",[15,5816,5817,5821],{},[5818,5819,5820],"strong",{},"Ebooks and guides."," If you can explain difficult things simply, or you've mastered a niche technology others struggle with, write it down. A deep dive into a framework, a guide to performance work, a set of practices you've settled on. You write it once, format it and sell it on Gumroad, Leanpub or your own site. Someone who specialises in Vue might write a Nuxt performance handbook.",[15,5823,5824,5827],{},[5818,5825,5826],{},"Online courses."," If you'd rather teach visually, platforms like Udemy, Teachable and Skillshare make it reasonably easy to publish video lessons with exercises and quizzes. You record once and the platform handles sales and delivery.",[15,5829,5830,5833],{},[5818,5831,5832],{},"Templates and boilerplates."," Developers like starting from a solid base. A polished UI component library, a well-built backend boilerplate or a website template can be sold on marketplaces like Creative Market or ThemeForest. A SaaS starter kit with authentication, database setup and a basic dashboard is a common example.",[15,5835,5836],{},"The thing that decides whether this works: solve a specific problem or fill a real knowledge gap. Repackaged documentation doesn't sell. What sells is the part only someone with your experience can write.",[10,5838,5840],{"id":5839},"_2-a-tiny-saas","2. A tiny SaaS",[15,5842,5843],{},"This one is close to the day job. A SaaS product is a tool hosted online that people pay to use. Not the next Facebook, though: small, niche, one problem.",[15,5845,5846],{},"Find a recurring pain point for a specific group, whether developers, marketers or small businesses, build a simple web app that solves it and charge monthly or annually. A basic uptime monitor, a tool that turns text into social media images, a lightweight CRM for freelancers. Once it's built the software does the work, and what's left is maintenance, support and the occasional feature.",[15,5848,5849],{},"Start genuinely small. The most common mistake is trying to build something big and feature-rich. Pick one problem, solve it unusually well, launch early and add the rest later if people ask.",[10,5851,5853],{"id":5852},"_3-selling-an-api","3. Selling an API",[15,5855,5856],{},"APIs are how software talks to software. If you can build a service or dataset others want to plug into their applications, that's a possible income stream.",[15,5858,5859],{},"You build an API that provides one piece of data or functionality, such as weather data, image manipulation, text summarisation or currency conversion, and charge by usage. Something that converts markdown into well formatted PDFs, or that provides quotes for obscure cryptocurrencies. Once it's stable and documented, it runs in the background serving requests, and your time goes to marketing and support.",[15,5861,5862],{},"This one needs more infrastructure than the others, plus real attention to documentation and rate limiting. If you have expertise in aggregating data, or an algorithm nobody else has, it's worth considering.",[10,5864,5866],{"id":5865},"_4-open-source-with-a-commercial-layer","4. Open source with a commercial layer",[15,5868,5869],{},"Plenty of developers contribute to open source for free. There are ways to earn from it without closing the project.",[15,5871,5872],{},"If you maintain a popular library, you can keep the core open and sell a pro version with advanced features, dedicated support or enterprise scalability. An open-source UI library might sell a theme builder or priority support.",[15,5874,5875],{},"Sponsorship is the other route. GitHub Sponsors, Open Collective and Patreon exist because companies that depend on a tool are sometimes willing to fund it. The maintainer of a widely used build tool can receive monthly contributions from the businesses relying on it.",[15,5877,5878],{},"This works best if you already have a presence in the open-source community. It's less about starting something for income and more about building on what you've already given away.",[10,5880,5882],{"id":5881},"_5-affiliate-links-for-tools-you-actually-use","5. Affiliate links for tools you actually use",[15,5884,5885],{},"Here you aren't building a product but using your credibility to recommend things you genuinely use. When someone buys through your link, you get a commission.",[15,5887,5888],{},"In practice that means affiliate links in blog posts, videos or courses when you're discussing tools, hosting, books or courses. A post about your development setup for the year, with links to the editor, monitor, host and cloud services you use. Once the content exists and people are finding it, the links keep working.",[15,5890,5891],{},"Authenticity decides this one. Recommend what you actually use. An audience notices immediately when someone is pushing unrelated products for a commission.",[10,5893,5895],{"id":5894},"where-to-start","Where to start",[15,5897,5898],{},"A thousand a month doesn't arrive overnight. It takes consistency and a tolerance for things not working the first time. What developers have going for them is that the skills needed to build these streams are the skills we already use.",[15,5900,5901],{},"Pick the one that suits you, start small and stick with it. The first sale, the first subscriber, the first commission: it accumulates from there.",{"title":37,"searchDepth":60,"depth":60,"links":5903},[5904,5905,5906,5907,5908,5909],{"id":5810,"depth":60,"text":5811},{"id":5839,"depth":60,"text":5840},{"id":5852,"depth":60,"text":5853},{"id":5865,"depth":60,"text":5866},{"id":5881,"depth":60,"text":5882},{"id":5894,"depth":60,"text":5895},"2025-08-28","Realistic ways programmers build digital products, small SaaS tools or freelancing systems that keep earning after the work is done.",{},"\u002Fblog\u002Fpassive-income-strategies-for-developers",{"title":5789,"description":5911},"blog\u002Fpassive-income-strategies-for-developers",[5917,2020,5918,5919,5920],"passive income","side hustle","financial freedom","coding","passive-income-strategies-for-developers","FHJPAO0LFJqW17hxvsAMDvM6e_3j1MxBeEOzW3YVTts",{"id":5924,"title":5925,"body":5926,"category":2255,"date":7306,"description":7307,"draft":1610,"extension":1611,"meta":7308,"navigation":300,"path":7309,"seo":7310,"stem":7311,"tags":7312,"translationKey":7314,"videoId":1622,"__hash__":7315},"blog_en\u002Fblog\u002Fsecurity-risks-in-vuejs-applications.md","Security risks in Vue.js projects and how to close them",{"type":7,"value":5927,"toc":7298},[5928,5931,5934,5937,5941,5944,5948,5951,5954,5972,5975,6029,6036,6042,6150,6153,6162,6358,6362,6365,6368,6410,6413,6416,6419,6623,6626,6630,6637,6647,7038,7041,7045,7048,7055,7058,7069,7285,7289,7295],[15,5929,5930],{},"Deploying a new Vue application usually comes with a small amount of unease. Did I miss something that could expose user data or compromise the app?",[15,5932,5933],{},"We spend hours on interfaces, performance and features, and security often ends up last in the queue. The reasoning is usually \"it's just a frontend, what could go wrong\". Quite a lot, as it turns out. Vue itself is secure, but how we use it and what we pull in around it is where vulnerabilities come from.",[15,5935,5936],{},"Below are five common risks in Vue projects and what to do about each.",[10,5938,5940],{"id":5939},"why-frontend-security-matters","Why frontend security matters",[15,5942,5943],{},"You could argue security is mostly a backend problem, since the server handles authentication, database access and everything sensitive. Backend security is critical, true. But the frontend is the user's direct interface with your application, and when it's compromised attackers can steal credentials through phishing or keyloggers, manipulate data such as prices in a shop, trigger actions the user never intended if your API validates poorly, or simply deface the site.",[10,5945,5947],{"id":5946},"_1-cross-site-scripting","1. Cross-site scripting",[15,5949,5950],{},"XSS is when an attacker injects a script, usually JavaScript, that then runs in the victim's browser. From there it can take cookies and session tokens or rewrite the page.",[15,5952,5953],{},"It usually happens when an application renders user input straight into the DOM without sanitising it. Picture a comment section where someone submits this:",[32,5955,5957],{"className":2955,"code":5956,"language":2957,"meta":37,"style":37},"\u003Cp>This is a great article!\u003C\u002Fp>\n",[39,5958,5959],{"__ignoreMap":37},[42,5960,5961,5963,5965,5968,5970],{"class":44,"line":45},[42,5962,1353],{"class":52},[42,5964,15],{"class":2196},[42,5966,5967],{"class":52},">This is a great article!\u003C\u002F",[42,5969,15],{"class":2196},[42,5971,2734],{"class":52},[15,5973,5974],{},"Harmless. Now this:",[32,5976,5978],{"className":2955,"code":5977,"language":2957,"meta":37,"style":37},"\u003Cp>\n  This is a great article!\n  \u003Cscript>\n    alert(\"You've been XSSed!\");\n  \u003C\u002Fscript>\n\u003C\u002Fp>\n",[39,5979,5980,5988,5993,6001,6013,6021],{"__ignoreMap":37},[42,5981,5982,5984,5986],{"class":44,"line":45},[42,5983,1353],{"class":52},[42,5985,15],{"class":2196},[42,5987,2734],{"class":52},[42,5989,5990],{"class":44,"line":60},[42,5991,5992],{"class":52},"  This is a great article!\n",[42,5994,5995,5997,5999],{"class":44,"line":86},[42,5996,2739],{"class":52},[42,5998,2866],{"class":2196},[42,6000,2734],{"class":52},[42,6002,6003,6006,6008,6011],{"class":44,"line":92},[42,6004,6005],{"class":66},"    alert",[42,6007,70],{"class":52},[42,6009,6010],{"class":613},"\"You've been XSSed!\"",[42,6012,795],{"class":52},[42,6014,6015,6017,6019],{"class":44,"line":188},[42,6016,2842],{"class":52},[42,6018,2866],{"class":2196},[42,6020,2734],{"class":52},[42,6022,6023,6025,6027],{"class":44,"line":194},[42,6024,2851],{"class":52},[42,6026,15],{"class":2196},[42,6028,2734],{"class":52},[15,6030,6031,6032,6035],{},"If your component binds that unsanitised input with ",[39,6033,6034],{},"v-html",", the script runs for everyone who views the comment.",[15,6037,6038,6039,6041],{},"The rule to remember: never use ",[39,6040,6034],{}," with untrusted user content. With standard interpolation, Vue escapes HTML by default.",[32,6043,6045],{"className":2955,"code":6044,"language":2957,"meta":37,"style":37},"\u003C!-- Safe: Vue escapes the HTML -->\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cp>{{ userComment }}\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from 'vue';\nconst userComment = ref('\u003Cscript>alert(\"XSS attempt!\");\u003C\u002Fscript>');\n\u003C\u002Fscript>\n",[39,6046,6047,6052,6060,6068,6081,6089,6097,6101,6111,6124,6142],{"__ignoreMap":37},[42,6048,6049],{"class":44,"line":45},[42,6050,6051],{"class":56},"\u003C!-- Safe: Vue escapes the HTML -->\n",[42,6053,6054,6056,6058],{"class":44,"line":60},[42,6055,1353],{"class":52},[42,6057,2731],{"class":2196},[42,6059,2734],{"class":52},[42,6061,6062,6064,6066],{"class":44,"line":86},[42,6063,2739],{"class":52},[42,6065,2742],{"class":2196},[42,6067,2734],{"class":52},[42,6069,6070,6072,6074,6077,6079],{"class":44,"line":92},[42,6071,2749],{"class":52},[42,6073,15],{"class":2196},[42,6075,6076],{"class":52},">{{ userComment }}\u003C\u002F",[42,6078,15],{"class":2196},[42,6080,2734],{"class":52},[42,6082,6083,6085,6087],{"class":44,"line":188},[42,6084,2842],{"class":52},[42,6086,2742],{"class":2196},[42,6088,2734],{"class":52},[42,6090,6091,6093,6095],{"class":44,"line":194},[42,6092,2851],{"class":52},[42,6094,2731],{"class":2196},[42,6096,2734],{"class":52},[42,6098,6099],{"class":44,"line":249},[42,6100,301],{"emptyLinePlaceholder":300},[42,6102,6103,6105,6107,6109],{"class":44,"line":263},[42,6104,1353],{"class":52},[42,6106,2866],{"class":2196},[42,6108,2869],{"class":400},[42,6110,2734],{"class":52},[42,6112,6113,6115,6117,6119,6122],{"class":44,"line":268},[42,6114,2421],{"class":48},[42,6116,3210],{"class":52},[42,6118,2427],{"class":48},[42,6120,6121],{"class":613}," 'vue'",[42,6123,419],{"class":52},[42,6125,6126,6128,6131,6133,6135,6137,6140],{"class":44,"line":273},[42,6127,604],{"class":48},[42,6129,6130],{"class":400}," userComment",[42,6132,610],{"class":48},[42,6134,2464],{"class":66},[42,6136,70],{"class":52},[42,6138,6139],{"class":613},"'\u003Cscript>alert(\"XSS attempt!\");\u003C\u002Fscript>'",[42,6141,795],{"class":52},[42,6143,6144,6146,6148],{"class":44,"line":287},[42,6145,2851],{"class":52},[42,6147,2866],{"class":2196},[42,6149,2734],{"class":52},[15,6151,6152],{},"That renders the script tag as plain text rather than executable code.",[15,6154,6155,6156,6159,6160,786],{},"If you genuinely need to render dynamic HTML, from a rich text editor for example, sanitise the input on the backend first with a library such as ",[39,6157,6158],{},"DOMPurify"," before storing it, and sanitise again on the frontend as a second layer before passing anything to ",[39,6161,6034],{},[32,6163,6165],{"className":2955,"code":6164,"language":2957,"meta":37,"style":37},"\u003C!-- Use v-html ONLY with trusted, sanitized content -->\n\u003Ctemplate>\n  \u003Cdiv v-html=\"sanitizedUserContent\">\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref, computed } from 'vue';\n\u002F\u002F In a real app, you'd get this from a backend API after sanitization\nconst rawUserContent = ref('\u003Cp>Hello \u003Cb>World\u003C\u002Fb>! \u003Cscript>alert(\"No!\");\u003C\u002Fscript>\u003C\u002Fp>');\n\n\u002F\u002F For demonstration, let's pretend this is sanitized\n\u002F\u002F In a real app, you'd use a proper library like DOMPurify\nconst sanitizedUserContent = computed(() => {\n  \u002F\u002F This is a VERY simplistic and insecure \"sanitization\" for example purposes.\n  \u002F\u002F DO NOT use this in production. Use a library like DOMPurify!\n  return rawUserContent.value.replace(\u002F\u003Cscript.*?>.*?\u003C\\\u002Fscript>\u002Fg, '');\n});\n\u003C\u002Fscript>\n",[39,6166,6167,6172,6180,6200,6208,6212,6222,6235,6240,6258,6262,6267,6272,6291,6296,6301,6346,6350],{"__ignoreMap":37},[42,6168,6169],{"class":44,"line":45},[42,6170,6171],{"class":56},"\u003C!-- Use v-html ONLY with trusted, sanitized content -->\n",[42,6173,6174,6176,6178],{"class":44,"line":60},[42,6175,1353],{"class":52},[42,6177,2731],{"class":2196},[42,6179,2734],{"class":52},[42,6181,6182,6184,6186,6189,6191,6194,6196,6198],{"class":44,"line":86},[42,6183,2739],{"class":52},[42,6185,2742],{"class":2196},[42,6187,6188],{"class":400}," v-html",[42,6190,1326],{"class":52},[42,6192,6193],{"class":613},"\"sanitizedUserContent\"",[42,6195,2990],{"class":52},[42,6197,2742],{"class":2196},[42,6199,2734],{"class":52},[42,6201,6202,6204,6206],{"class":44,"line":92},[42,6203,2851],{"class":52},[42,6205,2731],{"class":2196},[42,6207,2734],{"class":52},[42,6209,6210],{"class":44,"line":188},[42,6211,301],{"emptyLinePlaceholder":300},[42,6213,6214,6216,6218,6220],{"class":44,"line":194},[42,6215,1353],{"class":52},[42,6217,2866],{"class":2196},[42,6219,2869],{"class":400},[42,6221,2734],{"class":52},[42,6223,6224,6226,6229,6231,6233],{"class":44,"line":249},[42,6225,2421],{"class":48},[42,6227,6228],{"class":52}," { ref, computed } ",[42,6230,2427],{"class":48},[42,6232,6121],{"class":613},[42,6234,419],{"class":52},[42,6236,6237],{"class":44,"line":263},[42,6238,6239],{"class":56},"\u002F\u002F In a real app, you'd get this from a backend API after sanitization\n",[42,6241,6242,6244,6247,6249,6251,6253,6256],{"class":44,"line":268},[42,6243,604],{"class":48},[42,6245,6246],{"class":400}," rawUserContent",[42,6248,610],{"class":48},[42,6250,2464],{"class":66},[42,6252,70],{"class":52},[42,6254,6255],{"class":613},"'\u003Cp>Hello \u003Cb>World\u003C\u002Fb>! \u003Cscript>alert(\"No!\");\u003C\u002Fscript>\u003C\u002Fp>'",[42,6257,795],{"class":52},[42,6259,6260],{"class":44,"line":273},[42,6261,301],{"emptyLinePlaceholder":300},[42,6263,6264],{"class":44,"line":287},[42,6265,6266],{"class":56},"\u002F\u002F For demonstration, let's pretend this is sanitized\n",[42,6268,6269],{"class":44,"line":292},[42,6270,6271],{"class":56},"\u002F\u002F In a real app, you'd use a proper library like DOMPurify\n",[42,6273,6274,6276,6279,6281,6284,6287,6289],{"class":44,"line":297},[42,6275,604],{"class":48},[42,6277,6278],{"class":400}," sanitizedUserContent",[42,6280,610],{"class":48},[42,6282,6283],{"class":66}," computed",[42,6285,6286],{"class":52},"(() ",[42,6288,969],{"class":48},[42,6290,730],{"class":52},[42,6292,6293],{"class":44,"line":304},[42,6294,6295],{"class":56},"  \u002F\u002F This is a VERY simplistic and insecure \"sanitization\" for example purposes.\n",[42,6297,6298],{"class":44,"line":317},[42,6299,6300],{"class":56},"  \u002F\u002F DO NOT use this in production. Use a library like DOMPurify!\n",[42,6302,6303,6305,6308,6311,6313,6316,6318,6321,6323,6325,6327,6329,6333,6336,6339,6341,6344],{"class":44,"line":326},[42,6304,424],{"class":48},[42,6306,6307],{"class":52}," rawUserContent.value.",[42,6309,6310],{"class":66},"replace",[42,6312,70],{"class":52},[42,6314,6315],{"class":613},"\u002F\u003Cscript",[42,6317,786],{"class":400},[42,6319,6320],{"class":48},"*?",[42,6322,397],{"class":613},[42,6324,786],{"class":400},[42,6326,6320],{"class":48},[42,6328,1353],{"class":613},[42,6330,6332],{"class":6331},"sHaLz","\\\u002F",[42,6334,6335],{"class":613},"script>\u002F",[42,6337,6338],{"class":48},"g",[42,6340,77],{"class":52},[42,6342,6343],{"class":613},"''",[42,6345,795],{"class":52},[42,6347,6348],{"class":44,"line":334},[42,6349,1289],{"class":52},[42,6351,6352,6354,6356],{"class":44,"line":342},[42,6353,2851],{"class":52},[42,6355,2866],{"class":2196},[42,6357,2734],{"class":52},[10,6359,6361],{"id":6360},"_2-cross-site-request-forgery","2. Cross-site request forgery",[15,6363,6364],{},"CSRF tricks a logged-in user into performing an action they didn't intend. The attacker gets the user's browser to send a request to your server, relying on the fact that the user is already authenticated.",[15,6366,6367],{},"Say you're logged into your bank in one tab and open a malicious site in another. That site contains a hidden form or an image tag pointing at the bank's transfer endpoint:",[32,6369,6371],{"className":2955,"code":6370,"language":2957,"meta":37,"style":37},"\u003C!-- Malicious website's hidden content -->\n\u003Cimg\n  src=\"https:\u002F\u002Fyourbank.com\u002Ftransfer?to=attacker&amount=1000000\"\n  style=\"display:none;\"\n\u002F>\n",[39,6372,6373,6378,6385,6395,6405],{"__ignoreMap":37},[42,6374,6375],{"class":44,"line":45},[42,6376,6377],{"class":56},"\u003C!-- Malicious website's hidden content -->\n",[42,6379,6380,6382],{"class":44,"line":60},[42,6381,1353],{"class":52},[42,6383,6384],{"class":2196},"img\n",[42,6386,6387,6390,6392],{"class":44,"line":86},[42,6388,6389],{"class":400},"  src",[42,6391,1326],{"class":52},[42,6393,6394],{"class":613},"\"https:\u002F\u002Fyourbank.com\u002Ftransfer?to=attacker&amount=1000000\"\n",[42,6396,6397,6400,6402],{"class":44,"line":92},[42,6398,6399],{"class":400},"  style",[42,6401,1326],{"class":52},[42,6403,6404],{"class":613},"\"display:none;\"\n",[42,6406,6407],{"class":44,"line":188},[42,6408,6409],{"class":52},"\u002F>\n",[15,6411,6412],{},"When the browser loads that image it sends a GET request along with your session cookies. If the API has no CSRF protection, it may treat that as a legitimate request.",[15,6414,6415],{},"The usual defence is a CSRF token. The server generates an unpredictable token and sends it to the client, in a cookie, a meta tag or the initial payload. Every state-changing request from your Vue app, meaning POST, PUT and DELETE, includes that token in the headers or body. The backend rejects anything where the token doesn't match.",[15,6417,6418],{},"In practice you configure your HTTP client to attach it automatically.",[32,6420,6422],{"className":34,"code":6421,"language":36,"meta":37,"style":37},"\u002F\u002F Example using Axios (adjust based on how your backend provides the token)\nimport axios from \"axios\";\n\n\u002F\u002F 1. Get the CSRF token (e.g., from a meta tag in your HTML, or a cookie)\nfunction getCsrfToken() {\n  \u002F\u002F This is an example, your backend might provide it differently\n  const tokenElement = document.querySelector('meta[name=\"csrf-token\"]');\n  return tokenElement ? tokenElement.getAttribute(\"content\") : \"\";\n}\n\nconst csrfToken = getCsrfToken();\n\n\u002F\u002F 2. Configure Axios to include the token in requests\nif (csrfToken) {\n  axios.defaults.headers.common[\"X-CSRF-TOKEN\"] = csrfToken;\n}\n\n\u002F\u002F Now, when you make a POST, PUT, or DELETE request, the token will be included\naxios.post(\"\u002Fapi\u002Ftransfer-money\", {\n  recipient: \"myfriend\",\n  amount: 50,\n});\n",[39,6423,6424,6429,6443,6447,6452,6461,6466,6488,6517,6521,6525,6538,6542,6547,6555,6571,6575,6579,6584,6599,6609,6619],{"__ignoreMap":37},[42,6425,6426],{"class":44,"line":45},[42,6427,6428],{"class":56},"\u002F\u002F Example using Axios (adjust based on how your backend provides the token)\n",[42,6430,6431,6433,6436,6438,6441],{"class":44,"line":60},[42,6432,2421],{"class":48},[42,6434,6435],{"class":52}," axios ",[42,6437,2427],{"class":48},[42,6439,6440],{"class":613}," \"axios\"",[42,6442,419],{"class":52},[42,6444,6445],{"class":44,"line":86},[42,6446,301],{"emptyLinePlaceholder":300},[42,6448,6449],{"class":44,"line":92},[42,6450,6451],{"class":56},"\u002F\u002F 1. Get the CSRF token (e.g., from a meta tag in your HTML, or a cookie)\n",[42,6453,6454,6456,6459],{"class":44,"line":188},[42,6455,63],{"class":48},[42,6457,6458],{"class":66}," getCsrfToken",[42,6460,943],{"class":52},[42,6462,6463],{"class":44,"line":194},[42,6464,6465],{"class":56},"  \u002F\u002F This is an example, your backend might provide it differently\n",[42,6467,6468,6470,6473,6475,6478,6481,6483,6486],{"class":44,"line":249},[42,6469,670],{"class":48},[42,6471,6472],{"class":400}," tokenElement",[42,6474,610],{"class":48},[42,6476,6477],{"class":52}," document.",[42,6479,6480],{"class":66},"querySelector",[42,6482,70],{"class":52},[42,6484,6485],{"class":613},"'meta[name=\"csrf-token\"]'",[42,6487,795],{"class":52},[42,6489,6490,6492,6495,6497,6500,6503,6505,6508,6510,6512,6515],{"class":44,"line":263},[42,6491,424],{"class":48},[42,6493,6494],{"class":52}," tokenElement ",[42,6496,5516],{"class":48},[42,6498,6499],{"class":52}," tokenElement.",[42,6501,6502],{"class":66},"getAttribute",[42,6504,70],{"class":52},[42,6506,6507],{"class":613},"\"content\"",[42,6509,404],{"class":52},[42,6511,3835],{"class":48},[42,6513,6514],{"class":613}," \"\"",[42,6516,419],{"class":52},[42,6518,6519],{"class":44,"line":268},[42,6520,141],{"class":52},[42,6522,6523],{"class":44,"line":273},[42,6524,301],{"emptyLinePlaceholder":300},[42,6526,6527,6529,6532,6534,6536],{"class":44,"line":287},[42,6528,604],{"class":48},[42,6530,6531],{"class":400}," csrfToken",[42,6533,610],{"class":48},[42,6535,6458],{"class":66},[42,6537,819],{"class":52},[42,6539,6540],{"class":44,"line":292},[42,6541,301],{"emptyLinePlaceholder":300},[42,6543,6544],{"class":44,"line":297},[42,6545,6546],{"class":56},"\u002F\u002F 2. Configure Axios to include the token in requests\n",[42,6548,6549,6552],{"class":44,"line":304},[42,6550,6551],{"class":48},"if",[42,6553,6554],{"class":52}," (csrfToken) {\n",[42,6556,6557,6560,6563,6566,6568],{"class":44,"line":317},[42,6558,6559],{"class":52},"  axios.defaults.headers.common[",[42,6561,6562],{"class":613},"\"X-CSRF-TOKEN\"",[42,6564,6565],{"class":52},"] ",[42,6567,1326],{"class":48},[42,6569,6570],{"class":52}," csrfToken;\n",[42,6572,6573],{"class":44,"line":326},[42,6574,141],{"class":52},[42,6576,6577],{"class":44,"line":334},[42,6578,301],{"emptyLinePlaceholder":300},[42,6580,6581],{"class":44,"line":342},[42,6582,6583],{"class":56},"\u002F\u002F Now, when you make a POST, PUT, or DELETE request, the token will be included\n",[42,6585,6586,6589,6592,6594,6597],{"class":44,"line":350},[42,6587,6588],{"class":52},"axios.",[42,6590,6591],{"class":66},"post",[42,6593,70],{"class":52},[42,6595,6596],{"class":613},"\"\u002Fapi\u002Ftransfer-money\"",[42,6598,4143],{"class":52},[42,6600,6601,6604,6607],{"class":44,"line":2652},[42,6602,6603],{"class":52},"  recipient: ",[42,6605,6606],{"class":613},"\"myfriend\"",[42,6608,2208],{"class":52},[42,6610,6611,6614,6617],{"class":44,"line":2662},[42,6612,6613],{"class":52},"  amount: ",[42,6615,6616],{"class":400},"50",[42,6618,2208],{"class":52},[42,6620,6621],{"class":44,"line":2675},[42,6622,1289],{"class":52},[15,6624,6625],{},"GET requests generally don't need tokens because they're supposed to be idempotent. If your API changes state on a GET, that's a separate problem worth fixing.",[10,6627,6629],{"id":6628},"_3-vulnerable-dependencies","3. Vulnerable dependencies",[15,6631,6632,6633,6636],{},"Your ",[39,6634,6635],{},"node_modules"," folder is a large amount of other people's code. Most of it is fine, and one vulnerable package is enough to open a door. Either a package you depend on, or something in its own dependency tree, has a known flaw, or a maintainer's account is compromised and malicious code arrives in an update.",[15,6638,6639,6640,4256,6643,6646],{},"Audit regularly. Both ",[39,6641,6642],{},"npm audit",[39,6644,6645],{},"yarn audit"," check installed packages against known vulnerabilities and usually suggest fixes.",[32,6648,6652],{"className":6649,"code":6650,"language":6651,"meta":37,"style":37},"language-bash shiki shiki-themes github-dark-default","    # npm audit report\n\n    lodash  \u003C4.17.21\n    Severity: high\n    Prototype Pollution - https:\u002F\u002Fnpmjs.com\u002Fadvisories\u002F1526\n    No fix available\n\n    axios  \u003C0.21.1\n    Severity: moderate\n    Arbitrary File Write via Decompress - https:\u002F\u002Fnpmjs.com\u002Fadvisories\u002F1608\n    Fix available: `npm install axios@^0.21.1`\n    ```\n\nBefore adding a dependency, look at whether it's actively maintained, how many people use it, whether there are open security issues and when it was last updated.\n\nCommit your `package-lock.json` or `yarn.lock`. Those files pin exact versions so your team and your CI pipeline all build against the same tested set.\n\n## 4. Leaking secrets into the bundle\n\nThis one sounds obvious, and it still happens regularly. API keys hardcoded in components or JavaScript files end up in the client bundle, which makes them public. Detailed error messages leak information about your backend. Environment variables that were never meant for the client get bundled in.\n\nNever hardcode secrets in frontend code. If the frontend needs an API that requires a key, proxy it: your Vue app calls your backend, and your backend calls the third-party API with the key. So instead of `axios.get('https:\u002F\u002Fapi.thirdparty.com\u002Fdata?apiKey=YOUR_SECRET_KEY')` from the browser, the request goes through a server you control.\n\nEnvironment variables need the same care. Vue CLI exposes anything prefixed with `VUE_APP_` to the client bundle, and Vite does the same with `VITE_`. Put API endpoints there, not API keys. Keep production secrets out of version control and manage them through your hosting provider's configuration.\n\nHandle errors so they don't leak either. Raw backend errors can reveal database schema or server paths, so show the user something generic and log the detail on the server.\n\n```html\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cbutton @click=\"fetchData\">Fetch Data\u003C\u002Fbutton>\n    \u003Cp v-if=\"error\">{{ errorMessage }}\u003C\u002Fp>\n    \u003Cdiv v-if=\"data\">{{ data }}\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\n  import { ref } from \"vue\";\n  import axios from \"axios\";\n\n  const data = ref(null);\n  const error = ref(false);\n  const errorMessage = ref(\"\");\n\n  const fetchData = async () => {\n    try {\n      const response = await axios.get(\"\u002Fapi\u002Fsensitive-data\");\n      data.value = response.data;\n      error.value = false;\n    } catch (err) {\n      error.value = true;\n      \u002F\u002F Don't show the raw error to the user!\n      \u002F\u002F console.error(err.response.data); \u002F\u002F Bad practice\n      errorMessage.value =\n        \"Oops! Something went wrong. Please try again later.\";\n      \u002F\u002F Log the detailed error to a server-side logging service\n    }\n  };\n\u003C\u002Fscript>\n","bash",[39,6653,6654,6659,6663,6673,6681,6695,6706,6710,6720,6727,6749,6766,6771,6775,6783,6787,6792,6796,6801,6805,6810,6814,6819,6823,6828,6832,6840,6844,6849,6854,6859,6864,6869,6874,6879,6884,6888,6893,6898,6903,6907,6912,6917,6922,6926,6931,6936,6942,6948,6954,6960,6966,6972,6992,7001,7009,7017,7022,7028],{"__ignoreMap":37},[42,6655,6656],{"class":44,"line":45},[42,6657,6658],{"class":56},"    # npm audit report\n",[42,6660,6661],{"class":44,"line":60},[42,6662,301],{"emptyLinePlaceholder":300},[42,6664,6665,6668,6670],{"class":44,"line":86},[42,6666,6667],{"class":73},"    lodash",[42,6669,2739],{"class":48},[42,6671,6672],{"class":613},"4.17.21\n",[42,6674,6675,6678],{"class":44,"line":92},[42,6676,6677],{"class":73},"    Severity:",[42,6679,6680],{"class":613}," high\n",[42,6682,6683,6686,6689,6692],{"class":44,"line":188},[42,6684,6685],{"class":73},"    Prototype",[42,6687,6688],{"class":613}," Pollution",[42,6690,6691],{"class":613}," -",[42,6693,6694],{"class":613}," https:\u002F\u002Fnpmjs.com\u002Fadvisories\u002F1526\n",[42,6696,6697,6700,6703],{"class":44,"line":194},[42,6698,6699],{"class":73},"    No",[42,6701,6702],{"class":613}," fix",[42,6704,6705],{"class":613}," available\n",[42,6707,6708],{"class":44,"line":249},[42,6709,301],{"emptyLinePlaceholder":300},[42,6711,6712,6715,6717],{"class":44,"line":263},[42,6713,6714],{"class":73},"    axios",[42,6716,2739],{"class":48},[42,6718,6719],{"class":613},"0.21.1\n",[42,6721,6722,6724],{"class":44,"line":268},[42,6723,6677],{"class":73},[42,6725,6726],{"class":613}," moderate\n",[42,6728,6729,6732,6735,6738,6741,6744,6746],{"class":44,"line":273},[42,6730,6731],{"class":73},"    Arbitrary",[42,6733,6734],{"class":613}," File",[42,6736,6737],{"class":613}," Write",[42,6739,6740],{"class":613}," via",[42,6742,6743],{"class":613}," Decompress",[42,6745,6691],{"class":613},[42,6747,6748],{"class":613}," https:\u002F\u002Fnpmjs.com\u002Fadvisories\u002F1608\n",[42,6750,6751,6754,6757,6760,6763],{"class":44,"line":287},[42,6752,6753],{"class":73},"    Fix",[42,6755,6756],{"class":613}," available:",[42,6758,6759],{"class":613}," `",[42,6761,6762],{"class":73},"npm",[42,6764,6765],{"class":613}," install axios@^0.21.1`\n",[42,6767,6768],{"class":44,"line":292},[42,6769,6770],{"class":613},"    ```\n",[42,6772,6773],{"class":44,"line":297},[42,6774,301],{"emptyLinePlaceholder":300},[42,6776,6777,6780],{"class":44,"line":304},[42,6778,6779],{"class":73},"Before",[42,6781,6782],{"class":613}," adding a dependency, look at whether it's actively maintained, how many people use it, whether there are open security issues and when it was last updated.\n",[42,6784,6785],{"class":44,"line":317},[42,6786,301],{"emptyLinePlaceholder":300},[42,6788,6789],{"class":44,"line":326},[42,6790,6791],{"class":613},"Commit your `package-lock.json` or `yarn.lock`. Those files pin exact versions so your team and your CI pipeline all build against the same tested set.\n",[42,6793,6794],{"class":44,"line":334},[42,6795,301],{"emptyLinePlaceholder":300},[42,6797,6798],{"class":44,"line":342},[42,6799,6800],{"class":613},"## 4. Leaking secrets into the bundle\n",[42,6802,6803],{"class":44,"line":350},[42,6804,301],{"emptyLinePlaceholder":300},[42,6806,6807],{"class":44,"line":2652},[42,6808,6809],{"class":613},"This one sounds obvious, and it still happens regularly. API keys hardcoded in components or JavaScript files end up in the client bundle, which makes them public. Detailed error messages leak information about your backend. Environment variables that were never meant for the client get bundled in.\n",[42,6811,6812],{"class":44,"line":2662},[42,6813,301],{"emptyLinePlaceholder":300},[42,6815,6816],{"class":44,"line":2675},[42,6817,6818],{"class":613},"Never hardcode secrets in frontend code. If the frontend needs an API that requires a key, proxy it: your Vue app calls your backend, and your backend calls the third-party API with the key. So instead of `axios.get('https:\u002F\u002Fapi.thirdparty.com\u002Fdata?apiKey=YOUR_SECRET_KEY')` from the browser, the request goes through a server you control.\n",[42,6820,6821],{"class":44,"line":2680},[42,6822,301],{"emptyLinePlaceholder":300},[42,6824,6825],{"class":44,"line":2685},[42,6826,6827],{"class":613},"Environment variables need the same care. Vue CLI exposes anything prefixed with `VUE_APP_` to the client bundle, and Vite does the same with `VITE_`. Put API endpoints there, not API keys. Keep production secrets out of version control and manage them through your hosting provider's configuration.\n",[42,6829,6830],{"class":44,"line":2690},[42,6831,301],{"emptyLinePlaceholder":300},[42,6833,6834,6837],{"class":44,"line":2699},[42,6835,6836],{"class":73},"Handle",[42,6838,6839],{"class":613}," errors so they don't leak either. Raw backend errors can reveal database schema or server paths, so show the user something generic and log the detail on the server.\n",[42,6841,6842],{"class":44,"line":2704},[42,6843,301],{"emptyLinePlaceholder":300},[42,6845,6846],{"class":44,"line":2712},[42,6847,6848],{"class":613},"```html\n",[42,6850,6851],{"class":44,"line":3347},[42,6852,6853],{"class":613},"\u003Ctemplate>\n",[42,6855,6856],{"class":44,"line":3359},[42,6857,6858],{"class":613},"  \u003Cdiv>\n",[42,6860,6861],{"class":44,"line":3371},[42,6862,6863],{"class":613},"    \u003Cbutton @click=\"fetchData\">Fetch Data\u003C\u002Fbutton>\n",[42,6865,6866],{"class":44,"line":3387},[42,6867,6868],{"class":613},"    \u003Cp v-if=\"error\">{{ errorMessage }}\u003C\u002Fp>\n",[42,6870,6871],{"class":44,"line":3402},[42,6872,6873],{"class":613},"    \u003Cdiv v-if=\"data\">{{ data }}\u003C\u002Fdiv>\n",[42,6875,6876],{"class":44,"line":3432},[42,6877,6878],{"class":613},"  \u003C\u002Fdiv>\n",[42,6880,6881],{"class":44,"line":3445},[42,6882,6883],{"class":613},"\u003C\u002Ftemplate>\n",[42,6885,6886],{"class":44,"line":3458},[42,6887,301],{"emptyLinePlaceholder":300},[42,6889,6890],{"class":44,"line":3470},[42,6891,6892],{"class":613},"\u003Cscript setup>\n",[42,6894,6895],{"class":44,"line":3483},[42,6896,6897],{"class":613},"  import { ref } from \"vue\";\n",[42,6899,6900],{"class":44,"line":3488},[42,6901,6902],{"class":613},"  import axios from \"axios\";\n",[42,6904,6905],{"class":44,"line":3496},[42,6906,301],{"emptyLinePlaceholder":300},[42,6908,6909],{"class":44,"line":3509},[42,6910,6911],{"class":613},"  const data = ref(null);\n",[42,6913,6914],{"class":44,"line":3525},[42,6915,6916],{"class":613},"  const error = ref(false);\n",[42,6918,6919],{"class":44,"line":3540},[42,6920,6921],{"class":613},"  const errorMessage = ref(\"\");\n",[42,6923,6924],{"class":44,"line":3555},[42,6925,301],{"emptyLinePlaceholder":300},[42,6927,6928],{"class":44,"line":3597},[42,6929,6930],{"class":613},"  const fetchData = async () => {\n",[42,6932,6933],{"class":44,"line":3602},[42,6934,6935],{"class":613},"    try {\n",[42,6937,6939],{"class":44,"line":6938},47,[42,6940,6941],{"class":613},"      const response = await axios.get(\"\u002Fapi\u002Fsensitive-data\");\n",[42,6943,6945],{"class":44,"line":6944},48,[42,6946,6947],{"class":613},"      data.value = response.data;\n",[42,6949,6951],{"class":44,"line":6950},49,[42,6952,6953],{"class":613},"      error.value = false;\n",[42,6955,6957],{"class":44,"line":6956},50,[42,6958,6959],{"class":613},"    } catch (err) {\n",[42,6961,6963],{"class":44,"line":6962},51,[42,6964,6965],{"class":613},"      error.value = true;\n",[42,6967,6969],{"class":44,"line":6968},52,[42,6970,6971],{"class":613},"      \u002F\u002F Don't show the raw error to the user!\n",[42,6973,6975,6978,6981,6984,6986,6989],{"class":44,"line":6974},53,[42,6976,6977],{"class":73},"      \u002F\u002F",[42,6979,6980],{"class":613}," console.error(",[42,6982,6983],{"class":73},"err.response.data",[42,6985,5007],{"class":613},[42,6987,6988],{"class":73},"\u002F\u002F",[42,6990,6991],{"class":613}," Bad practice\n",[42,6993,6995,6998],{"class":44,"line":6994},54,[42,6996,6997],{"class":73},"      errorMessage.value",[42,6999,7000],{"class":613}," =\n",[42,7002,7004,7007],{"class":44,"line":7003},55,[42,7005,7006],{"class":73},"        \"Oops! Something went wrong. Please try again later.\"",[42,7008,419],{"class":613},[42,7010,7012,7014],{"class":44,"line":7011},56,[42,7013,6977],{"class":73},[42,7015,7016],{"class":613}," Log the detailed error to a server-side logging service\n",[42,7018,7020],{"class":44,"line":7019},57,[42,7021,800],{"class":613},[42,7023,7025],{"class":44,"line":7024},58,[42,7026,7027],{"class":613},"  };\n",[42,7029,7031,7033,7036],{"class":44,"line":7030},59,[42,7032,1353],{"class":48},[42,7034,7035],{"class":73},"\u002Fscript",[42,7037,2734],{"class":613},[15,7039,7040],{},"Assume everything in your compiled frontend is public, because it is.",[10,7042,7044],{"id":7043},"_5-authentication-and-authorisation-on-the-client","5. Authentication and authorisation on the client",[15,7046,7047],{},"This isn't specific to Vue, but it comes up constantly. Enforcing auth purely on the frontend is a bouncer at the front door with the back door open.",[15,7049,7050,7051,7054],{},"It shows up as hiding UI based on a role, assuming the backend will check permissions anyway, or storing an ",[39,7052,7053],{},"isAdmin"," flag in Pinia or Vuex and treating it as authoritative.",[15,7056,7057],{},"Every endpoint that requires authentication should verify the token or session, and every endpoint that requires a permission should check that permission before doing anything. The frontend is there for user experience: hiding a button someone can't use is reasonable, but it isn't security, and anyone can bypass your JavaScript.",[15,7059,7060,7061,7064,7065,7068],{},"Store tokens carefully. ",[39,7062,7063],{},"HttpOnly"," cookies set by the backend keep tokens out of reach of client-side JavaScript, which limits the damage of an XSS bug. If you use ",[39,7066,7067],{},"localStorage",", understand that you're relying entirely on having no XSS anywhere.",[32,7070,7072],{"className":34,"code":7071,"language":36,"meta":37,"style":37},"\u002F\u002F BAD: Relying on client-side state for authorization\n\u002F\u002F In a real app, an attacker could manipulate `isAdmin` in their browser.\nconst user = {\n  name: \"Alice\",\n  isAdmin: false, \u002F\u002F Imagine an attacker changing this to true in the console\n};\n\nif (user.isAdmin) {\n  \u002F\u002F Show admin button - this is for UX, not security!\n}\n\n\u002F\u002F GOOD: Backend always validates permissions\nasync function deleteUser(userId) {\n  try {\n    \u002F\u002F Frontend sends the request\n    await axios.delete(`\u002Fapi\u002Fusers\u002F${userId}`);\n    alert(\"User deleted!\");\n  } catch (error) {\n    \u002F\u002F Backend rejects if user is not authorized\n    if (error.response && error.response.status === 403) {\n      alert(\"You are not authorized to perform this action.\");\n    } else {\n      alert(\"An error occurred.\");\n    }\n  }\n}\n",[39,7073,7074,7079,7084,7095,7105,7117,7121,7125,7132,7137,7141,7145,7150,7165,7171,7176,7198,7209,7217,7222,7242,7254,7262,7273,7277,7281],{"__ignoreMap":37},[42,7075,7076],{"class":44,"line":45},[42,7077,7078],{"class":56},"\u002F\u002F BAD: Relying on client-side state for authorization\n",[42,7080,7081],{"class":44,"line":60},[42,7082,7083],{"class":56},"\u002F\u002F In a real app, an attacker could manipulate `isAdmin` in their browser.\n",[42,7085,7086,7088,7091,7093],{"class":44,"line":86},[42,7087,604],{"class":48},[42,7089,7090],{"class":400}," user",[42,7092,610],{"class":48},[42,7094,730],{"class":52},[42,7096,7097,7100,7103],{"class":44,"line":92},[42,7098,7099],{"class":52},"  name: ",[42,7101,7102],{"class":613},"\"Alice\"",[42,7104,2208],{"class":52},[42,7106,7107,7110,7112,7114],{"class":44,"line":188},[42,7108,7109],{"class":52},"  isAdmin: ",[42,7111,3236],{"class":400},[42,7113,77],{"class":52},[42,7115,7116],{"class":56},"\u002F\u002F Imagine an attacker changing this to true in the console\n",[42,7118,7119],{"class":44,"line":194},[42,7120,980],{"class":52},[42,7122,7123],{"class":44,"line":249},[42,7124,301],{"emptyLinePlaceholder":300},[42,7126,7127,7129],{"class":44,"line":263},[42,7128,6551],{"class":48},[42,7130,7131],{"class":52}," (user.isAdmin) {\n",[42,7133,7134],{"class":44,"line":268},[42,7135,7136],{"class":56},"  \u002F\u002F Show admin button - this is for UX, not security!\n",[42,7138,7139],{"class":44,"line":273},[42,7140,141],{"class":52},[42,7142,7143],{"class":44,"line":287},[42,7144,301],{"emptyLinePlaceholder":300},[42,7146,7147],{"class":44,"line":292},[42,7148,7149],{"class":56},"\u002F\u002F GOOD: Backend always validates permissions\n",[42,7151,7152,7154,7156,7159,7161,7163],{"class":44,"line":297},[42,7153,711],{"class":48},[42,7155,714],{"class":48},[42,7157,7158],{"class":66}," deleteUser",[42,7160,70],{"class":52},[42,7162,663],{"class":73},[42,7164,83],{"class":52},[42,7166,7167,7169],{"class":44,"line":304},[42,7168,727],{"class":48},[42,7170,730],{"class":52},[42,7172,7173],{"class":44,"line":317},[42,7174,7175],{"class":56},"    \u002F\u002F Frontend sends the request\n",[42,7177,7178,7181,7184,7187,7189,7192,7194,7196],{"class":44,"line":326},[42,7179,7180],{"class":48},"    await",[42,7182,7183],{"class":52}," axios.",[42,7185,7186],{"class":66},"delete",[42,7188,70],{"class":52},[42,7190,7191],{"class":613},"`\u002Fapi\u002Fusers\u002F${",[42,7193,663],{"class":52},[42,7195,792],{"class":613},[42,7197,795],{"class":52},[42,7199,7200,7202,7204,7207],{"class":44,"line":334},[42,7201,6005],{"class":66},[42,7203,70],{"class":52},[42,7205,7206],{"class":613},"\"User deleted!\"",[42,7208,795],{"class":52},[42,7210,7211,7213,7215],{"class":44,"line":342},[42,7212,831],{"class":52},[42,7214,834],{"class":48},[42,7216,837],{"class":52},[42,7218,7219],{"class":44,"line":350},[42,7220,7221],{"class":56},"    \u002F\u002F Backend rejects if user is not authorized\n",[42,7223,7224,7226,7229,7232,7235,7237,7240],{"class":44,"line":2652},[42,7225,755],{"class":48},[42,7227,7228],{"class":52}," (error.response ",[42,7230,7231],{"class":48},"&&",[42,7233,7234],{"class":52}," error.response.status ",[42,7236,1389],{"class":48},[42,7238,7239],{"class":400}," 403",[42,7241,83],{"class":52},[42,7243,7244,7247,7249,7252],{"class":44,"line":2662},[42,7245,7246],{"class":66},"      alert",[42,7248,70],{"class":52},[42,7250,7251],{"class":613},"\"You are not authorized to perform this action.\"",[42,7253,795],{"class":52},[42,7255,7256,7258,7260],{"class":44,"line":2675},[42,7257,1421],{"class":52},[42,7259,1424],{"class":48},[42,7261,730],{"class":52},[42,7263,7264,7266,7268,7271],{"class":44,"line":2680},[42,7265,7246],{"class":66},[42,7267,70],{"class":52},[42,7269,7270],{"class":613},"\"An error occurred.\"",[42,7272,795],{"class":52},[42,7274,7275],{"class":44,"line":2685},[42,7276,800],{"class":52},[42,7278,7279],{"class":44,"line":2690},[42,7280,885],{"class":52},[42,7282,7283],{"class":44,"line":2699},[42,7284,141],{"class":52},[10,7286,7288],{"id":7287},"in-short","In short",[15,7290,7291,7292,7294],{},"Vue gives you a secure foundation and the rest is on us. Sanitise user input, especially anything approaching ",[39,7293,6034],{},". Implement CSRF tokens with your backend. Audit and update dependencies. Keep secrets out of the bundle. Validate authentication and authorisation on the server, every time.",[1592,7296,7297],{},"html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}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 .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sHaLz, html code.shiki .sHaLz{--shiki-default:#7EE787;--shiki-default-font-weight:bold}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}",{"title":37,"searchDepth":60,"depth":60,"links":7299},[7300,7301,7302,7303,7304,7305],{"id":5939,"depth":60,"text":5940},{"id":5946,"depth":60,"text":5947},{"id":6360,"depth":60,"text":6361},{"id":6628,"depth":60,"text":6629},{"id":7043,"depth":60,"text":7044},{"id":7287,"depth":60,"text":7288},"2023-09-15","Five vulnerabilities that show up in Vue applications, with the fixes for each.",{},"\u002Fblog\u002Fsecurity-risks-in-vuejs-applications",{"title":5925,"description":7307},"blog\u002Fsecurity-risks-in-vuejs-applications",[2722,7313,36,5783,1739],"security","security-risks-in-vuejs-applications","Q9U8gqNid4zBUxPcxE-FYShTwUKvzYxypszKlpeKRA0",{"id":7317,"title":7318,"body":7319,"category":2368,"date":1608,"description":7683,"draft":1610,"extension":1611,"meta":7684,"navigation":300,"path":7685,"seo":7686,"stem":7687,"tags":7688,"translationKey":7690,"videoId":1622,"__hash__":7691},"blog_en\u002Fblog\u002Fthe-future-of-ai-coding-assistants.md","The future of AI coding assistants and GPT-5",{"type":7,"value":7320,"toc":7669},[7321,7325,7328,7331,7335,7338,7341,7345,7349,7352,7369,7563,7566,7570,7573,7576,7580,7583,7586,7590,7597,7604,7608,7614,7621,7625,7632,7635,7638,7641,7645,7648,7659,7663,7666],[10,7322,7324],{"id":7323},"how-writing-code-has-changed","How writing code has changed",[15,7326,7327],{},"Remember typing out every line by hand, hunting for semicolons that vanished for no clear reason, and spending hours on a single error?",[15,7329,7330],{},"Software development has always been about solving problems, but the tools we solve them with keep changing. From punch cards to IDEs, and from there to AI coding assistants. If you're thinking \"another tool?\", it earns its place when you want to write code faster, cleaner and easier to scale.",[10,7332,7334],{"id":7333},"what-gpt-5-brings","What GPT-5 brings",[15,7336,7337],{},"We've watched AI grow in a lot of fields, and it's now having a clear effect on how we write software. Tools like GitHub Copilot, built on models from OpenAI's GPT family, already started changing the rules. So what happens with something more advanced, like GPT-5?",[15,7339,7340],{},"Imagine an assistant that has read almost every piece of code ever written, knows several programming languages inside out and can anticipate what you're trying to do. That's the promise of these models. It's no longer only autocompletion: there are suggestions with judgement behind them, error detection, and whole functions or modules generated from a short prompt.",[10,7342,7344],{"id":7343},"where-it-speeds-up-your-work","Where it speeds up your work",[1775,7346,7348],{"id":7347},"code-generation","Code generation",[15,7350,7351],{},"The most obvious benefit. Instead of writing boilerplate or common patterns by hand, you describe what you want and the model produces it.",[15,7353,7354,7355,7358,7359,7362,7363,4256,7366,7368],{},"Say you need a function that fetches data from an API, handles the loading state and displays errors. What's in your head is roughly: \"I need a ",[39,7356,7357],{},"fetchData"," function with ",[39,7360,7361],{},"async\u002Fawait",", and I need to manage ",[39,7364,7365],{},"isLoading",[39,7367,845],{}," states.\" From your project context or a short prompt, the model can produce this skeleton:",[32,7370,7372],{"className":34,"code":7371,"language":36,"meta":37,"style":37},"async function fetchData(url) {\n  const isLoading = ref(true);\n  const error = ref(null);\n  const data = ref(null);\n\n  try {\n    const response = await fetch(url);\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`);\n    }\n    data.value = await response.json();\n  } catch (e) {\n    error.value = e.message;\n  } finally {\n    isLoading.value = false;\n  }\n  return { data, isLoading, error };\n}\n",[39,7373,7374,7388,7405,7421,7437,7441,7447,7461,7471,7493,7497,7512,7520,7529,7537,7548,7552,7559],{"__ignoreMap":37},[42,7375,7376,7378,7380,7382,7384,7386],{"class":44,"line":45},[42,7377,711],{"class":48},[42,7379,714],{"class":48},[42,7381,2520],{"class":66},[42,7383,70],{"class":52},[42,7385,2451],{"class":73},[42,7387,83],{"class":52},[42,7389,7390,7392,7395,7397,7399,7401,7403],{"class":44,"line":60},[42,7391,670],{"class":48},[42,7393,7394],{"class":400}," isLoading",[42,7396,610],{"class":48},[42,7398,2464],{"class":66},[42,7400,70],{"class":52},[42,7402,2504],{"class":400},[42,7404,795],{"class":52},[42,7406,7407,7409,7411,7413,7415,7417,7419],{"class":44,"line":86},[42,7408,670],{"class":48},[42,7410,2478],{"class":400},[42,7412,610],{"class":48},[42,7414,2464],{"class":66},[42,7416,70],{"class":52},[42,7418,2469],{"class":400},[42,7420,795],{"class":52},[42,7422,7423,7425,7427,7429,7431,7433,7435],{"class":44,"line":92},[42,7424,670],{"class":48},[42,7426,673],{"class":400},[42,7428,610],{"class":48},[42,7430,2464],{"class":66},[42,7432,70],{"class":52},[42,7434,2469],{"class":400},[42,7436,795],{"class":52},[42,7438,7439],{"class":44,"line":188},[42,7440,301],{"emptyLinePlaceholder":300},[42,7442,7443,7445],{"class":44,"line":194},[42,7444,727],{"class":48},[42,7446,730],{"class":52},[42,7448,7449,7451,7453,7455,7457,7459],{"class":44,"line":249},[42,7450,735],{"class":48},[42,7452,738],{"class":400},[42,7454,610],{"class":48},[42,7456,743],{"class":48},[42,7458,2568],{"class":66},[42,7460,2571],{"class":52},[42,7462,7463,7465,7467,7469],{"class":44,"line":263},[42,7464,755],{"class":48},[42,7466,758],{"class":52},[42,7468,761],{"class":48},[42,7470,764],{"class":52},[42,7472,7473,7475,7477,7479,7481,7483,7485,7487,7489,7491],{"class":44,"line":268},[42,7474,769],{"class":48},[42,7476,772],{"class":48},[42,7478,775],{"class":66},[42,7480,70],{"class":52},[42,7482,2597],{"class":613},[42,7484,783],{"class":52},[42,7486,786],{"class":613},[42,7488,2605],{"class":52},[42,7490,792],{"class":613},[42,7492,795],{"class":52},[42,7494,7495],{"class":44,"line":273},[42,7496,800],{"class":52},[42,7498,7499,7502,7504,7506,7508,7510],{"class":44,"line":287},[42,7500,7501],{"class":52},"    data.value ",[42,7503,1326],{"class":48},[42,7505,743],{"class":48},[42,7507,813],{"class":52},[42,7509,816],{"class":66},[42,7511,819],{"class":52},[42,7513,7514,7516,7518],{"class":44,"line":292},[42,7515,831],{"class":52},[42,7517,834],{"class":48},[42,7519,2639],{"class":52},[42,7521,7522,7524,7526],{"class":44,"line":297},[42,7523,2539],{"class":52},[42,7525,1326],{"class":48},[42,7527,7528],{"class":52}," e.message;\n",[42,7530,7531,7533,7535],{"class":44,"line":304},[42,7532,831],{"class":52},[42,7534,2657],{"class":48},[42,7536,730],{"class":52},[42,7538,7539,7542,7544,7546],{"class":44,"line":317},[42,7540,7541],{"class":52},"    isLoading.value ",[42,7543,1326],{"class":48},[42,7545,2670],{"class":400},[42,7547,419],{"class":52},[42,7549,7550],{"class":44,"line":326},[42,7551,885],{"class":52},[42,7553,7554,7556],{"class":44,"line":334},[42,7555,424],{"class":48},[42,7557,7558],{"class":52}," { data, isLoading, error };\n",[42,7560,7561],{"class":44,"line":342},[42,7562,141],{"class":52},[15,7564,7565],{},"That's where you save the minutes, sometimes hours, that go into typing repetitive structures.",[1775,7567,7569],{"id":7568},"understanding-and-documenting-code","Understanding and documenting code",[15,7571,7572],{},"Ever stared at a block written by someone else, or by you six months ago, wondering what it actually does? The model can explain complicated logic and generate documentation.",[15,7574,7575],{},"If you have a convoluted regular expression or a difficult algorithm, you select the code and ask it to explain the regex or describe the algorithm's purpose. It turns the logic into readable sentences, which makes onboarding a new team member or reading legacy code easier. It can also propose documentation in formats like JSDoc.",[1775,7577,7579],{"id":7578},"catching-bugs-before-you-run-the-code","Catching bugs before you run the code",[15,7581,7582],{},"This is where AI goes beyond autocompletion. It can review your code for errors, performance bottlenecks and security holes before you even run it.",[15,7584,7585],{},"If an off-by-one error slips into a loop or you forget to sanitise user input, it can flag that in your editor and suggest a fix, or rewrite the problematic section itself. Think of a linter that understands context far better than traditional tools.",[1775,7587,7589],{"id":7588},"refactoring-suggestions","Refactoring suggestions",[15,7591,7592,7593,7596],{},"Writing code is one thing, writing ",[633,7594,7595],{},"good"," code is another. The model can act like an experienced developer reviewing your work and proposing improvements in readability, efficiency and established practice.",[15,7598,7599,7600,7603],{},"If you've written a long chain of conditionals and there's a tidier way, it can suggest a ",[39,7601,7602],{},"switch",", a lookup object, or a design pattern that simplifies the logic. It also points out inefficient loops and data structures worth improving.",[1775,7605,7607],{"id":7606},"learning-along-the-way","Learning along the way",[15,7609,7610,7611,7613],{},"You might wonder whether this will make you a worse developer. You can use it the other way round, as a mentor available at any hour. When the model suggests a solution or a pattern you don't recognise, you can ask ",[633,7612,1791],{}," that approach is better.",[15,7615,7616,7617,7620],{},"If it suggests a ",[39,7618,7619],{},"Map"," instead of a plain object for a given data structure and you ask why, it will explain the performance differences, key ordering and how each handles keys of different types.",[10,7622,7624],{"id":7623},"the-human-part-still-counts","The human part still counts",[15,7626,7627,7628,7631],{},"Before assuming AI will replace everyone, it's worth slowing down. GPT-5 is powerful, but it's a ",[633,7629,7630],{},"tool","; it doesn't replace creativity, problem solving or judgement.",[15,7633,7634],{},"It doesn't know your context the way you do. Models are good at patterns, but they don't understand your project's specific business logic, its long-term direction or requirements with nuance in them. You still set the high-level direction and review the output critically.",[15,7636,7637],{},"There's an ethical side too: relying only on AI can carry over biases present in its training data, or produce weak solutions to genuinely new problems.",[15,7639,7640],{},"Then there are the AI's own bugs. It sometimes generates code that looks correct but hides a subtle fault. Testing and the quality of the final product remain your responsibility.",[10,7642,7644],{"id":7643},"how-it-fits-into-your-workflow","How it fits into your workflow",[15,7646,7647],{},"The good thing about these tools is that they sit inside the editors you already use. Whether you work in VS Code, IntelliJ or something else, there are extensions that bring these capabilities within reach.",[15,7649,7650,7651,7654,7655,7658],{},"Imagine writing a comment like ",[39,7652,7653],{},"\u002F\u002F Function to validate user email"," and watching the function appear below it. Or typing ",[39,7656,7657],{},"const user = "," and getting property suggestions based on your project's schema. The point is to reduce friction and keep you in flow.",[10,7660,7662],{"id":7661},"where-it-leads","Where it leads",[15,7664,7665],{},"The future of coding is shared work between human ingenuity and artificial intelligence. These tools aren't here to take your job; they're here to take the repetitive part and leave you the hard and the new.",[1592,7667,7668],{},"html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}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);}",{"title":37,"searchDepth":60,"depth":60,"links":7670},[7671,7672,7673,7680,7681,7682],{"id":7323,"depth":60,"text":7324},{"id":7333,"depth":60,"text":7334},{"id":7343,"depth":60,"text":7344,"children":7674},[7675,7676,7677,7678,7679],{"id":7347,"depth":86,"text":7348},{"id":7568,"depth":86,"text":7569},{"id":7578,"depth":86,"text":7579},{"id":7588,"depth":86,"text":7589},{"id":7606,"depth":86,"text":7607},{"id":7623,"depth":60,"text":7624},{"id":7643,"depth":60,"text":7644},{"id":7661,"depth":60,"text":7662},"How AI coding assistants like GPT-5 change the development workflow, where they genuinely help, and where a human is still required.",{},"\u002Fblog\u002Fthe-future-of-ai-coding-assistants",{"title":7318,"description":7683},"blog\u002Fthe-future-of-ai-coding-assistants",[1847,7689,5920,2018],"GPT-5","the-future-of-ai-coding-assistants","H_cMwcOdi7tlBHU3C2fdBWSlkwJ_Lt7Gdxht2Czx-Zg",{"id":7693,"title":7694,"body":7695,"category":1607,"date":2369,"description":7858,"draft":1610,"extension":1611,"meta":7859,"navigation":300,"path":7860,"seo":7861,"stem":7862,"tags":7863,"translationKey":7866,"videoId":1622,"__hash__":7867},"blog_en\u002Fblog\u002Funderrated-productivity-tools-for-developers.md","Ten underrated productivity tools for developers",{"type":7,"value":7696,"toc":7845},[7697,7700,7704,7707,7710,7713,7717,7720,7723,7726,7730,7733,7740,7744,7747,7750,7753,7757,7760,7763,7767,7770,7773,7776,7780,7783,7786,7790,7793,7796,7800,7807,7822,7825,7829,7832,7835,7838,7842],[15,7698,7699],{},"Development work usually means juggling a dozen things at once, from fixing bugs to designing new features. We all have the standard kit: VS Code, Git, maybe Jira or Trello. This list is about the other ones, the tools that don't show up on every roundup but quietly remove friction from a working day.",[10,7701,7703],{"id":7702},"_1-raycast","1. Raycast",[15,7705,7706],{},"You might reasonably ask why you'd want another launcher when macOS already has Spotlight. Raycast is a fast keyboard-driven launcher that goes considerably further.",[15,7708,7709],{},"From one search bar you can create snippets, convert currencies, control Spotify and manage clipboard history. You can write your own extensions to automate the repetitive parts of your own workflow, such as a daily Git push or hitting a specific API endpoint in a few keystrokes. It also connects to Notion, Jira, Linear and GitHub, so you can manage tasks, open issues and search documents without opening a browser.",[15,7711,7712],{},"It saves the kind of seconds that turn into hours over a month.",[10,7714,7716],{"id":7715},"_2-typora","2. Typora",[15,7718,7719],{},"A markdown editor doesn't sound remarkable until you use this one. If you write documentation, blog posts or long notes, Typora gives you a quiet place to do it.",[15,7721,7722],{},"There are no split panes: you write markdown and it renders as you type. Focus mode hides everything except the paragraph you're working on. Export covers PDF, Word, HTML and others.",[15,7724,7725],{},"It's the kind of tool that makes you want to write more.",[10,7727,7729],{"id":7728},"_3-warp","3. Warp",[15,7731,7732],{},"A terminal doesn't sound underrated, but Warp is built around a different set of assumptions. It uses AI and a collaborative model to make the command line more efficient.",[15,7734,7735,7736,7739],{},"If you can't remember an obscure ",[39,7737,7738],{},"git"," command, it will suggest one based on your context. Each command and its output are grouped into a visual block, which makes scrolling back through a session much easier. You can also share commands and workflows with your team from inside the terminal.",[10,7741,7743],{"id":7742},"_4-obsidian","4. Obsidian",[15,7745,7746],{},"Most of us have tried a dozen note apps. Obsidian's difference is that it's built for connecting notes rather than filing them.",[15,7748,7749],{},"Everything is a local markdown file, so you keep control of your own data. Links work in both directions, so you can see which notes point back at the one you're reading, and the graph view shows those connections visually.",[15,7751,7752],{},"For developers who are constantly learning, that structure holds up much better over years than a folder tree.",[10,7754,7756],{"id":7755},"_5-rectangle","5. Rectangle",[15,7758,7759],{},"Simple and effective. Rectangle is a free, open-source window manager for macOS that snaps windows into layouts with keyboard shortcuts or drag gestures: halves, quarters, custom sizes, across multiple displays.",[15,7761,7762],{},"No more dragging and resizing windows by hand. Small change, noticeable difference.",[10,7764,7766],{"id":7765},"_6-cron","6. Cron",[15,7768,7769],{},"A calendar app, yes. Cron is designed for people whose schedule is a working tool rather than a record.",[15,7771,7772],{},"The interface is clean and keyboard-first, so you can create events and jump between dates without reaching for the mouse. Time blocking is built in, which makes it practical to reserve focus time rather than hoping for it.",[15,7774,7775],{},"It helps you keep to a schedule rather than only track one.",[10,7777,7779],{"id":7778},"_7-excalidraw","7. Excalidraw",[15,7781,7782],{},"Sometimes you need to sketch an idea, a flow or a system's architecture without opening a serious diagramming tool. Excalidraw is a virtual whiteboard where everything looks hand-drawn.",[15,7784,7785],{},"That rough look is the point: it suits brainstorming and early ideas, where a polished diagram would imply more certainty than you have. Boards can be shared for real-time collaboration, embedded in Notion or Obsidian, or exported as SVG or PNG.",[10,7787,7789],{"id":7788},"_8-session","8. Session",[15,7791,7792],{},"Everyone knows the Pomodoro technique. Work twenty five minutes, break for five. Simple in theory and easy to forget in practice. Session is a minimal app that helps you actually do it.",[15,7794,7795],{},"The timer is clean and unobtrusive, it nudges you to take breaks and start new sessions, and it tracks how much focused work you're getting through.",[10,7797,7799],{"id":7798},"_9-fzf","9. fzf",[15,7801,7802,7803,7806],{},"This one is more technical, but ",[39,7804,7805],{},"fzf"," changes how the terminal feels. It's a general-purpose fuzzy finder for the command line: files, command history, processes, Git commits.",[15,7808,7809,7810,77,7813,77,7816,4256,7819,7821],{},"You can find almost anything in a few keystrokes, and it plugs into ",[39,7811,7812],{},"cd",[39,7814,7815],{},"grep",[39,7817,7818],{},"history",[39,7820,7738],{},". It's highly configurable, so it ends up shaped around your habits.",[15,7823,7824],{},"In large projects it saves a genuinely surprising amount of time.",[10,7826,7828],{"id":7827},"_10-toucan","10. Toucan",[15,7830,7831],{},"This isn't a developer tool in the usual sense. Toucan is a browser extension that quietly swaps words on the pages you already visit for words in a language you're learning.",[15,7833,7834],{},"You pick up vocabulary in context without setting aside study time. It also breaks up the day with something that isn't code, and stepping away from a problem with a different kind of task is often what lets you come back to it with fresh eyes.",[15,7836,7837],{},"The connection to development work is less strange than it sounds. Developers travel, work in international teams and read documentation written all over the world.",[10,7839,7841],{"id":7840},"wrapping-up","Wrapping up",[15,7843,7844],{},"None of these are on every list of top productivity apps, and each removes a specific bit of friction. Productivity isn't really about working harder; most of it is about removing the small obstacles between you and the work.",{"title":37,"searchDepth":60,"depth":60,"links":7846},[7847,7848,7849,7850,7851,7852,7853,7854,7855,7856,7857],{"id":7702,"depth":60,"text":7703},{"id":7715,"depth":60,"text":7716},{"id":7728,"depth":60,"text":7729},{"id":7742,"depth":60,"text":7743},{"id":7755,"depth":60,"text":7756},{"id":7765,"depth":60,"text":7766},{"id":7778,"depth":60,"text":7779},{"id":7788,"depth":60,"text":7789},{"id":7798,"depth":60,"text":7799},{"id":7827,"depth":60,"text":7828},{"id":7840,"depth":60,"text":7841},"Lesser known tools that cut down repetitive work, organise what you know and make focus easier to reach.",{},"\u002Fblog\u002Funderrated-productivity-tools-for-developers",{"title":7694,"description":7858},"blog\u002Funderrated-productivity-tools-for-developers",[2018,7864,2020,7865],"tools","workflow","underrated-productivity-tools-for-developers","xxPxepocz7zPwPTBDGygd7WpMODLlGIOCS30c7FljtM",{"id":4,"title":5,"body":7869,"category":1607,"date":1608,"description":1609,"draft":1610,"extension":1611,"meta":9169,"navigation":300,"path":1613,"seo":9170,"stem":1615,"tags":9171,"translationKey":1621,"videoId":1622,"__hash__":1623},{"type":7,"value":7870,"toc":9157},[7871,7873,7875,7877,7879,7881,7883,7921,7923,7957,7959,7961,7963,7999,8001,8129,8131,8133,8135,8239,8241,8297,8299,8301,8303,8319,8321,8353,8357,8359,8361,8363,8405,8407,8555,8557,8563,8565,8627,8629,8685,8687,8689,8691,8693,8901,8903,8905,9033,9035,9147,9149,9151,9153,9155],[10,7872,13],{"id":12},[15,7874,17],{},[15,7876,20],{},[15,7878,23],{},[10,7880,27],{"id":26},[15,7882,30],{},[32,7884,7885],{"className":34,"code":35,"language":36,"meta":37,"style":37},[39,7886,7887,7895,7911,7915],{"__ignoreMap":37},[42,7888,7889,7891,7893],{"class":44,"line":45},[42,7890,49],{"class":48},[42,7892,53],{"class":52},[42,7894,57],{"class":56},[42,7896,7897,7899,7901,7903,7905,7907,7909],{"class":44,"line":60},[42,7898,63],{"class":48},[42,7900,67],{"class":66},[42,7902,70],{"class":52},[42,7904,74],{"class":73},[42,7906,77],{"class":52},[42,7908,80],{"class":73},[42,7910,83],{"class":52},[42,7912,7913],{"class":44,"line":86},[42,7914,89],{"class":56},[42,7916,7917,7919],{"class":44,"line":92},[42,7918,95],{"class":52},[42,7920,98],{"class":56},[15,7922,101],{},[32,7924,7925],{"className":34,"code":104,"language":36,"meta":37,"style":37},[39,7926,7927,7933,7949,7953],{"__ignoreMap":37},[42,7928,7929,7931],{"class":44,"line":45},[42,7930,49],{"class":48},[42,7932,113],{"class":52},[42,7934,7935,7937,7939,7941,7943,7945,7947],{"class":44,"line":60},[42,7936,63],{"class":48},[42,7938,120],{"class":66},[42,7940,70],{"class":52},[42,7942,125],{"class":73},[42,7944,77],{"class":52},[42,7946,130],{"class":73},[42,7948,83],{"class":52},[42,7950,7951],{"class":44,"line":86},[42,7952,89],{"class":56},[42,7954,7955],{"class":44,"line":92},[42,7956,141],{"class":52},[15,7958,144],{},[10,7960,148],{"id":147},[15,7962,151],{},[32,7964,7965],{"className":34,"code":154,"language":36,"meta":37,"style":37},[39,7966,7967,7979,7983,7987,7991,7995],{"__ignoreMap":37},[42,7968,7969,7971,7973,7975,7977],{"class":44,"line":45},[42,7970,63],{"class":48},[42,7972,163],{"class":66},[42,7974,70],{"class":52},[42,7976,168],{"class":73},[42,7978,83],{"class":52},[42,7980,7981],{"class":44,"line":60},[42,7982,175],{"class":56},[42,7984,7985],{"class":44,"line":86},[42,7986,180],{"class":56},[42,7988,7989],{"class":44,"line":92},[42,7990,185],{"class":56},[42,7992,7993],{"class":44,"line":188},[42,7994,191],{"class":56},[42,7996,7997],{"class":44,"line":194},[42,7998,141],{"class":52},[15,8000,199],{},[32,8002,8003],{"className":34,"code":202,"language":36,"meta":37,"style":37},[39,8004,8005,8017,8021,8025,8037,8041,8045,8057,8061,8065,8077,8081,8085,8089,8101,8107,8113,8119,8125],{"__ignoreMap":37},[42,8006,8007,8009,8011,8013,8015],{"class":44,"line":45},[42,8008,63],{"class":48},[42,8010,211],{"class":66},[42,8012,70],{"class":52},[42,8014,168],{"class":73},[42,8016,83],{"class":52},[42,8018,8019],{"class":44,"line":60},[42,8020,89],{"class":56},[42,8022,8023],{"class":44,"line":86},[42,8024,141],{"class":52},[42,8026,8027,8029,8031,8033,8035],{"class":44,"line":92},[42,8028,63],{"class":48},[42,8030,232],{"class":66},[42,8032,70],{"class":52},[42,8034,168],{"class":73},[42,8036,83],{"class":52},[42,8038,8039],{"class":44,"line":188},[42,8040,89],{"class":56},[42,8042,8043],{"class":44,"line":194},[42,8044,141],{"class":52},[42,8046,8047,8049,8051,8053,8055],{"class":44,"line":249},[42,8048,63],{"class":48},[42,8050,254],{"class":66},[42,8052,70],{"class":52},[42,8054,168],{"class":73},[42,8056,83],{"class":52},[42,8058,8059],{"class":44,"line":263},[42,8060,89],{"class":56},[42,8062,8063],{"class":44,"line":268},[42,8064,141],{"class":52},[42,8066,8067,8069,8071,8073,8075],{"class":44,"line":273},[42,8068,63],{"class":48},[42,8070,278],{"class":66},[42,8072,70],{"class":52},[42,8074,168],{"class":73},[42,8076,83],{"class":52},[42,8078,8079],{"class":44,"line":287},[42,8080,89],{"class":56},[42,8082,8083],{"class":44,"line":292},[42,8084,141],{"class":52},[42,8086,8087],{"class":44,"line":297},[42,8088,301],{"emptyLinePlaceholder":300},[42,8090,8091,8093,8095,8097,8099],{"class":44,"line":304},[42,8092,63],{"class":48},[42,8094,163],{"class":66},[42,8096,70],{"class":52},[42,8098,168],{"class":73},[42,8100,83],{"class":52},[42,8102,8103,8105],{"class":44,"line":317},[42,8104,320],{"class":66},[42,8106,323],{"class":52},[42,8108,8109,8111],{"class":44,"line":326},[42,8110,329],{"class":66},[42,8112,323],{"class":52},[42,8114,8115,8117],{"class":44,"line":334},[42,8116,337],{"class":66},[42,8118,323],{"class":52},[42,8120,8121,8123],{"class":44,"line":342},[42,8122,345],{"class":66},[42,8124,323],{"class":52},[42,8126,8127],{"class":44,"line":350},[42,8128,141],{"class":52},[15,8130,355],{},[10,8132,359],{"id":358},[15,8134,362],{},[32,8136,8137],{"className":34,"code":365,"language":36,"meta":37,"style":37},[39,8138,8139,8143,8155,8177,8183,8187,8191,8195,8207,8229,8235],{"__ignoreMap":37},[42,8140,8141],{"class":44,"line":45},[42,8142,372],{"class":56},[42,8144,8145,8147,8149,8151,8153],{"class":44,"line":60},[42,8146,63],{"class":48},[42,8148,379],{"class":66},[42,8150,70],{"class":52},[42,8152,384],{"class":73},[42,8154,83],{"class":52},[42,8156,8157,8159,8161,8163,8165,8167,8169,8171,8173,8175],{"class":44,"line":86},[42,8158,391],{"class":48},[42,8160,394],{"class":52},[42,8162,397],{"class":48},[42,8164,401],{"class":400},[42,8166,404],{"class":52},[42,8168,407],{"class":48},[42,8170,410],{"class":52},[42,8172,413],{"class":48},[42,8174,416],{"class":400},[42,8176,419],{"class":52},[42,8178,8179,8181],{"class":44,"line":92},[42,8180,424],{"class":48},[42,8182,427],{"class":52},[42,8184,8185],{"class":44,"line":188},[42,8186,141],{"class":52},[42,8188,8189],{"class":44,"line":194},[42,8190,301],{"emptyLinePlaceholder":300},[42,8192,8193],{"class":44,"line":249},[42,8194,440],{"class":56},[42,8196,8197,8199,8201,8203,8205],{"class":44,"line":263},[42,8198,63],{"class":48},[42,8200,447],{"class":66},[42,8202,70],{"class":52},[42,8204,452],{"class":73},[42,8206,83],{"class":52},[42,8208,8209,8211,8213,8215,8217,8219,8221,8223,8225,8227],{"class":44,"line":268},[42,8210,391],{"class":48},[42,8212,461],{"class":52},[42,8214,397],{"class":48},[42,8216,401],{"class":400},[42,8218,404],{"class":52},[42,8220,407],{"class":48},[42,8222,472],{"class":52},[42,8224,413],{"class":48},[42,8226,416],{"class":400},[42,8228,419],{"class":52},[42,8230,8231,8233],{"class":44,"line":273},[42,8232,424],{"class":48},[42,8234,485],{"class":52},[42,8236,8237],{"class":44,"line":287},[42,8238,141],{"class":52},[15,8240,492],{},[32,8242,8243],{"className":34,"code":495,"language":36,"meta":37,"style":37},[39,8244,8245,8257,8279,8285,8289,8293],{"__ignoreMap":37},[42,8246,8247,8249,8251,8253,8255],{"class":44,"line":45},[42,8248,63],{"class":48},[42,8250,504],{"class":66},[42,8252,70],{"class":52},[42,8254,384],{"class":73},[42,8256,83],{"class":52},[42,8258,8259,8261,8263,8265,8267,8269,8271,8273,8275,8277],{"class":44,"line":60},[42,8260,391],{"class":48},[42,8262,394],{"class":52},[42,8264,397],{"class":48},[42,8266,401],{"class":400},[42,8268,404],{"class":52},[42,8270,407],{"class":48},[42,8272,410],{"class":52},[42,8274,413],{"class":48},[42,8276,416],{"class":400},[42,8278,419],{"class":52},[42,8280,8281,8283],{"class":44,"line":86},[42,8282,424],{"class":48},[42,8284,427],{"class":52},[42,8286,8287],{"class":44,"line":92},[42,8288,141],{"class":52},[42,8290,8291],{"class":44,"line":188},[42,8292,301],{"emptyLinePlaceholder":300},[42,8294,8295],{"class":44,"line":194},[42,8296,551],{"class":56},[10,8298,555],{"id":554},[15,8300,558],{},[15,8302,561],{},[32,8304,8305],{"className":34,"code":564,"language":36,"meta":37,"style":37},[39,8306,8307,8311],{"__ignoreMap":37},[42,8308,8309],{"class":44,"line":45},[42,8310,571],{"class":56},[42,8312,8313,8315,8317],{"class":44,"line":60},[42,8314,576],{"class":52},[42,8316,579],{"class":48},[42,8318,419],{"class":52},[15,8320,584],{},[32,8322,8323],{"className":34,"code":587,"language":36,"meta":37,"style":37},[39,8324,8325,8329,8333],{"__ignoreMap":37},[42,8326,8327],{"class":44,"line":45},[42,8328,594],{"class":56},[42,8330,8331],{"class":44,"line":60},[42,8332,599],{"class":56},[42,8334,8335,8337,8339,8341,8343,8345,8347,8349,8351],{"class":44,"line":86},[42,8336,604],{"class":48},[42,8338,607],{"class":400},[42,8340,610],{"class":48},[42,8342,614],{"class":613},[42,8344,617],{"class":48},[42,8346,620],{"class":400},[42,8348,623],{"class":48},[42,8350,626],{"class":613},[42,8352,419],{"class":52},[15,8354,631,8355,636],{},[633,8356,635],{},[10,8358,640],{"id":639},[15,8360,643],{},[15,8362,646],{},[32,8364,8365],{"className":34,"code":649,"language":36,"meta":37,"style":37},[39,8366,8367,8379,8395,8401],{"__ignoreMap":37},[42,8368,8369,8371,8373,8375,8377],{"class":44,"line":45},[42,8370,63],{"class":48},[42,8372,658],{"class":66},[42,8374,70],{"class":52},[42,8376,663],{"class":73},[42,8378,83],{"class":52},[42,8380,8381,8383,8385,8387,8389,8391,8393],{"class":44,"line":60},[42,8382,670],{"class":48},[42,8384,673],{"class":400},[42,8386,610],{"class":48},[42,8388,678],{"class":52},[42,8390,681],{"class":66},[42,8392,684],{"class":52},[42,8394,687],{"class":56},[42,8396,8397,8399],{"class":44,"line":86},[42,8398,424],{"class":48},[42,8400,694],{"class":52},[42,8402,8403],{"class":44,"line":92},[42,8404,141],{"class":52},[15,8406,701],{},[32,8408,8409],{"className":34,"code":704,"language":36,"meta":37,"style":37},[39,8410,8411,8425,8431,8447,8457,8479,8483,8499,8505,8513,8529,8533,8547,8551],{"__ignoreMap":37},[42,8412,8413,8415,8417,8419,8421,8423],{"class":44,"line":45},[42,8414,711],{"class":48},[42,8416,714],{"class":48},[42,8418,658],{"class":66},[42,8420,70],{"class":52},[42,8422,663],{"class":73},[42,8424,83],{"class":52},[42,8426,8427,8429],{"class":44,"line":60},[42,8428,727],{"class":48},[42,8430,730],{"class":52},[42,8432,8433,8435,8437,8439,8441,8443,8445],{"class":44,"line":86},[42,8434,735],{"class":48},[42,8436,738],{"class":400},[42,8438,610],{"class":48},[42,8440,743],{"class":48},[42,8442,678],{"class":52},[42,8444,681],{"class":66},[42,8446,750],{"class":52},[42,8448,8449,8451,8453,8455],{"class":44,"line":92},[42,8450,755],{"class":48},[42,8452,758],{"class":52},[42,8454,761],{"class":48},[42,8456,764],{"class":52},[42,8458,8459,8461,8463,8465,8467,8469,8471,8473,8475,8477],{"class":44,"line":188},[42,8460,769],{"class":48},[42,8462,772],{"class":48},[42,8464,775],{"class":66},[42,8466,70],{"class":52},[42,8468,780],{"class":613},[42,8470,783],{"class":52},[42,8472,786],{"class":613},[42,8474,789],{"class":52},[42,8476,792],{"class":613},[42,8478,795],{"class":52},[42,8480,8481],{"class":44,"line":194},[42,8482,800],{"class":52},[42,8484,8485,8487,8489,8491,8493,8495,8497],{"class":44,"line":249},[42,8486,735],{"class":48},[42,8488,673],{"class":400},[42,8490,610],{"class":48},[42,8492,743],{"class":48},[42,8494,813],{"class":52},[42,8496,816],{"class":66},[42,8498,819],{"class":52},[42,8500,8501,8503],{"class":44,"line":263},[42,8502,824],{"class":48},[42,8504,694],{"class":52},[42,8506,8507,8509,8511],{"class":44,"line":268},[42,8508,831],{"class":52},[42,8510,834],{"class":48},[42,8512,837],{"class":52},[42,8514,8515,8517,8519,8521,8523,8525,8527],{"class":44,"line":273},[42,8516,842],{"class":52},[42,8518,845],{"class":66},[42,8520,70],{"class":52},[42,8522,850],{"class":613},[42,8524,663],{"class":52},[42,8526,855],{"class":613},[42,8528,858],{"class":52},[42,8530,8531],{"class":44,"line":287},[42,8532,863],{"class":56},[42,8534,8535,8537,8539,8541,8543,8545],{"class":44,"line":292},[42,8536,868],{"class":48},[42,8538,772],{"class":48},[42,8540,873],{"class":66},[42,8542,70],{"class":52},[42,8544,878],{"class":613},[42,8546,795],{"class":52},[42,8548,8549],{"class":44,"line":297},[42,8550,885],{"class":52},[42,8552,8553],{"class":44,"line":304},[42,8554,141],{"class":52},[10,8556,893],{"id":892},[15,8558,896,8559,900,8561,904],{},[39,8560,899],{},[39,8562,903],{},[15,8564,907],{},[32,8566,8567],{"className":34,"code":910,"language":36,"meta":37,"style":37},[39,8568,8569,8575,8587,8595,8599,8603,8619,8623],{"__ignoreMap":37},[42,8570,8571,8573],{"class":44,"line":45},[42,8572,49],{"class":48},[42,8574,919],{"class":52},[42,8576,8577,8579,8581,8583,8585],{"class":44,"line":60},[42,8578,604],{"class":48},[42,8580,926],{"class":400},[42,8582,610],{"class":48},[42,8584,931],{"class":400},[42,8586,419],{"class":52},[42,8588,8589,8591,8593],{"class":44,"line":86},[42,8590,63],{"class":48},[42,8592,940],{"class":66},[42,8594,943],{"class":52},[42,8596,8597],{"class":44,"line":92},[42,8598,89],{"class":56},[42,8600,8601],{"class":44,"line":188},[42,8602,141],{"class":52},[42,8604,8605,8607,8609,8611,8613,8615,8617],{"class":44,"line":194},[42,8606,604],{"class":48},[42,8608,958],{"class":66},[42,8610,610],{"class":48},[42,8612,963],{"class":48},[42,8614,966],{"class":52},[42,8616,969],{"class":48},[42,8618,730],{"class":52},[42,8620,8621],{"class":44,"line":249},[42,8622,89],{"class":56},[42,8624,8625],{"class":44,"line":263},[42,8626,980],{"class":52},[15,8628,983],{},[32,8630,8631],{"className":34,"code":986,"language":36,"meta":37,"style":37},[39,8632,8633,8639,8651,8659,8663,8667,8677,8681],{"__ignoreMap":37},[42,8634,8635,8637],{"class":44,"line":45},[42,8636,49],{"class":48},[42,8638,919],{"class":52},[42,8640,8641,8643,8645,8647,8649],{"class":44,"line":60},[42,8642,604],{"class":48},[42,8644,1001],{"class":400},[42,8646,610],{"class":48},[42,8648,931],{"class":400},[42,8650,419],{"class":52},[42,8652,8653,8655,8657],{"class":44,"line":86},[42,8654,63],{"class":48},[42,8656,940],{"class":66},[42,8658,943],{"class":52},[42,8660,8661],{"class":44,"line":92},[42,8662,89],{"class":56},[42,8664,8665],{"class":44,"line":188},[42,8666,141],{"class":52},[42,8668,8669,8671,8673,8675],{"class":44,"line":194},[42,8670,711],{"class":48},[42,8672,714],{"class":48},[42,8674,958],{"class":66},[42,8676,943],{"class":52},[42,8678,8679],{"class":44,"line":249},[42,8680,89],{"class":56},[42,8682,8683],{"class":44,"line":263},[42,8684,141],{"class":52},[15,8686,1044],{},[10,8688,1048],{"id":1047},[15,8690,1051],{},[15,8692,1054],{},[32,8694,8695],{"className":34,"code":1057,"language":36,"meta":37,"style":37},[39,8696,8697,8701,8717,8727,8731,8735,8739,8753,8767,8793,8797,8801,8815,8843,8847,8851,8865,8893,8897],{"__ignoreMap":37},[42,8698,8699],{"class":44,"line":45},[42,8700,1064],{"class":56},[42,8702,8703,8705,8707,8709,8711,8713,8715],{"class":44,"line":60},[42,8704,63],{"class":48},[42,8706,1071],{"class":66},[42,8708,70],{"class":52},[42,8710,74],{"class":73},[42,8712,77],{"class":52},[42,8714,80],{"class":73},[42,8716,83],{"class":52},[42,8718,8719,8721,8723,8725],{"class":44,"line":86},[42,8720,424],{"class":48},[42,8722,1088],{"class":52},[42,8724,1091],{"class":48},[42,8726,1094],{"class":52},[42,8728,8729],{"class":44,"line":92},[42,8730,141],{"class":52},[42,8732,8733],{"class":44,"line":188},[42,8734,301],{"emptyLinePlaceholder":300},[42,8736,8737],{"class":44,"line":194},[42,8738,1107],{"class":56},[42,8740,8741,8743,8745,8747,8749,8751],{"class":44,"line":249},[42,8742,1112],{"class":66},[42,8744,70],{"class":52},[42,8746,1117],{"class":613},[42,8748,1120],{"class":52},[42,8750,969],{"class":48},[42,8752,730],{"class":52},[42,8754,8755,8757,8759,8761,8763,8765],{"class":44,"line":263},[42,8756,1129],{"class":66},[42,8758,70],{"class":52},[42,8760,1134],{"class":613},[42,8762,1120],{"class":52},[42,8764,969],{"class":48},[42,8766,730],{"class":52},[42,8768,8769,8771,8773,8775,8777,8779,8781,8783,8785,8787,8789,8791],{"class":44,"line":268},[42,8770,1145],{"class":66},[42,8772,70],{"class":52},[42,8774,1150],{"class":66},[42,8776,70],{"class":52},[42,8778,1155],{"class":400},[42,8780,77],{"class":52},[42,8782,1160],{"class":400},[42,8784,1163],{"class":52},[42,8786,1166],{"class":66},[42,8788,70],{"class":52},[42,8790,1171],{"class":400},[42,8792,795],{"class":52},[42,8794,8795],{"class":44,"line":273},[42,8796,1178],{"class":52},[42,8798,8799],{"class":44,"line":287},[42,8800,301],{"emptyLinePlaceholder":300},[42,8802,8803,8805,8807,8809,8811,8813],{"class":44,"line":292},[42,8804,1129],{"class":66},[42,8806,70],{"class":52},[42,8808,1191],{"class":613},[42,8810,1120],{"class":52},[42,8812,969],{"class":48},[42,8814,730],{"class":52},[42,8816,8817,8819,8821,8823,8825,8827,8829,8831,8833,8835,8837,8839,8841],{"class":44,"line":297},[42,8818,1145],{"class":66},[42,8820,70],{"class":52},[42,8822,1150],{"class":66},[42,8824,70],{"class":52},[42,8826,1210],{"class":48},[42,8828,1155],{"class":400},[42,8830,77],{"class":52},[42,8832,1217],{"class":400},[42,8834,1163],{"class":52},[42,8836,1166],{"class":66},[42,8838,70],{"class":52},[42,8840,1226],{"class":400},[42,8842,795],{"class":52},[42,8844,8845],{"class":44,"line":304},[42,8846,1178],{"class":52},[42,8848,8849],{"class":44,"line":317},[42,8850,301],{"emptyLinePlaceholder":300},[42,8852,8853,8855,8857,8859,8861,8863],{"class":44,"line":326},[42,8854,1129],{"class":66},[42,8856,70],{"class":52},[42,8858,1245],{"class":613},[42,8860,1120],{"class":52},[42,8862,969],{"class":48},[42,8864,730],{"class":52},[42,8866,8867,8869,8871,8873,8875,8877,8879,8881,8883,8885,8887,8889,8891],{"class":44,"line":334},[42,8868,1145],{"class":66},[42,8870,70],{"class":52},[42,8872,1150],{"class":66},[42,8874,70],{"class":52},[42,8876,1210],{"class":48},[42,8878,1171],{"class":400},[42,8880,77],{"class":52},[42,8882,1171],{"class":400},[42,8884,1163],{"class":52},[42,8886,1166],{"class":66},[42,8888,70],{"class":52},[42,8890,1278],{"class":400},[42,8892,795],{"class":52},[42,8894,8895],{"class":44,"line":342},[42,8896,1178],{"class":52},[42,8898,8899],{"class":44,"line":350},[42,8900,1289],{"class":52},[10,8902,1293],{"id":1292},[15,8904,1296],{},[32,8906,8907],{"className":34,"code":1299,"language":36,"meta":37,"style":37},[39,8908,8909,8921,8933,8961,8971,8983,8999,9007,9015,9019,9023,9029],{"__ignoreMap":37},[42,8910,8911,8913,8915,8917,8919],{"class":44,"line":45},[42,8912,63],{"class":48},[42,8914,1308],{"class":66},[42,8916,70],{"class":52},[42,8918,1313],{"class":73},[42,8920,83],{"class":52},[42,8922,8923,8925,8927,8929,8931],{"class":44,"line":60},[42,8924,1320],{"class":48},[42,8926,1323],{"class":52},[42,8928,1326],{"class":48},[42,8930,1329],{"class":400},[42,8932,419],{"class":52},[42,8934,8935,8937,8939,8941,8943,8945,8947,8949,8951,8953,8955,8957,8959],{"class":44,"line":86},[42,8936,1336],{"class":48},[42,8938,758],{"class":52},[42,8940,49],{"class":48},[42,8942,1343],{"class":52},[42,8944,1326],{"class":48},[42,8946,1329],{"class":400},[42,8948,1350],{"class":52},[42,8950,1353],{"class":48},[42,8952,1356],{"class":52},[42,8954,1359],{"class":400},[42,8956,1362],{"class":52},[42,8958,579],{"class":48},[42,8960,83],{"class":52},[42,8962,8963,8965,8967,8969],{"class":44,"line":92},[42,8964,1371],{"class":48},[42,8966,1374],{"class":52},[42,8968,1326],{"class":48},[42,8970,1379],{"class":52},[42,8972,8973,8975,8977,8979,8981],{"class":44,"line":188},[42,8974,755],{"class":48},[42,8976,1386],{"class":52},[42,8978,1389],{"class":48},[42,8980,1392],{"class":613},[42,8982,83],{"class":52},[42,8984,8985,8987,8989,8991,8993,8995,8997],{"class":44,"line":194},[42,8986,1399],{"class":52},[42,8988,1402],{"class":48},[42,8990,1405],{"class":52},[42,8992,413],{"class":48},[42,8994,1410],{"class":400},[42,8996,1413],{"class":52},[42,8998,1416],{"class":56},[42,9000,9001,9003,9005],{"class":44,"line":249},[42,9002,1421],{"class":52},[42,9004,1424],{"class":48},[42,9006,730],{"class":52},[42,9008,9009,9011,9013],{"class":44,"line":263},[42,9010,1399],{"class":52},[42,9012,1402],{"class":48},[42,9014,1435],{"class":52},[42,9016,9017],{"class":44,"line":268},[42,9018,800],{"class":52},[42,9020,9021],{"class":44,"line":273},[42,9022,885],{"class":52},[42,9024,9025,9027],{"class":44,"line":287},[42,9026,424],{"class":48},[42,9028,1450],{"class":52},[42,9030,9031],{"class":44,"line":292},[42,9032,141],{"class":52},[15,9034,1457],{},[32,9036,9037],{"className":34,"code":1460,"language":36,"meta":37,"style":37},[39,9038,9039,9057,9069,9081,9085,9091,9095,9099,9111,9143],{"__ignoreMap":37},[42,9040,9041,9043,9045,9047,9049,9051,9053,9055],{"class":44,"line":45},[42,9042,604],{"class":48},[42,9044,1469],{"class":66},[42,9046,610],{"class":48},[42,9048,758],{"class":52},[42,9050,1476],{"class":73},[42,9052,404],{"class":52},[42,9054,969],{"class":48},[42,9056,730],{"class":52},[42,9058,9059,9061,9063,9065,9067],{"class":44,"line":60},[42,9060,391],{"class":48},[42,9062,1386],{"class":52},[42,9064,1389],{"class":48},[42,9066,1392],{"class":613},[42,9068,83],{"class":52},[42,9070,9071,9073,9075,9077,9079],{"class":44,"line":86},[42,9072,824],{"class":48},[42,9074,1405],{"class":52},[42,9076,413],{"class":48},[42,9078,1410],{"class":400},[42,9080,419],{"class":52},[42,9082,9083],{"class":44,"line":92},[42,9084,885],{"class":52},[42,9086,9087,9089],{"class":44,"line":188},[42,9088,424],{"class":48},[42,9090,1435],{"class":52},[42,9092,9093],{"class":44,"line":194},[42,9094,980],{"class":52},[42,9096,9097],{"class":44,"line":249},[42,9098,301],{"emptyLinePlaceholder":300},[42,9100,9101,9103,9105,9107,9109],{"class":44,"line":263},[42,9102,63],{"class":48},[42,9104,1308],{"class":66},[42,9106,70],{"class":52},[42,9108,1313],{"class":73},[42,9110,83],{"class":52},[42,9112,9113,9115,9117,9119,9121,9123,9125,9127,9129,9131,9133,9135,9137,9139,9141],{"class":44,"line":268},[42,9114,424],{"class":48},[42,9116,1356],{"class":52},[42,9118,1545],{"class":66},[42,9120,1548],{"class":52},[42,9122,1551],{"class":73},[42,9124,77],{"class":52},[42,9126,1476],{"class":73},[42,9128,404],{"class":52},[42,9130,969],{"class":48},[42,9132,1562],{"class":52},[42,9134,1091],{"class":48},[42,9136,1469],{"class":66},[42,9138,1569],{"class":52},[42,9140,1278],{"class":400},[42,9142,795],{"class":52},[42,9144,9145],{"class":44,"line":273},[42,9146,141],{"class":52},[15,9148,1580],{},[10,9150,1584],{"id":1583},[15,9152,1587],{},[15,9154,1590],{},[1592,9156,1594],{},{"title":37,"searchDepth":60,"depth":60,"links":9158},[9159,9160,9161,9162,9163,9164,9165,9166,9167,9168],{"id":12,"depth":60,"text":13},{"id":26,"depth":60,"text":27},{"id":147,"depth":60,"text":148},{"id":358,"depth":60,"text":359},{"id":554,"depth":60,"text":555},{"id":639,"depth":60,"text":640},{"id":892,"depth":60,"text":893},{"id":1047,"depth":60,"text":1048},{"id":1292,"depth":60,"text":1293},{"id":1583,"depth":60,"text":1584},{},{"title":5,"description":1609},[1617,1618,1619,1620],{"id":9173,"title":9174,"body":9175,"category":2255,"date":9263,"description":9264,"draft":1610,"extension":1611,"meta":9265,"navigation":300,"path":9266,"seo":9267,"stem":9268,"tags":9269,"translationKey":9274,"videoId":1622,"__hash__":9275},"blog_en\u002Fblog\u002Fwhy-javascript-framework-wars-matter-in-2025.md","Why the JavaScript framework wars still matter: Vue, React and Angular",{"type":7,"value":9176,"toc":9255},[9177,9181,9184,9187,9191,9194,9197,9200,9203,9207,9210,9213,9216,9219,9223,9226,9229,9232,9235,9239,9242,9245,9249,9252],[10,9178,9180],{"id":9179},"the-argument-that-never-ends-or-does-it","The argument that never ends, or does it",[15,9182,9183],{},"Framework wars get every JavaScript developer's pulse up, or at least mildly irritated. Vue against React against Angular, every year a new contender and a fresh round of arguments about which one wins. And yet the same three are still standing. It's fair to ask whether any of it matters anymore, and whether you can just pick one and get on with your life.",[15,9185,9186],{},"The core ideas do stay put, but the ground keeps moving. Performance work, new features and where the community's attention goes all affect how a project ages. Picking a framework isn't about what's fashionable, it's about what stays sustainable and pleasant to work in. Here's where each one stands.",[10,9188,9190],{"id":9189},"react-the-component-king","React: the component king",[15,9192,9193],{},"React either started this or at least popularised the component approach most of us now use. It came out of Facebook, now Meta, and it has been dominant for years.",[15,9195,9196],{},"Its biggest advantage is the ecosystem. If you hit a problem, someone has already hit it, and there's an answer or a library waiting. React is also usually described as a library rather than a framework, which means freedom: you choose your router, your state management, your build setup. Experienced teams who want to shape their own stack tend to like that. The Virtual DOM keeps UI updates efficient by minimising direct DOM manipulation, and hooks made functional components far cleaner to reason about than the class components they replaced.",[15,9198,9199],{},"The cost of that freedom is decision fatigue. The number of choices can overwhelm someone new. React also moves quickly, and keeping up with current practice, libraries and deprecations takes real time.",[15,9201,9202],{},"It suits teams with experienced developers who value flexibility and want a large ecosystem behind them. For complex, highly interactive single-page applications it's still a strong default.",[10,9204,9206],{"id":9205},"vue-approachable-and-progressive","Vue: approachable and progressive",[15,9208,9209],{},"Vue is known for being approachable and for its syntax, and it has grown its share steadily. It sits between flexibility and structure rather than committing fully to either.",[15,9211,9212],{},"Ease of learning is where it stands out. If you come from HTML, CSS and some JavaScript, single-file components and Vue's API feel natural, and it's usually described as having the gentlest learning curve of the three. It's also progressively adoptable: you can drop it into an existing project piece by piece or build a full application with it. The documentation is a genuine high point, clear and well organised. Vue is lightweight and its reactivity system is well optimised. With Pinia as the recommended state management solution, that part of the stack is settled too.",[15,9214,9215],{},"The ecosystem, while solid and growing, is still smaller than React's, so for niche cases you may find fewer third-party libraries. A related practical point: some resources and community discussions happen outside English, which occasionally means hunting for a translation.",[15,9217,9218],{},"It suits developers who want something quick to pick up with a good day-to-day experience, and it scales from small projects to large applications.",[10,9220,9222],{"id":9221},"angular-structured-and-opinionated","Angular: structured and opinionated",[15,9224,9225],{},"Angular, backed by Google, is a full framework. Choosing it means adopting an entire ecosystem along with strong opinions about how applications should be built.",[15,9227,9228],{},"Everything is included: routing, state management, form handling, an HTTP client. Less time choosing libraries, more time writing code. It's built on TypeScript from the ground up, so type checking is enforced rather than optional, which pays off in large applications through fewer runtime errors and better tooling. The CLI is genuinely powerful for scaffolding projects, generating components and services, running tests and building. Google's backing gives a sense of stability and a visible roadmap. Because it's opinionated, Angular projects tend to look alike, which helps larger teams and long-term maintenance.",[15,9230,9231],{},"It also has the steepest learning curve of the three. Modules, services, dependency injection, observables through RxJS and decorators are a lot to absorb at once. Projects carry more boilerplate than the equivalent in React or Vue, and deviating from the prescribed way of doing things is harder.",[15,9233,9234],{},"It suits large applications and teams that put structure, maintainability and type safety first.",[10,9236,9238],{"id":9237},"so-which-one-wins","So which one wins",[15,9240,9241],{},"Nothing, and that's the honest answer. Each has matured around its own strengths while borrowing from the others. The right one depends on your project, your team and what they already know.",[15,9243,9244],{},"Choose React for a huge ecosystem and flexibility, with a team comfortable making architectural decisions and tracking rapid change. Choose Vue for an easy start, a good developer experience and a balance between structure and freedom. Choose Angular for large applications where a prescribed structure, type checking and a comprehensive CLI are worth the learning curve.",[10,9246,9248],{"id":9247},"what-actually-decides-it","What actually decides it",[15,9250,9251],{},"The framework wars are less about victory than positioning. All three are capable. What decides the outcome of your project is how productive the tool makes your team, whether it performs well for your users, whether the code can still be understood in a few years, whether support and libraries exist when you need them, and what your developers already know. Learning a new framework has a cost, and that cost is part of the decision.",[15,9253,9254],{},"The ecosystem is richer than it has ever been. Picking the right tool for the job beats winning the argument.",{"title":37,"searchDepth":60,"depth":60,"links":9256},[9257,9258,9259,9260,9261,9262],{"id":9179,"depth":60,"text":9180},{"id":9189,"depth":60,"text":9190},{"id":9205,"depth":60,"text":9206},{"id":9221,"depth":60,"text":9222},{"id":9237,"depth":60,"text":9238},{"id":9247,"depth":60,"text":9248},"2025-09-17","Where the three big frameworks stand today, what each is good at, and how to pick one for your next project.",{},"\u002Fblog\u002Fwhy-javascript-framework-wars-matter-in-2025",{"title":9174,"description":9264},"blog\u002Fwhy-javascript-framework-wars-matter-in-2025",[36,9270,2722,9271,9272,9273],"frameworks","react","angular","frontend development","why-javascript-framework-wars-matter-in-2025","wJ_g_9hg-XGcMf4JXzX-uTj47J75bmSyFmcwPqnMUYk",1786529717364]