[{"data":1,"prerenderedAt":2864},["ShallowReactive",2],{"blog-posts":3},[4,335,751,1075],{"id":5,"title":6,"body":7,"date":323,"description":324,"extension":325,"meta":326,"navigation":327,"path":328,"seo":329,"stem":330,"tags":331,"__hash__":334},"blog\u002Fblog\u002Fmodular-frontend-architecture.md","A future-proof, plug-and-play folder structure for your frontend apps",{"type":8,"value":9,"toc":311},"minimark",[10,14,17,22,25,61,64,68,71,74,84,87,91,98,101,115,118,121,147,158,162,165,171,175,186,192,201,205,211,215,218,269,273,276,282,288,294,298,301],[11,12,13],"p",{},"Every frontend framework ships with roughly the same default structure: all pages in one folder, all stores in another, all components in a third. It works great for a demo. Then the app grows, the team grows, and suddenly finding everything related to a single feature means hunting through five different directories.",[11,15,16],{},"I've spent years working in codebases organized this way, and I've watched the same problems appear every single time. In this post I'll show you the structure I use instead: a modular, plug-and-play architecture inspired by Domain-Driven Design. It has scaled well on every project where I've applied it.",[18,19,21],"h2",{"id":20},"why-the-default-structure-breaks-down","Why the default structure breaks down",[11,23,24],{},"The \"organize by file type\" approach scatters related logic across the entire repository. Three problems show up as the app scales:",[26,27,28,49,55],"ul",{},[29,30,31,35,36,40,41,44,45,48],"li",{},[32,33,34],"strong",{},"Constant context switching."," The user profile page lives in ",[37,38,39],"code",{},"pages\u002F",", its store in ",[37,42,43],{},"stores\u002F",", its components in ",[37,46,47],{},"components\u002F",", its API calls somewhere else entirely. Working on one feature means keeping five folder locations in your head.",[29,50,51,54],{},[32,52,53],{},"Zombie code."," Removing a feature is risky business. You delete the page, but was that store used anywhere else? What about those three components? Nobody is ever quite sure, so dead code accumulates forever.",[29,56,57,60],{},[32,58,59],{},"Code duplication."," When related files are disjointed, it's hard to know what already exists. Developers recreate logic that's already written because they simply couldn't find it.",[11,62,63],{},"None of these are developer discipline problems. They're structural problems, and no amount of code review fixes a structure that works against you.",[18,65,67],{"id":66},"the-plug-and-play-idea","The plug-and-play idea",[11,69,70],{},"The fix is strict feature encapsulation: everything related to a feature lives in one co-located directory, called a module. Need a new feature? Drop in a new module folder. Removing one? Delete the folder. No leftovers, no archaeology, no \"is this still used somewhere?\" anxiety.",[11,72,73],{},"Here's the full structure:",[75,76,81],"pre",{"className":77,"code":79,"language":80},[78],"language-text","src\u002F\n├── api\u002F         # Typed backend integrations (Zod schemas, API calls)\n├── ui\u002F          # Design system bridge (base UI wrapper components)\n├── shared\u002F      # Globally reusable code\n│   ├── cmp\u002F       # reusable components\n│   ├── use\u002F       # reusable composables\n│   ├── utils\u002F     # reusable utilities\n│   └── assets\u002F    # reusable assets\n├── modules\u002F     # Feature-based, self-contained business logic\n│   └── user\u002F\n│       ├── __tests__\u002F\n│       ├── components\u002F\n│       ├── stores\u002F\n│       ├── api.ts\n│       ├── constants.ts\n│       ├── router.ts\n│       └── User.vue\n├── css\u002F         # Global styles and design tokens\n├── plugins\u002F     # Third-party package initialization (Pinia, Router, etc.)\n├── stores\u002F      # Global state management\n├── types\u002F       # Global TypeScript definitions\n└── constants\u002F   # Global constants\n","text",[37,82,79],{"__ignoreMap":83},"",[11,85,86],{},"Let's go through the pieces that matter most.",[18,88,90],{"id":89},"the-modules-directory","The modules\u002F directory",[11,92,93,94,97],{},"This is the heart of the application. Each folder inside ",[37,95,96],{},"modules\u002F"," is one feature, fully self-contained: its components, stores, tests, styles, constants, and routes all live together.",[11,99,100],{},"Only two files are actually required to bootstrap a module:",[26,102,103,109],{},[29,104,105,108],{},[37,106,107],{},"router.ts",": the routing needs to know the module exists.",[29,110,111,114],{},[37,112,113],{},"Page.vue",": a route needs a component to render.",[11,116,117],{},"Everything else is optional. Inside a module, the team has complete freedom to organize files however best serves that specific feature. The macro-architecture is strict; the micro-architecture is yours.",[11,119,120],{},"What you get in return:",[26,122,123,129,135,141],{},[29,124,125,128],{},[32,126,127],{},"Discoverability."," Debugging a feature? Everything is in one folder. Following the flow of the app becomes trivial.",[29,130,131,134],{},[32,132,133],{},"Clean deletions."," Deprecating a feature means deleting a folder. It's structurally hard to leave dead code behind.",[29,136,137,140],{},[32,138,139],{},"Aggressive lazy-loading."," Since a module co-locates its logic, images, and SVGs, you can lazy-load the entire thing as one chunk and keep the initial bundle small.",[29,142,143,146],{},[32,144,145],{},"An isolated playground."," You can maintain a barebones shell app with only the common functionality, plug a single module into it, and develop that feature in total isolation. When it's done, plug it back into the main app.",[11,148,149,150,153,154,157],{},"One rule keeps this whole system honest: ",[32,151,152],{},"a module never imports from another module."," If two modules need the same thing, that thing gets extracted to ",[37,155,156],{},"shared\u002F",". The moment you allow cross-module imports, you're back to spaghetti with extra steps.",[18,159,161],{"id":160},"the-api-directory-your-backend-bridge","The api\u002F directory: your backend bridge",[11,163,164],{},"All reusable API calls live here, fully typed and validated at runtime with Zod. TypeScript types vanish at runtime, so without validation the backend is one deploy away from silently breaking your UI. Zod validation ensures the data you receive is actually shaped like the data you expected.",[11,166,167,170],{},[32,168,169],{},"The best approach?"," Backend and frontend teams establish a single source of truth by adopting a design-first approach with OpenAPI. Whenever the backend updates an endpoint, response, or query parameter, the frontend can instantly regenerate fully-typed API clients with a single command. This completely eliminates the guesswork of integration, protects us from silent breaking changes, and ensures that data structures match perfectly across the stack.",[18,172,174],{"id":173},"the-ui-directory-your-design-system-bridge","The ui\u002F directory: your design system bridge",[11,176,177,178,181,182,185],{},"Just as ",[37,179,180],{},"api\u002F"," bridges the backend, ",[37,183,184],{},"ui\u002F"," is the single bridge to your design system. Every base element from the design (buttons, inputs, modals, typography) is built as a component here.",[11,187,188,189,191],{},"This connects to a rule of mine: never use third-party UI library components directly in business logic. The wrappers live in ",[37,190,184],{},". When the underlying library changes, you touch one folder.",[11,193,194,195,197,198,200],{},"There's a longer-term payoff here too. ",[37,196,184],{}," maps to the design system, ",[37,199,180],{}," maps to the backend. Because both are strictly isolated, either can be extracted into a standalone package later (say the company suddenly needs to share UI or API logic with another app) without breaking changes rippling through the codebase.",[18,202,204],{"id":203},"the-shared-directory","The shared\u002F directory",[11,206,207,208,210],{},"Cross-cutting code that multiple modules need: common components, composables, utilities, assets. Don't over-invest here upfront. Let duplication appear inside modules first, and extract to ",[37,209,156],{}," when a real pattern emerges. Premature extraction creates abstractions nobody asked for.",[18,212,214],{"id":213},"rules-that-keep-the-architecture-healthy","Rules that keep the architecture healthy",[11,216,217],{},"Structure alone isn't enough. A few rules I enforce on every project:",[26,219,220,234,248,254,260],{},[29,221,222,225,226,229,230,233],{},[32,223,224],{},"No magic strings."," ",[37,227,228],{},"const moduleName = 'users'"," instead of scattering ",[37,231,232],{},"'users'"," around the codebase.",[29,235,236,239,240,243,244,247],{},[32,237,238],{},"Name-based routing only."," Navigate via route names, never hardcoded URL paths. Paths change; names survive. Nested route names should reflect their hierarchy (",[37,241,242],{},"user.profile",", not ",[37,245,246],{},"profile","), which makes debugging navigation issues much easier.",[29,249,250,253],{},[32,251,252],{},"Every API request and response is typed."," Backend data is the fuel of the frontend. Untyped fuel blows up engines.",[29,255,256,259],{},[32,257,258],{},"Every bug fix ships with a test."," The test proves the bug is dead and guarantees it never quietly returns.",[29,261,262,268],{},[32,263,264,265,267],{},"Document every ",[37,266,184],{}," component."," The design system bridge only works if people can discover what already exists.",[18,270,272],{"id":271},"what-about-monorepos-and-micro-frontends","What about monorepos and micro-frontends?",[11,274,275],{},"Since folder structure inevitably leads to the repository question, here is my short version.",[11,277,278,281],{},[32,279,280],{},"Micro-frontends: almost certainly not."," Splitting a frontend across multiple isolated repositories fractures the developer experience, breeds duplication, and makes enforcing shared standards nearly impossible. Unless you're a huge enterprise with deeply siloed teams and dedicated DevOps support, the overhead will eat you alive.",[11,283,284,287],{},[32,285,286],{},"Monorepos: sometimes."," If you're housing a frontend, a backend, and a shared component library together, workspace tooling solves real problems: atomic commits across the stack and no dependency hell.",[11,289,290,293],{},[32,291,292],{},"A single repository: usually the answer."," With the modular architecture above, a single repo scales remarkably far. If a part of it (like your UI library) outgrows the app, extract it into a package at that point. Start simple and add complexity only when the pain is real.",[18,295,297],{"id":296},"wrapping-up","Wrapping up",[11,299,300],{},"Thank you all for reading. Please share this post with your friends and colleagues if you found it useful.",[11,302,303,304],{},"If you haven't already, follow me on ",[305,306,310],"a",{"href":307,"rel":308},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Froland-doda\u002F",[309],"nofollow","Linkedin",{"title":83,"searchDepth":312,"depth":312,"links":313},2,[314,315,316,317,318,319,320,321,322],{"id":20,"depth":312,"text":21},{"id":66,"depth":312,"text":67},{"id":89,"depth":312,"text":90},{"id":160,"depth":312,"text":161},{"id":173,"depth":312,"text":174},{"id":203,"depth":312,"text":204},{"id":213,"depth":312,"text":214},{"id":271,"depth":312,"text":272},{"id":296,"depth":312,"text":297},"2026-09-03","Why the default \"components, pages, stores\" folder structure falls apart at scale, and the modular architecture I use instead.","md",{},true,"\u002Fblog\u002Fmodular-frontend-architecture",{"title":6,"description":324},"blog\u002Fmodular-frontend-architecture",[332,333],"architecture","vue","qTo1hnfIolJO8bibarS-Smz_DDQ0G0qA2osaUB6l_kQ",{"id":336,"title":337,"body":338,"date":742,"description":743,"extension":325,"meta":744,"navigation":327,"path":745,"seo":746,"stem":747,"tags":748,"__hash__":750},"blog\u002Fblog\u002Fagent-harness.md","What Is an Agent Harness?",{"type":8,"value":339,"toc":726},[340,343,346,353,357,360,363,390,401,404,408,411,416,433,450,457,461,468,486,490,493,497,500,532,536,539,553,557,560,562,566,658,660,664,667,681,688,690,694,701,708,711,713,716,719],[11,341,342],{},"When tools like Claude Code, Cursor, Aider, or Devin read your files, edit code across multiple directories, run terminal tests, fix errors on their own, and stop only when the task is done, it is tempting to believe the underlying Large Language Model (LLM) is doing everything.",[11,344,345],{},"In reality, the AI model is not running your computer.",[11,347,348,349,352],{},"Raw language models do not have built-in terminals, cannot execute commands directly, and do not maintain active processes. The software system wrapping around the model is called an ",[32,350,351],{},"Agent Harness",".",[18,354,356],{"id":355},"the-core-problem","The Core Problem",[11,358,359],{},"Modern foundation models are extraordinary reasoning engines. Given a prompt, they can understand intent, explain complex code, and plan multi-step solutions.",[11,361,362],{},"However, a raw LLM suffers from four fundamental constraints:",[364,365,366,372,378,384],"ol",{},[29,367,368,371],{},[32,369,370],{},"Passive & Text-Bound:"," An LLM only accepts tokens and produces tokens. It cannot open a file on your disk, inspect an active database, or trigger a command.",[29,373,374,377],{},[32,375,376],{},"Stateless & Amnesiac:"," Once an API call ends, the model resets. It does not natively retain long-term state across sessions without external context injection.",[29,379,380,383],{},[32,381,382],{},"Information Blindness:"," An LLM cannot digest a 50,000-file repository in one go. Dumping entire codebases into the prompt wastes tokens, causes context pollution, and degrades reasoning.",[29,385,386,389],{},[32,387,388],{},"No Real-World Feedback:"," The model can output code, but it cannot know if the code actually compiles, runs, or breaks until something tests it and returns the output.",[11,391,392,393,396,397,400],{},"If the model is the ",[32,394,395],{},"brain",", the harness is the ",[32,398,399],{},"body, operating system, and nervous system"," combined.",[402,403],"hr",{},[18,405,407],{"id":406},"how-an-agent-harness-solves-these-problems","How an Agent Harness Solves These Problems",[11,409,410],{},"There is no fixed blueprint for a harness. Different tools make different trade-offs: some run entirely in your local shell, others provision full containerized sandboxes, some ship sophisticated memory systems, others none at all.\nWhat follows are responsibilities that most well-built harnesses cover:",[412,413,415],"h3",{"id":414},"providing-tools","Providing Tools",[11,417,418,419,422,423,422,426,429,430,352],{},"Instead of asking the model to magically edit files, the harness exposes structured capabilities—increasingly standardized via protocols like MCP (Model Context Protocol) such as ",[37,420,421],{},"read_file",", ",[37,424,425],{},"write_file",[37,427,428],{},"grep_search",", or ",[37,431,432],{},"execute_command",[26,434,435,447],{},[29,436,437,438,446],{},"The LLM emits structured intent (e.g., ",[439,440,441,442,445],"em",{},"“Run ",[37,443,444],{},"npm test","”",").",[29,448,449],{},"The harness intercepts the command, runs it, and feeds the output back to the model.",[11,451,452,453,456],{},"One nuance: modern models are post-trained for tool calling, so deciding ",[439,454,455],{},"when"," to call a tool is partly model capability. The harness's job is to define, execute, and govern those calls—it decides what the model is allowed to do, not what it wants to do.",[412,458,460],{"id":459},"context-memory-management","Context & Memory Management",[11,462,463,464,467],{},"Feeding too much information degrades an LLM's attention. The harness handles ",[32,465,466],{},"progressive context discovery",":",[26,469,470,480,483],{},[29,471,472,473,476,477,446],{},"Loads project rules (",[37,474,475],{},"AGENTS.md"," or ",[37,478,479],{},"CLAUDE.md",[29,481,482],{},"Fetches only relevant file trees, function signatures, or search snippets when requested.",[29,484,485],{},"Compacts or summarizes historical turns—or offloads large tool outputs to the filesystem—so the context window never overflows during long-running tasks.",[412,487,489],{"id":488},"safe-execution-sandboxes","Safe Execution Sandboxes",[11,491,492],{},"The harness provisions isolated environments—such as containerized sandboxes, virtual filesystems, or shell subprocesses—where code can run without corrupting the host machine or leaking sensitive environment variables. This one varies a lot in practice: some tools run agent code in fully isolated containers, while others execute directly in your local shell and lean on git for rollback.",[412,494,496],{"id":495},"the-feedback-loop-reason-act-observe-adjust","The Feedback Loop (Reason -> Act -> Observe -> Adjust)",[11,498,499],{},"Autonomy requires closed-loop execution:",[364,501,502,508,517,526],{},[29,503,504,507],{},[32,505,506],{},"Reason:"," The model analyzes current context and plans the next atomic step.",[29,509,510,513,514,446],{},[32,511,512],{},"Act:"," The model requests a tool call (e.g., modify line 42 in ",[37,515,516],{},"auth.ts",[29,518,519,522,523,446],{},[32,520,521],{},"Observe:"," The harness executes the change, triggers test suites, and captures the error output (e.g., ",[37,524,525],{},"AssertionError: Token is null",[29,527,528,531],{},[32,529,530],{},"Adjust:"," The harness feeds the trace back to the model to reason and self-correct.",[412,533,535],{"id":534},"guardrails-governance","Guardrails & Governance",[11,537,538],{},"Autonomous agents can be unpredictable. The harness enforces deterministic execution states and permission tiers:",[26,540,541,547],{},[29,542,543,546],{},[32,544,545],{},"Safe Actions (Auto-approved):"," Reading a file, running a read-only query, running unit tests.",[29,548,549,552],{},[32,550,551],{},"Sensitive Actions (Require Human Approval):"," Deleting tables, overwriting critical files, publishing commits, or running destructive shell scripts.",[412,554,556],{"id":555},"observability-tracing","Observability & Tracing",[11,558,559],{},"When an agent works autonomously for 15 minutes, developers need transparency. The harness records every tool invocation, latency metric, token expenditure, and intermediate failure.",[402,561],{},[18,563,565],{"id":564},"comparing-the-model-vs-the-harness","Comparing the Model vs. The Harness",[567,568,569,585],"table",{},[570,571,572],"thead",{},[573,574,575,579,582],"tr",{},[576,577,578],"th",{},"Responsibility",[576,580,581],{},"The LLM",[576,583,584],{},"The Agent Harness",[586,587,588,602,619,632,645],"tbody",{},[573,589,590,596,599],{},[591,592,593],"td",{},[32,594,595],{},"Primary Job",[591,597,598],{},"Reasoning, synthesis, code generation",[591,600,601],{},"State management, file I\u002FO, tool execution",[573,603,604,609,612],{},[591,605,606],{},[32,607,608],{},"Context Handling",[591,610,611],{},"Interprets tokens provided to it",[591,613,614,615,618],{},"Decides ",[439,616,617],{},"what"," tokens to include or prune",[573,620,621,626,629],{},[591,622,623],{},[32,624,625],{},"Verification",[591,627,628],{},"Assumes output looks syntactically valid",[591,630,631],{},"Runs linters\u002Fcompilers to prove correctness",[573,633,634,639,642],{},[591,635,636],{},[32,637,638],{},"Safety",[591,640,641],{},"Tries to follow ethical prompt guidelines",[591,643,644],{},"Hard-blocks unauthorized filesystem\u002Fnetwork calls",[573,646,647,652,655],{},[591,648,649],{},[32,650,651],{},"Persistence",[591,653,654],{},"None (stateless API)",[591,656,657],{},"Retains scratchpads, todo lists, and task logs",[402,659],{},[18,661,663],{"id":662},"why-the-harness-is-where-true-leverage-lives","Why the Harness Is Where True Leverage Lives",[11,665,666],{},"Two engineering teams can build products using the exact same underlying LLM, yet achieve radically different outcomes:",[26,668,669,675],{},[29,670,671,674],{},[32,672,673],{},"Product A"," passes your prompt directly to the model and streams back a block of code.",[29,676,677,680],{},[32,678,679],{},"Product B"," uses a purpose-built harness that indexes your codebase, executes unit tests, applies linter back-pressure to catch syntax issues, and checks every change against your existing test suite before presenting a finished pull request.",[11,682,683,684,687],{},"The difference in performance is not model intelligence, ",[32,685,686],{},"it is harness engineering",". The same model can rank near the bottom or near the top of agentic benchmarks depending purely on the harness wrapped around it.",[402,689],{},[18,691,693],{"id":692},"harnesses-are-temporary-scaffolding","Harnesses Are Temporary Scaffolding",[11,695,696,697,700],{},"One caveat worth internalizing: a harness largely compensates for ",[439,698,699],{},"current"," model limitations, and those limitations shift with every model release. Capabilities that required hand-built pipelines a year ago may now be handled natively by the model.",[11,702,703,704,707],{},"There's also a feedback loop at play: model vendors now post-train models ",[439,705,706],{},"with their harnesses in the loop",", which is why a model often performs best inside the harness it was trained with and why harness and model design are increasingly co-evolving.",[11,709,710],{},"The practical takeaway: build harnesses to be modular and easy to simplify, so each component can be removed the moment the model absorbs its job.",[402,712],{},[18,714,715],{"id":296},"Wrapping Up",[11,717,718],{},"Thank you for reading! If you found this breakdown useful, please share it with your team and colleagues.",[11,720,721,722,352],{},"If you haven't already, feel free to connect with me on ",[305,723,725],{"href":307,"rel":724},[309],"LinkedIn",{"title":83,"searchDepth":312,"depth":312,"links":727},[728,729,738,739,740,741],{"id":355,"depth":312,"text":356},{"id":406,"depth":312,"text":407,"children":730},[731,733,734,735,736,737],{"id":414,"depth":732,"text":415},3,{"id":459,"depth":732,"text":460},{"id":488,"depth":732,"text":489},{"id":495,"depth":732,"text":496},{"id":534,"depth":732,"text":535},{"id":555,"depth":732,"text":556},{"id":564,"depth":312,"text":565},{"id":662,"depth":312,"text":663},{"id":692,"depth":312,"text":693},{"id":296,"depth":312,"text":715},"2026-09-01","Let's explore the concept of an agent harness, its purpose, and how it can be used to manage and control autonomous agents effectively.",{},"\u002Fblog\u002Fagent-harness",{"title":337,"description":743},"blog\u002Fagent-harness",[749],"ai","jScmXiHph2VlwQi61ic0vOfNEQNOMfxK_TSUBpJ8P_g",{"id":752,"title":753,"body":754,"date":1066,"description":1067,"extension":325,"meta":1068,"navigation":327,"path":1069,"seo":1070,"stem":1071,"tags":1072,"__hash__":1074},"blog\u002Fblog\u002Fbest-frontend-stack.md","The best frontend tech stack to choose in 2026",{"type":8,"value":755,"toc":1054},[756,759,766,769,773,776,779,783,786,789,815,818,822,825,836,840,843,846,867,871,874,884,887,901,905,908,911,915,918,921,925,928,931,951,958,962,1042,1045,1047,1049],[11,757,758],{},"Picking a stack for a new project is one of those decisions that looks small on day one and haunts you for years. I've inherited enough codebases to know the difference between a project where tooling gets out of your way and one where every task starts with a fight.",[11,760,761,762,765],{},"My guiding principle is simple: ",[32,763,764],{},"the easier it is for developers to contribute to a project, the better the final outcome."," When a codebase prioritizes Developer Experience, it reduces friction and cognitive load. People ship features instead of fighting their tools.",[11,767,768],{},"So here it is: the stack I'd reach for on any new project today, and the reasoning behind each pick. Opinionated? Absolutely. But every choice here comes from real projects, not benchmarks in a vacuum.",[18,770,772],{"id":771},"vite","Vite",[11,774,775],{},"I once worked on a legacy Webpack project that took 3-5 minutes to start. Every code edit took several seconds to show up in the browser. You'd change a line, alt-tab, wait, and slowly lose your train of thought. Multiply that by a whole team, every day, and you're burning serious money on waiting.",[11,777,778],{},"Vite ended that era. Near-instant server start, hot module replacement that actually feels hot, and a plugin API that's pleasant to work with. It's also become the foundation of the ecosystem: Vue, React, SvelteKit, and Solid all build on it, so betting on Vite isn't a bet at all anymore.",[18,780,782],{"id":781},"vue-3-pinia-vue-router-vueuse","Vue 3 (+ Pinia, Vue Router, VueUse)",[11,784,785],{},"Qwik, Solid, and Svelte all bring genuinely interesting ideas to the table. I keep an eye on them. But when I need to ship something that a team will maintain for years, Vue 3 is still my pick.",[11,787,788],{},"A few reasons:",[26,790,791,797,803,809],{},[29,792,793,796],{},[32,794,795],{},"It's small and fast."," The bundle size is lean and the reactivity system is heavily optimized, which matters when you're rendering dynamic, data-heavy UIs.",[29,798,799,802],{},[32,800,801],{},"The Composition API scales."," Logic composes into small, reusable functions instead of piling up in bloated components.",[29,804,805,808],{},[32,806,807],{},"You can adopt it gradually."," I've dropped Vue into legacy projects one page at a time. Try that with most frameworks.",[29,810,811,814],{},[32,812,813],{},"The ecosystem is cohesive."," Pinia for state, Vue Router for routing, VueUse for the hundred little composables you'd otherwise write yourself. These aren't random third-party packages. They're maintained by people at the core of the Vue ecosystem, and it shows in how well everything fits together.",[11,816,817],{},"That last point is underrated. Frameworks don't fail because of their rendering strategy but because the ecosystem around them is fragmented. Vue's isn't.",[18,819,821],{"id":820},"typescript","TypeScript",[11,823,824],{},"Almost everyone uses TypeScript these days but here are a few reasons why to choose it.\nThe research backs this up: one well-known study found that static types could have prevented around 38% of production bugs. But the day-to-day benefits are what sell it:",[26,826,827,830,833],{},[29,828,829],{},"Refactoring goes from terrifying to routine. Rename a field, follow the compiler errors, done.",[29,831,832],{},"Your editor becomes genuinely smart. Autocomplete that knows your data shapes is worth the setup cost alone.",[29,834,835],{},"You write fewer trivial unit tests, because the compiler already guarantees the structural stuff.",[18,837,839],{"id":838},"tailwind-css","Tailwind CSS",[11,841,842],{},"I resisted Tailwind for a while. Utility classes in markup felt wrong until I actually worked a project built with it and then went back to a legacy codebase with thousand of lines of cascading, half-dead CSS that nobody dared to delete.",[11,844,845],{},"Tailwind fixes the problems that actually hurt at scale:",[26,847,848,851,858,861,864],{},[29,849,850],{},"No unused CSS in production.",[29,852,853,854,857],{},"No duplication, no specificity wars, no ",[37,855,856],{},"!important"," everywhere.",[29,859,860],{},"Responsive design handled in the markup, where you can see it.",[29,862,863],{},"First-class editor tooling with autocomplete and linting.",[29,865,866],{},"Tailwind's config is a single source of truth for your design system. You can define your spacing scale, color palette, and typography in one place, and the entire team uses those values consistently.",[18,868,870],{"id":869},"vitest-playwright","Vitest + Playwright",[11,872,873],{},"Testing infrastructure should never be the bottleneck, and for years it was. Anyone who's fought Jest over ESM imports knows the specific flavor of despair I'm talking about.",[11,875,876,879,880,883],{},[32,877,878],{},"Vitest"," is the drop-in replacement Jest should have been: Vite-native, TypeScript out of the box, top-level await, watch mode, etc\n",[32,881,882],{},"Playwright"," covers the end-to-end and component testing side with full isolation, any browser, and remarkably little flakiness.",[11,885,886],{},"On strategy: I respect TDD, but I don't practice it dogmatically. It demands a level of discipline and time that most product teams can't sustain. What works for me is a pragmatic middle ground:",[26,888,889,895],{},[29,890,891,894],{},[32,892,893],{},"Mandatory coverage on critical paths",": authentication, authorization, the API layer, complex core components. No exceptions there.",[29,896,897,900],{},[32,898,899],{},"A test for every bug fix.",": When you patch a bug, you write a test that proves it's dead. That single habit prevents more regressions than any coverage percentage ever will.",[18,902,904],{"id":903},"automated-formatting-prettier-or-oxc","Automated formatting (Prettier or Oxc)",[11,906,907],{},"Nobody has ever won an argument about code style in a PR review, because there's nothing to win. Standardize a formatter, run it on save and in a pre-commit hook, and reclaim all that energy for actual engineering.",[11,909,910],{},"Prettier has been the default answer for years and still works great. That said, the ecosystem is clearly moving toward Rust-based tooling, and the Oxc formatter delivers the same guarantees at speeds that make Prettier look sleepy. Either is fine: the point is that formatting is a solved problem, and you should never review it manually again.",[18,912,914],{"id":913},"storybook","Storybook",[11,916,917],{},"Documentation is the part of the stack everyone skips, and then pays for during every onboarding. A living component catalog -- where anyone can browse the design system's typography, components, and usage rules -- is one of the highest-leverage things you can build.",[11,919,920],{},"Storybook earned a rough reputation in its early years: heavy config, React-first, fragile upgrades. That criticism is outdated. Modern Storybook has native Vite support, works smoothly with Vue, and its ecosystem and UI-testing capabilities are far ahead of the alternatives. Treat it as a first-class citizen of the codebase, not an afterthought.",[18,922,924],{"id":923},"the-ui-library-question","The UI library question",[11,926,927],{},"Should you build your own component library? Almost certainly not. Doing it properly means cross-browser testing, WCAG accessibility, keyboard navigation, RTL support, API design, documentation... it's a full-time job for a dedicated team, and for most companies it's a distraction from the actual product.",[11,929,930],{},"For Vue, here are three options are worth your attention:",[26,932,933,939,945],{},[29,934,935,938],{},[32,936,937],{},"PrimeVue",": 80+ components, the enterprise workhorse. If you need heavy data tables and charts, it's the pragmatic pick, and its unstyled mode plays well with Tailwind.",[29,940,941,944],{},[32,942,943],{},"Reka UI",": unstyled, accessible primitives in the spirit of Radix. Total visual freedom, but you're signing up for a lot of upfront styling work.",[29,946,947,950],{},[32,948,949],{},"Nuxt UI",": my recommendation. It sits on top of headless accessibility primitives, ships polished Tailwind-native defaults, and is backed by the core Nuxt team. You get the velocity of a styled library with the flexibility of a headless one.",[11,952,953,954,957],{},"One rule regardless of which you pick: ",[32,955,956],{},"never use library components directly in your business logic."," Wrap every one in your own thin component. If the library ever needs replacing, you change one file per component instead of hunting through the entire app. Cheap insurance, massive payoff.",[18,959,961],{"id":960},"the-stack-at-a-glance","The stack, at a glance",[567,963,964,974],{},[570,965,966],{},[573,967,968,971],{},[576,969,970],{},"Concern",[576,972,973],{},"Pick",[586,975,976,983,991,998,1005,1012,1019,1027,1034],{},[573,977,978,981],{},[591,979,980],{},"Build tool",[591,982,772],{},[573,984,985,988],{},[591,986,987],{},"Framework",[591,989,990],{},"Vue 3 + Pinia + Vue Router + VueUse",[573,992,993,996],{},[591,994,995],{},"Language",[591,997,821],{},[573,999,1000,1003],{},[591,1001,1002],{},"Styling",[591,1004,839],{},[573,1006,1007,1010],{},[591,1008,1009],{},"Unit tests",[591,1011,878],{},[573,1013,1014,1017],{},[591,1015,1016],{},"E2E \u002F component tests",[591,1018,882],{},[573,1020,1021,1024],{},[591,1022,1023],{},"Formatting",[591,1025,1026],{},"Prettier or Oxc",[573,1028,1029,1032],{},[591,1030,1031],{},"Component docs",[591,1033,914],{},[573,1035,1036,1039],{},[591,1037,1038],{},"UI library",[591,1040,1041],{},"Nuxt UI (wrapped)",[11,1043,1044],{},"Every tool here is fast, actively maintained, and built around respecting your time as a developer.",[18,1046,297],{"id":296},[11,1048,300],{},[11,1050,303,1051],{},[305,1052,310],{"href":307,"rel":1053},[309],{"title":83,"searchDepth":312,"depth":312,"links":1055},[1056,1057,1058,1059,1060,1061,1062,1063,1064,1065],{"id":771,"depth":312,"text":772},{"id":781,"depth":312,"text":782},{"id":820,"depth":312,"text":821},{"id":838,"depth":312,"text":839},{"id":869,"depth":312,"text":870},{"id":903,"depth":312,"text":904},{"id":913,"depth":312,"text":914},{"id":923,"depth":312,"text":924},{"id":960,"depth":312,"text":961},{"id":296,"depth":312,"text":297},"2026-08-27","The tech stack I'd reach for on any new project today, and the reasoning behind each pick.",{},"\u002Fblog\u002Fbest-frontend-stack",{"title":753,"description":1067},"blog\u002Fbest-frontend-stack",[333,820,1073],"tooling","4mvScDKLI-kkTt0HTDl8pqsnAdAhmXnmzCczbMTDqMQ",{"id":1076,"title":1077,"body":1078,"date":2856,"description":2857,"extension":325,"meta":2858,"navigation":327,"path":2859,"seo":2860,"stem":2861,"tags":2862,"__hash__":2863},"blog\u002Fblog\u002Fvue3-tips-tricks.md","7 Vue3 tips & tricks I guarantee you didn’t know",{"type":8,"value":1079,"toc":2846},[1080,1083,1087,1090,1122,1125,1564,1573,1577,1580,1596,1601,1727,1732,1824,1828,1831,2006,2016,2020,2023,2099,2102,2221,2227,2230,2235,2272,2277,2391,2394,2397,2401,2411,2425,2434,2438,2445,2516,2519,2540,2549,2552,2559,2562,2611,2615,2618,2621,2806,2826,2834,2836,2838,2843],[11,1081,1082],{},"Vue 3 has a lot of hidden gems. Here are seven tips and tricks that most developers don't know about — but definitely should.",[18,1084,1086],{"id":1085},"_1-vnode-hooks-v33","1. VNode hooks (v3.3+)",[11,1088,1089],{},"On any component or HTML tag, we can use some special hooks as event listeners. The hooks are:",[26,1091,1092,1097,1102,1107,1112,1117],{},[29,1093,1094],{},[37,1095,1096],{},"@vue:beforeMount",[29,1098,1099],{},[37,1100,1101],{},"@vue:mounted",[29,1103,1104],{},[37,1105,1106],{},"@vue:beforeUpdate",[29,1108,1109],{},[37,1110,1111],{},"@vue:updated",[29,1113,1114],{},[37,1115,1116],{},"@vue:beforeUnmount",[29,1118,1119],{},[37,1120,1121],{},"@vue:unmounted",[11,1123,1124],{},"Beaware that these hooks are not documented in the official Vue 3 docs which means that they are not officially supported. So use them at your own risk.",[75,1126,1129],{"className":1127,"code":1128,"language":333,"meta":83,"style":83},"language-vue shiki shiki-themes night-owl","\u003Cscript setup>\nimport { ref } from 'vue'\nimport Comp from '.\u002FComp.vue'\n\nconst count = ref(0)\nconst cmp = ref(true)\n\nfunction onMyComponentMounted() { console.log('component mounted') }\nfunction onMyComponentUnMounted() { console.log('component unmounted') }\nfunction divThatDisplaysCountWasUpdated() { console.log('div was updated') }\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Check the console in devtools\u003C\u002Fp>\n  \u003Chr \u002F>\n  \u003CComp v-if=\"cmp\" @vue:mounted=\"onMyComponentMounted\" @vue:unmounted=\"onMyComponentUnMounted\" \u002F>\n  \u003Cdiv @vue:updated=\"divThatDisplaysCountWasUpdated\">{{ count }}\u003C\u002Fdiv>\n\n  \u003Cbutton @click=\"count++\">+\u003C\u002Fbutton>\n  \u003Cbutton @click=\"cmp = !cmp\">Mount\u002FUnmount component\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[37,1130,1131,1151,1174,1190,1196,1223,1243,1248,1285,1314,1343,1353,1358,1368,1388,1398,1459,1490,1495,1526,1555],{"__ignoreMap":83},[1132,1133,1136,1140,1144,1148],"span",{"class":1134,"line":1135},"line",1,[1132,1137,1139],{"class":1138},"s4DUm","\u003C",[1132,1141,1143],{"class":1142},"sOKB6","script",[1132,1145,1147],{"class":1146},"stjdN"," setup",[1132,1149,1150],{"class":1138},">\n",[1132,1152,1153,1157,1161,1164,1168,1171],{"class":1134,"line":312},[1132,1154,1156],{"class":1155},"sVyDW","import",[1132,1158,1160],{"class":1159},"sJeqr"," { ref } ",[1132,1162,1163],{"class":1155},"from",[1132,1165,1167],{"class":1166},"stNC8"," '",[1132,1169,333],{"class":1170},"sNQAM",[1132,1172,1173],{"class":1166},"'\n",[1132,1175,1176,1178,1181,1183,1185,1188],{"class":1134,"line":732},[1132,1177,1156],{"class":1155},[1132,1179,1180],{"class":1159}," Comp ",[1132,1182,1163],{"class":1155},[1132,1184,1167],{"class":1166},[1132,1186,1187],{"class":1170},".\u002FComp.vue",[1132,1189,1173],{"class":1166},[1132,1191,1193],{"class":1134,"line":1192},4,[1132,1194,1195],{"emptyLinePlaceholder":327},"\n",[1132,1197,1199,1203,1207,1210,1213,1216,1220],{"class":1134,"line":1198},5,[1132,1200,1202],{"class":1201},"sJ14y","const",[1132,1204,1206],{"class":1205},"sywZT"," count",[1132,1208,1209],{"class":1201}," =",[1132,1211,1212],{"class":1205}," ref",[1132,1214,1215],{"class":1159},"(",[1132,1217,1219],{"class":1218},"sx098","0",[1132,1221,1222],{"class":1159},")\n",[1132,1224,1226,1228,1231,1233,1235,1237,1241],{"class":1134,"line":1225},6,[1132,1227,1202],{"class":1201},[1132,1229,1230],{"class":1205}," cmp",[1132,1232,1209],{"class":1201},[1132,1234,1212],{"class":1205},[1132,1236,1215],{"class":1159},[1132,1238,1240],{"class":1239},"sIpC3","true",[1132,1242,1222],{"class":1159},[1132,1244,1246],{"class":1134,"line":1245},7,[1132,1247,1195],{"emptyLinePlaceholder":327},[1132,1249,1251,1254,1257,1260,1263,1267,1269,1272,1274,1277,1280,1282],{"class":1134,"line":1250},8,[1132,1252,1253],{"class":1201},"function",[1132,1255,1256],{"class":1205}," onMyComponentMounted",[1132,1258,1259],{"class":1166},"()",[1132,1261,1262],{"class":1159}," { ",[1132,1264,1266],{"class":1265},"slldn","console",[1132,1268,352],{"class":1155},[1132,1270,1271],{"class":1205},"log",[1132,1273,1215],{"class":1159},[1132,1275,1276],{"class":1166},"'",[1132,1278,1279],{"class":1170},"component mounted",[1132,1281,1276],{"class":1166},[1132,1283,1284],{"class":1159},") }\n",[1132,1286,1288,1290,1293,1295,1297,1299,1301,1303,1305,1307,1310,1312],{"class":1134,"line":1287},9,[1132,1289,1253],{"class":1201},[1132,1291,1292],{"class":1205}," onMyComponentUnMounted",[1132,1294,1259],{"class":1166},[1132,1296,1262],{"class":1159},[1132,1298,1266],{"class":1265},[1132,1300,352],{"class":1155},[1132,1302,1271],{"class":1205},[1132,1304,1215],{"class":1159},[1132,1306,1276],{"class":1166},[1132,1308,1309],{"class":1170},"component unmounted",[1132,1311,1276],{"class":1166},[1132,1313,1284],{"class":1159},[1132,1315,1317,1319,1322,1324,1326,1328,1330,1332,1334,1336,1339,1341],{"class":1134,"line":1316},10,[1132,1318,1253],{"class":1201},[1132,1320,1321],{"class":1205}," divThatDisplaysCountWasUpdated",[1132,1323,1259],{"class":1166},[1132,1325,1262],{"class":1159},[1132,1327,1266],{"class":1265},[1132,1329,352],{"class":1155},[1132,1331,1271],{"class":1205},[1132,1333,1215],{"class":1159},[1132,1335,1276],{"class":1166},[1132,1337,1338],{"class":1170},"div was updated",[1132,1340,1276],{"class":1166},[1132,1342,1284],{"class":1159},[1132,1344,1346,1349,1351],{"class":1134,"line":1345},11,[1132,1347,1348],{"class":1138},"\u003C\u002F",[1132,1350,1143],{"class":1142},[1132,1352,1150],{"class":1138},[1132,1354,1356],{"class":1134,"line":1355},12,[1132,1357,1195],{"emptyLinePlaceholder":327},[1132,1359,1361,1363,1366],{"class":1134,"line":1360},13,[1132,1362,1139],{"class":1138},[1132,1364,1365],{"class":1142},"template",[1132,1367,1150],{"class":1138},[1132,1369,1371,1374,1376,1379,1382,1384,1386],{"class":1134,"line":1370},14,[1132,1372,1373],{"class":1138},"  \u003C",[1132,1375,11],{"class":1142},[1132,1377,1378],{"class":1138},">",[1132,1380,1381],{"class":1159},"Check the console in devtools",[1132,1383,1348],{"class":1138},[1132,1385,11],{"class":1142},[1132,1387,1150],{"class":1138},[1132,1389,1391,1393,1395],{"class":1134,"line":1390},15,[1132,1392,1373],{"class":1138},[1132,1394,402],{"class":1142},[1132,1396,1397],{"class":1138}," \u002F>\n",[1132,1399,1401,1403,1406,1409,1412,1415,1418,1420,1423,1425,1427,1430,1432,1434,1437,1439,1441,1443,1445,1448,1450,1452,1455,1457],{"class":1134,"line":1400},16,[1132,1402,1373],{"class":1138},[1132,1404,1405],{"class":1142},"Comp",[1132,1407,1408],{"class":1155}," v-if",[1132,1410,1411],{"class":1138},"=",[1132,1413,1414],{"class":1166},"\"",[1132,1416,1417],{"class":1159},"cmp",[1132,1419,1414],{"class":1166},[1132,1421,1422],{"class":1138}," @",[1132,1424,333],{"class":1146},[1132,1426,467],{"class":1138},[1132,1428,1429],{"class":1146},"mounted",[1132,1431,1411],{"class":1138},[1132,1433,1414],{"class":1166},[1132,1435,1436],{"class":1159},"onMyComponentMounted",[1132,1438,1414],{"class":1166},[1132,1440,1422],{"class":1138},[1132,1442,333],{"class":1146},[1132,1444,467],{"class":1138},[1132,1446,1447],{"class":1146},"unmounted",[1132,1449,1411],{"class":1138},[1132,1451,1414],{"class":1166},[1132,1453,1454],{"class":1159},"onMyComponentUnMounted",[1132,1456,1414],{"class":1166},[1132,1458,1397],{"class":1138},[1132,1460,1462,1464,1467,1470,1472,1474,1477,1479,1481,1484,1486,1488],{"class":1134,"line":1461},17,[1132,1463,1373],{"class":1138},[1132,1465,1466],{"class":1142},"div",[1132,1468,1469],{"class":1146}," @vue:updated",[1132,1471,1411],{"class":1138},[1132,1473,1414],{"class":1166},[1132,1475,1476],{"class":1170},"divThatDisplaysCountWasUpdated",[1132,1478,1414],{"class":1166},[1132,1480,1378],{"class":1138},[1132,1482,1483],{"class":1159},"{{ count }}",[1132,1485,1348],{"class":1138},[1132,1487,1466],{"class":1142},[1132,1489,1150],{"class":1138},[1132,1491,1493],{"class":1134,"line":1492},18,[1132,1494,1195],{"emptyLinePlaceholder":327},[1132,1496,1498,1500,1503,1506,1508,1510,1513,1515,1517,1520,1522,1524],{"class":1134,"line":1497},19,[1132,1499,1373],{"class":1138},[1132,1501,1502],{"class":1142},"button",[1132,1504,1505],{"class":1146}," @click",[1132,1507,1411],{"class":1138},[1132,1509,1414],{"class":1166},[1132,1511,1512],{"class":1170},"count++",[1132,1514,1414],{"class":1166},[1132,1516,1378],{"class":1138},[1132,1518,1519],{"class":1159},"+",[1132,1521,1348],{"class":1138},[1132,1523,1502],{"class":1142},[1132,1525,1150],{"class":1138},[1132,1527,1529,1531,1533,1535,1537,1539,1542,1544,1546,1549,1551,1553],{"class":1134,"line":1528},20,[1132,1530,1373],{"class":1138},[1132,1532,1502],{"class":1142},[1132,1534,1505],{"class":1146},[1132,1536,1411],{"class":1138},[1132,1538,1414],{"class":1166},[1132,1540,1541],{"class":1170},"cmp = !cmp",[1132,1543,1414],{"class":1166},[1132,1545,1378],{"class":1138},[1132,1547,1548],{"class":1159},"Mount\u002FUnmount component",[1132,1550,1348],{"class":1138},[1132,1552,1502],{"class":1142},[1132,1554,1150],{"class":1138},[1132,1556,1558,1560,1562],{"class":1134,"line":1557},21,[1132,1559,1348],{"class":1138},[1132,1561,1365],{"class":1142},[1132,1563,1150],{"class":1138},[11,1565,1566,1567,1569,1570,1572],{},"It should be noted that these hooks pass arguments to the callback function. They pass only one argument — the current VNode — except for ",[37,1568,1106],{}," and ",[37,1571,1111],{},", which pass two arguments: the current VNode and the previous VNode.",[18,1574,1576],{"id":1575},"_2-debugging-hooks","2. Debugging hooks",[11,1578,1579],{},"We all know the lifecycle hooks that Vue provides us. But did you know that Vue 3 gives us two hooks we can use for debugging purposes? They are:",[26,1581,1582,1589],{},[29,1583,1584],{},[305,1585,1588],{"href":1586,"rel":1587},"https:\u002F\u002Fvuejs.org\u002Fapi\u002Fcomposition-api-lifecycle.html#onrendertracked",[309],"onRenderTracked",[29,1590,1591],{},[305,1592,1595],{"href":1593,"rel":1594},"https:\u002F\u002Fvuejs.org\u002Fapi\u002Fcomposition-api-lifecycle.html#onrendertriggered",[309],"onRenderTriggered",[11,1597,1598,1600],{},[37,1599,1588],{}," gets called for every reactive dependency that has been tracked.",[75,1602,1604],{"className":1127,"code":1603,"language":333,"meta":83,"style":83},"\u003Cscript setup>\nimport { ref, onRenderTracked } from 'vue'\n\nconst count = ref(0)\nconst count2 = ref(0)\n\n\u002F\u002F It will be called for every reactive dependency that has been tracked\nonRenderTracked((event) => {\n  console.log(event)\n})\n\u003C\u002Fscript>\n",[37,1605,1606,1616,1631,1635,1651,1668,1672,1678,1699,1714,1719],{"__ignoreMap":83},[1132,1607,1608,1610,1612,1614],{"class":1134,"line":1135},[1132,1609,1139],{"class":1138},[1132,1611,1143],{"class":1142},[1132,1613,1147],{"class":1146},[1132,1615,1150],{"class":1138},[1132,1617,1618,1620,1623,1625,1627,1629],{"class":1134,"line":312},[1132,1619,1156],{"class":1155},[1132,1621,1622],{"class":1159}," { ref, onRenderTracked } ",[1132,1624,1163],{"class":1155},[1132,1626,1167],{"class":1166},[1132,1628,333],{"class":1170},[1132,1630,1173],{"class":1166},[1132,1632,1633],{"class":1134,"line":732},[1132,1634,1195],{"emptyLinePlaceholder":327},[1132,1636,1637,1639,1641,1643,1645,1647,1649],{"class":1134,"line":1192},[1132,1638,1202],{"class":1201},[1132,1640,1206],{"class":1205},[1132,1642,1209],{"class":1201},[1132,1644,1212],{"class":1205},[1132,1646,1215],{"class":1159},[1132,1648,1219],{"class":1218},[1132,1650,1222],{"class":1159},[1132,1652,1653,1655,1658,1660,1662,1664,1666],{"class":1134,"line":1198},[1132,1654,1202],{"class":1201},[1132,1656,1657],{"class":1205}," count2",[1132,1659,1209],{"class":1201},[1132,1661,1212],{"class":1205},[1132,1663,1215],{"class":1159},[1132,1665,1219],{"class":1218},[1132,1667,1222],{"class":1159},[1132,1669,1670],{"class":1134,"line":1225},[1132,1671,1195],{"emptyLinePlaceholder":327},[1132,1673,1674],{"class":1134,"line":1245},[1132,1675,1677],{"class":1676},"s8J1n","\u002F\u002F It will be called for every reactive dependency that has been tracked\n",[1132,1679,1680,1682,1684,1686,1690,1693,1696],{"class":1134,"line":1250},[1132,1681,1588],{"class":1205},[1132,1683,1215],{"class":1159},[1132,1685,1215],{"class":1166},[1132,1687,1689],{"class":1688},"sasy7","event",[1132,1691,1692],{"class":1166},")",[1132,1694,1695],{"class":1201}," =>",[1132,1697,1698],{"class":1159}," {\n",[1132,1700,1701,1704,1706,1708,1710,1712],{"class":1134,"line":1287},[1132,1702,1703],{"class":1265},"  console",[1132,1705,352],{"class":1155},[1132,1707,1271],{"class":1205},[1132,1709,1215],{"class":1159},[1132,1711,1689],{"class":1688},[1132,1713,1222],{"class":1159},[1132,1715,1716],{"class":1134,"line":1316},[1132,1717,1718],{"class":1159},"})\n",[1132,1720,1721,1723,1725],{"class":1134,"line":1345},[1132,1722,1348],{"class":1138},[1132,1724,1143],{"class":1142},[1132,1726,1150],{"class":1138},[11,1728,1729,1731],{},[37,1730,1595],{}," gets called when we trigger a reactivity update, or as the docs say: \"when a reactive dependency triggers the component's render effect to be re-run\".",[75,1733,1735],{"className":1127,"code":1734,"language":333,"meta":83,"style":83},"\u003Cscript setup>\nimport { ref, onRenderTriggered } from 'vue'\n\nconst count = ref(0)\n\n\u002F\u002F It will be called when we update the reactive dependency\nonRenderTriggered((event) => {\n  debugger\n})\n\u003C\u002Fscript>\n",[37,1736,1737,1747,1762,1766,1782,1786,1791,1807,1812,1816],{"__ignoreMap":83},[1132,1738,1739,1741,1743,1745],{"class":1134,"line":1135},[1132,1740,1139],{"class":1138},[1132,1742,1143],{"class":1142},[1132,1744,1147],{"class":1146},[1132,1746,1150],{"class":1138},[1132,1748,1749,1751,1754,1756,1758,1760],{"class":1134,"line":312},[1132,1750,1156],{"class":1155},[1132,1752,1753],{"class":1159}," { ref, onRenderTriggered } ",[1132,1755,1163],{"class":1155},[1132,1757,1167],{"class":1166},[1132,1759,333],{"class":1170},[1132,1761,1173],{"class":1166},[1132,1763,1764],{"class":1134,"line":732},[1132,1765,1195],{"emptyLinePlaceholder":327},[1132,1767,1768,1770,1772,1774,1776,1778,1780],{"class":1134,"line":1192},[1132,1769,1202],{"class":1201},[1132,1771,1206],{"class":1205},[1132,1773,1209],{"class":1201},[1132,1775,1212],{"class":1205},[1132,1777,1215],{"class":1159},[1132,1779,1219],{"class":1218},[1132,1781,1222],{"class":1159},[1132,1783,1784],{"class":1134,"line":1198},[1132,1785,1195],{"emptyLinePlaceholder":327},[1132,1787,1788],{"class":1134,"line":1225},[1132,1789,1790],{"class":1676},"\u002F\u002F It will be called when we update the reactive dependency\n",[1132,1792,1793,1795,1797,1799,1801,1803,1805],{"class":1134,"line":1245},[1132,1794,1595],{"class":1205},[1132,1796,1215],{"class":1159},[1132,1798,1215],{"class":1166},[1132,1800,1689],{"class":1688},[1132,1802,1692],{"class":1166},[1132,1804,1695],{"class":1201},[1132,1806,1698],{"class":1159},[1132,1808,1809],{"class":1134,"line":1250},[1132,1810,1811],{"class":1138},"  debugger\n",[1132,1813,1814],{"class":1134,"line":1287},[1132,1815,1718],{"class":1159},[1132,1817,1818,1820,1822],{"class":1134,"line":1316},[1132,1819,1348],{"class":1138},[1132,1821,1143],{"class":1142},[1132,1823,1150],{"class":1138},[18,1825,1827],{"id":1826},"_3-expose-slots-from-a-child-component","3. Expose slots from a child component",[11,1829,1830],{},"If you use a third-party component, chances are that you wrap its implementation in your own \"wrapper\" component. This is a good practice and a scalable solution, but in that way the slots of the third-party component get lost, and we should find a way to expose them to the parent component:",[75,1832,1834],{"className":1127,"code":1833,"language":333,"meta":83,"style":83},"\u003C!-- WrapperComponent.vue -->\n\u003Ctemplate>\n  \u003Cdiv class=\"wrapper-of-third-party-component\">\n    \u003CThirdPartyComponent v-bind=\"$attrs\">\n      \u003C!-- Expose the slots of the third-party component -->\n      \u003Ctemplate v-for=\"(_, name) in $slots\" #[name]=\"slotData\">\n        \u003Cslot :name=\"name\" v-bind=\"slotData || {}\">\u003C\u002Fslot>\n      \u003C\u002Ftemplate>\n    \u003C\u002FThirdPartyComponent>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[37,1835,1836,1841,1849,1869,1891,1896,1933,1971,1980,1989,1998],{"__ignoreMap":83},[1132,1837,1838],{"class":1134,"line":1135},[1132,1839,1840],{"class":1676},"\u003C!-- WrapperComponent.vue -->\n",[1132,1842,1843,1845,1847],{"class":1134,"line":312},[1132,1844,1139],{"class":1138},[1132,1846,1365],{"class":1142},[1132,1848,1150],{"class":1138},[1132,1850,1851,1853,1855,1858,1860,1862,1865,1867],{"class":1134,"line":732},[1132,1852,1373],{"class":1138},[1132,1854,1466],{"class":1142},[1132,1856,1857],{"class":1146}," class",[1132,1859,1411],{"class":1138},[1132,1861,1414],{"class":1166},[1132,1863,1864],{"class":1170},"wrapper-of-third-party-component",[1132,1866,1414],{"class":1166},[1132,1868,1150],{"class":1138},[1132,1870,1871,1874,1877,1880,1882,1884,1887,1889],{"class":1134,"line":1192},[1132,1872,1873],{"class":1138},"    \u003C",[1132,1875,1876],{"class":1142},"ThirdPartyComponent",[1132,1878,1879],{"class":1146}," v-bind",[1132,1881,1411],{"class":1138},[1132,1883,1414],{"class":1166},[1132,1885,1886],{"class":1159},"$attrs",[1132,1888,1414],{"class":1166},[1132,1890,1150],{"class":1138},[1132,1892,1893],{"class":1134,"line":1198},[1132,1894,1895],{"class":1676},"      \u003C!-- Expose the slots of the third-party component -->\n",[1132,1897,1898,1901,1903,1906,1908,1910,1913,1916,1919,1921,1924,1926,1929,1931],{"class":1134,"line":1225},[1132,1899,1900],{"class":1138},"      \u003C",[1132,1902,1365],{"class":1142},[1132,1904,1905],{"class":1155}," v-for",[1132,1907,1411],{"class":1159},[1132,1909,1414],{"class":1166},[1132,1911,1912],{"class":1159},"(_, name) ",[1132,1914,1915],{"class":1138},"in",[1132,1917,1918],{"class":1159}," $slots",[1132,1920,1414],{"class":1166},[1132,1922,1923],{"class":1159}," #[name]=",[1132,1925,1414],{"class":1166},[1132,1927,1928],{"class":1159},"slotData",[1132,1930,1414],{"class":1166},[1132,1932,1150],{"class":1138},[1132,1934,1935,1938,1941,1944,1946,1948,1951,1953,1955,1957,1959,1962,1964,1967,1969],{"class":1134,"line":1245},[1132,1936,1937],{"class":1138},"        \u003C",[1132,1939,1940],{"class":1142},"slot",[1132,1942,1943],{"class":1146}," :name",[1132,1945,1411],{"class":1138},[1132,1947,1414],{"class":1166},[1132,1949,1950],{"class":1170},"name",[1132,1952,1414],{"class":1166},[1132,1954,1879],{"class":1146},[1132,1956,1411],{"class":1138},[1132,1958,1414],{"class":1166},[1132,1960,1961],{"class":1170},"slotData || {}",[1132,1963,1414],{"class":1166},[1132,1965,1966],{"class":1138},">\u003C\u002F",[1132,1968,1940],{"class":1142},[1132,1970,1150],{"class":1138},[1132,1972,1973,1976,1978],{"class":1134,"line":1250},[1132,1974,1975],{"class":1138},"      \u003C\u002F",[1132,1977,1365],{"class":1142},[1132,1979,1150],{"class":1138},[1132,1981,1982,1985,1987],{"class":1134,"line":1287},[1132,1983,1984],{"class":1138},"    \u003C\u002F",[1132,1986,1876],{"class":1142},[1132,1988,1150],{"class":1138},[1132,1990,1991,1994,1996],{"class":1134,"line":1316},[1132,1992,1993],{"class":1138},"  \u003C\u002F",[1132,1995,1466],{"class":1142},[1132,1997,1150],{"class":1138},[1132,1999,2000,2002,2004],{"class":1134,"line":1345},[1132,2001,1348],{"class":1138},[1132,2003,1365],{"class":1142},[1132,2005,1150],{"class":1138},[11,2007,2008,2009,2012,2013,2015],{},"Now every component that uses ",[37,2010,2011],{},"WrapperComponent"," can use the slots of ",[37,2014,1876],{}," 🎉.",[18,2017,2019],{"id":2018},"_4-scoped-styles-and-multi-root-nodes-dont-work-well-together","4. Scoped styles and multi-root nodes don't work well together",[11,2021,2022],{},"In Vue 3 we can finally have more than one root node in a component. That is great, but personally I fall into a design limitation when doing that. Imagine we have a child component:",[75,2024,2026],{"className":1127,"code":2025,"language":333,"meta":83,"style":83},"\u003Ctemplate>\n  \u003Cp class=\"my-p\">First p\u003C\u002Fp>\n  \u003Cp class=\"my-p\">Second p\u003C\u002Fp>\n\u003C\u002Ftemplate>\n",[37,2027,2028,2036,2064,2091],{"__ignoreMap":83},[1132,2029,2030,2032,2034],{"class":1134,"line":1135},[1132,2031,1139],{"class":1138},[1132,2033,1365],{"class":1142},[1132,2035,1150],{"class":1138},[1132,2037,2038,2040,2042,2044,2046,2048,2051,2053,2055,2058,2060,2062],{"class":1134,"line":312},[1132,2039,1373],{"class":1138},[1132,2041,11],{"class":1142},[1132,2043,1857],{"class":1146},[1132,2045,1411],{"class":1138},[1132,2047,1414],{"class":1166},[1132,2049,2050],{"class":1170},"my-p",[1132,2052,1414],{"class":1166},[1132,2054,1378],{"class":1138},[1132,2056,2057],{"class":1159},"First p",[1132,2059,1348],{"class":1138},[1132,2061,11],{"class":1142},[1132,2063,1150],{"class":1138},[1132,2065,2066,2068,2070,2072,2074,2076,2078,2080,2082,2085,2087,2089],{"class":1134,"line":732},[1132,2067,1373],{"class":1138},[1132,2069,11],{"class":1142},[1132,2071,1857],{"class":1146},[1132,2073,1411],{"class":1138},[1132,2075,1414],{"class":1166},[1132,2077,2050],{"class":1170},[1132,2079,1414],{"class":1166},[1132,2081,1378],{"class":1138},[1132,2083,2084],{"class":1159},"Second p",[1132,2086,1348],{"class":1138},[1132,2088,11],{"class":1142},[1132,2090,1150],{"class":1138},[1132,2092,2093,2095,2097],{"class":1134,"line":1192},[1132,2094,1348],{"class":1138},[1132,2096,1365],{"class":1142},[1132,2098,1150],{"class":1138},[11,2100,2101],{},"And a parent component:",[75,2103,2105],{"className":1127,"code":2104,"language":333,"meta":83,"style":83},"\u003Ctemplate>\n  \u003Ch1>My awesome component\u003C\u002Fh1>\n  \u003CMyChildComponent \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n\u002F* There is no way to style the p tags of MyChildComponent *\u002F\n.my-p { color: red; }\n:deep(.my-p) { color: red; }\n\u003C\u002Fstyle>\n",[37,2106,2107,2115,2133,2142,2150,2154,2166,2171,2193,2213],{"__ignoreMap":83},[1132,2108,2109,2111,2113],{"class":1134,"line":1135},[1132,2110,1139],{"class":1138},[1132,2112,1365],{"class":1142},[1132,2114,1150],{"class":1138},[1132,2116,2117,2119,2122,2124,2127,2129,2131],{"class":1134,"line":312},[1132,2118,1373],{"class":1138},[1132,2120,2121],{"class":1142},"h1",[1132,2123,1378],{"class":1138},[1132,2125,2126],{"class":1159},"My awesome component",[1132,2128,1348],{"class":1138},[1132,2130,2121],{"class":1142},[1132,2132,1150],{"class":1138},[1132,2134,2135,2137,2140],{"class":1134,"line":732},[1132,2136,1373],{"class":1138},[1132,2138,2139],{"class":1142},"MyChildComponent",[1132,2141,1397],{"class":1138},[1132,2143,2144,2146,2148],{"class":1134,"line":1192},[1132,2145,1348],{"class":1138},[1132,2147,1365],{"class":1142},[1132,2149,1150],{"class":1138},[1132,2151,2152],{"class":1134,"line":1198},[1132,2153,1195],{"emptyLinePlaceholder":327},[1132,2155,2156,2158,2161,2164],{"class":1134,"line":1225},[1132,2157,1139],{"class":1138},[1132,2159,2160],{"class":1142},"style",[1132,2162,2163],{"class":1146}," scoped",[1132,2165,1150],{"class":1138},[1132,2167,2168],{"class":1134,"line":1245},[1132,2169,2170],{"class":1676},"\u002F* There is no way to style the p tags of MyChildComponent *\u002F\n",[1132,2172,2173,2176,2178,2180,2184,2187,2190],{"class":1134,"line":1250},[1132,2174,352],{"class":2175},"s34Dl",[1132,2177,2050],{"class":1146},[1132,2179,1262],{"class":1159},[1132,2181,2183],{"class":2182},"sz1_M","color",[1132,2185,2186],{"class":1159},": ",[1132,2188,2189],{"class":1138},"red",[1132,2191,2192],{"class":1159},"; }\n",[1132,2194,2195,2198,2200,2202,2205,2207,2209,2211],{"class":1134,"line":1287},[1132,2196,2197],{"class":1155},":deep(",[1132,2199,352],{"class":2175},[1132,2201,2050],{"class":1146},[1132,2203,2204],{"class":1159},") { ",[1132,2206,2183],{"class":2182},[1132,2208,2186],{"class":1159},[1132,2210,2189],{"class":1138},[1132,2212,2192],{"class":1159},[1132,2214,2215,2217,2219],{"class":1134,"line":1316},[1132,2216,1348],{"class":1138},[1132,2218,2160],{"class":1142},[1132,2220,1150],{"class":1138},[11,2222,2223,2224,2226],{},"There is no way from the scoped styling of the multi-root parent component to style the child component's ",[37,2225,11],{}," tags. In short: a multi-root component can't target a multi-root child component's styles with scoped styles.",[11,2228,2229],{},"The best way to fix that would be to wrap the parent or child component (or both) so we have only one root element. But if you absolutely need both to have multi-root nodes, you can:",[11,2231,2232],{},[32,2233,2234],{},"Use a non-scoped style",[75,2236,2238],{"className":1127,"code":2237,"language":333,"meta":83,"style":83},"\u003Cstyle>\n.my-p { color: red; }\n\u003C\u002Fstyle>\n",[37,2239,2240,2248,2264],{"__ignoreMap":83},[1132,2241,2242,2244,2246],{"class":1134,"line":1135},[1132,2243,1139],{"class":1138},[1132,2245,2160],{"class":1142},[1132,2247,1150],{"class":1138},[1132,2249,2250,2252,2254,2256,2258,2260,2262],{"class":1134,"line":312},[1132,2251,352],{"class":2175},[1132,2253,2050],{"class":1146},[1132,2255,1262],{"class":1159},[1132,2257,2183],{"class":2182},[1132,2259,2186],{"class":1159},[1132,2261,2189],{"class":1138},[1132,2263,2192],{"class":1159},[1132,2265,2266,2268,2270],{"class":1134,"line":732},[1132,2267,1348],{"class":1138},[1132,2269,2160],{"class":1142},[1132,2271,1150],{"class":1138},[11,2273,2274],{},[32,2275,2276],{},"Use CSS Modules",[75,2278,2280],{"className":1127,"code":2279,"language":333,"meta":83,"style":83},"\u003Ctemplate>\n  \u003Ch1>My awesome component\u003C\u002Fh1>\n  \u003CMyChildComponent :class=\"$style.trick\" \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cstyle module>\n.trick {\n  color: red;\n}\n\u003C\u002Fstyle>\n",[37,2281,2282,2290,2306,2335,2343,2347,2358,2366,2378,2383],{"__ignoreMap":83},[1132,2283,2284,2286,2288],{"class":1134,"line":1135},[1132,2285,1139],{"class":1138},[1132,2287,1365],{"class":1142},[1132,2289,1150],{"class":1138},[1132,2291,2292,2294,2296,2298,2300,2302,2304],{"class":1134,"line":312},[1132,2293,1373],{"class":1138},[1132,2295,2121],{"class":1142},[1132,2297,1378],{"class":1138},[1132,2299,2126],{"class":1159},[1132,2301,1348],{"class":1138},[1132,2303,2121],{"class":1142},[1132,2305,1150],{"class":1138},[1132,2307,2308,2310,2312,2315,2318,2320,2322,2325,2327,2331,2333],{"class":1134,"line":732},[1132,2309,1373],{"class":1138},[1132,2311,2139],{"class":1142},[1132,2313,2314],{"class":1138}," :",[1132,2316,2317],{"class":1146},"class",[1132,2319,1411],{"class":1138},[1132,2321,1414],{"class":1166},[1132,2323,2324],{"class":1159},"$style",[1132,2326,352],{"class":1155},[1132,2328,2330],{"class":2329},"sddte","trick",[1132,2332,1414],{"class":1166},[1132,2334,1397],{"class":1138},[1132,2336,2337,2339,2341],{"class":1134,"line":1192},[1132,2338,1348],{"class":1138},[1132,2340,1365],{"class":1142},[1132,2342,1150],{"class":1138},[1132,2344,2345],{"class":1134,"line":1198},[1132,2346,1195],{"emptyLinePlaceholder":327},[1132,2348,2349,2351,2353,2356],{"class":1134,"line":1225},[1132,2350,1139],{"class":1138},[1132,2352,2160],{"class":1142},[1132,2354,2355],{"class":1146}," module",[1132,2357,1150],{"class":1138},[1132,2359,2360,2362,2364],{"class":1134,"line":1245},[1132,2361,352],{"class":2175},[1132,2363,2330],{"class":1146},[1132,2365,1698],{"class":1159},[1132,2367,2368,2371,2373,2375],{"class":1134,"line":1250},[1132,2369,2370],{"class":2182},"  color",[1132,2372,2186],{"class":1159},[1132,2374,2189],{"class":1138},[1132,2376,2377],{"class":1159},";\n",[1132,2379,2380],{"class":1134,"line":1287},[1132,2381,2382],{"class":1159},"}\n",[1132,2384,2385,2387,2389],{"class":1134,"line":1316},[1132,2386,1348],{"class":1138},[1132,2388,2160],{"class":1142},[1132,2390,1150],{"class":1138},[11,2392,2393],{},"Since we are specifying a class here, the multi-root child component has to explicitly specify the attribute fallthrough behavior.",[11,2395,2396],{},"If you want my opinion: unless you absolutely need a multi-root node component, go with a single root node and don't deal with this design limitation at all.",[18,2398,2400],{"id":2399},"_5-be-careful-when-using-css-selectors","5. Be careful when using CSS selectors",[11,2402,2403,2406,2407,2410],{},[37,2404,2405],{},"#main-nav > li {}"," will be many times slower compared to ",[37,2408,2409],{},".my-li { color: red }",". From the docs:",[2412,2413,2414],"blockquote",{},[11,2415,2416,2417,2420,2421,2424],{},"Due to the way browsers render various CSS selectors, ",[37,2418,2419],{},"p { color: red }"," will be many times slower when scoped (i.e. when combined with an attribute selector). If you use classes or ids instead, such as in ",[37,2422,2423],{},".example { color: red }",", then you virtually eliminate that performance hit.",[11,2426,2427,2428,2433],{},"I highly recommend you read ",[305,2429,2432],{"href":2430,"rel":2431},"https:\u002F\u002Fcss-tricks.com\u002Fefficiently-rendering-css\u002F",[309],"Efficiently Rendering CSS"," if you want to dive deeper into this topic.",[18,2435,2437],{"id":2436},"_6-boolean-casting","6. Boolean casting",[11,2439,2440,2441,2444],{},"In Vue 2 or early versions of Vue 3, for props with ",[37,2442,2443],{},"Boolean"," types we had different behavior depending on the order:",[75,2446,2450],{"className":2447,"code":2448,"language":2449,"meta":83,"style":83},"language-js shiki shiki-themes night-owl","\u002F\u002F 1st case\nprops: {\n  hoverColor: [String, Boolean] \u002F\u002F \u003C- defaults to ''\n}\n\n\u002F\u002F 2nd case\nprops: {\n  hoverColor: [Boolean, String] \u002F\u002F \u003C- defaults to false\n}\n","js",[37,2451,2452,2457,2462,2480,2484,2488,2493,2497,2512],{"__ignoreMap":83},[1132,2453,2454],{"class":1134,"line":1135},[1132,2455,2456],{"class":1676},"\u002F\u002F 1st case\n",[1132,2458,2459],{"class":1134,"line":312},[1132,2460,2461],{"class":1159},"props: {\n",[1132,2463,2464,2467,2470,2472,2474,2477],{"class":1134,"line":732},[1132,2465,2466],{"class":1159},"  hoverColor: [",[1132,2468,2469],{"class":1688},"String",[1132,2471,422],{"class":1159},[1132,2473,2443],{"class":1688},[1132,2475,2476],{"class":1159},"] ",[1132,2478,2479],{"class":1676},"\u002F\u002F \u003C- defaults to ''\n",[1132,2481,2482],{"class":1134,"line":1192},[1132,2483,2382],{"class":1159},[1132,2485,2486],{"class":1134,"line":1198},[1132,2487,1195],{"emptyLinePlaceholder":327},[1132,2489,2490],{"class":1134,"line":1225},[1132,2491,2492],{"class":1676},"\u002F\u002F 2nd case\n",[1132,2494,2495],{"class":1134,"line":1245},[1132,2496,2461],{"class":1159},[1132,2498,2499,2501,2503,2505,2507,2509],{"class":1134,"line":1250},[1132,2500,2466],{"class":1159},[1132,2502,2443],{"class":1688},[1132,2504,422],{"class":1159},[1132,2506,2469],{"class":1688},[1132,2508,2476],{"class":1159},[1132,2510,2511],{"class":1676},"\u002F\u002F \u003C- defaults to false\n",[1132,2513,2514],{"class":1134,"line":1287},[1132,2515,2382],{"class":1159},[11,2517,2518],{},"Not only that, but if you pass the prop like this:",[75,2520,2522],{"className":1127,"code":2521,"language":333,"meta":83,"style":83},"\u003Cmy-component hover-color>\u003C\u002Fmy-component>\n",[37,2523,2524],{"__ignoreMap":83},[1132,2525,2526,2528,2531,2534,2536,2538],{"class":1134,"line":1135},[1132,2527,1139],{"class":1138},[1132,2529,2530],{"class":1142},"my-component",[1132,2532,2533],{"class":1146}," hover-color",[1132,2535,1966],{"class":1138},[1132,2537,2530],{"class":1142},[1132,2539,1150],{"class":1138},[11,2541,2542,2543,2546,2547,352],{},"In the first case it would be an empty string ",[37,2544,2545],{},"''",". In the second case, it would be ",[37,2548,1240],{},[11,2550,2551],{},"As you can see, this was a bit confusing and inconsistent. Fortunately, in Vue 3 we have a new behavior that is consistent and predictable:",[2412,2553,2554],{},[11,2555,2556,2558],{},[37,2557,2443],{}," behavior will apply regardless of type appearance order.",[11,2560,2561],{},"So:",[75,2563,2565],{"className":2447,"code":2564,"language":2449,"meta":83,"style":83},"hoverColor: [String, Boolean] \u002F\u002F \u003C- defaults to false\nhoverColor: [Boolean, String] \u002F\u002F \u003C- defaults to false\nhoverColor: [Boolean, Number] \u002F\u002F \u003C- defaults to false\n",[37,2566,2567,2582,2596],{"__ignoreMap":83},[1132,2568,2569,2572,2574,2576,2578,2580],{"class":1134,"line":1135},[1132,2570,2571],{"class":1159},"hoverColor: [",[1132,2573,2469],{"class":1688},[1132,2575,422],{"class":1159},[1132,2577,2443],{"class":1688},[1132,2579,2476],{"class":1159},[1132,2581,2511],{"class":1676},[1132,2583,2584,2586,2588,2590,2592,2594],{"class":1134,"line":312},[1132,2585,2571],{"class":1159},[1132,2587,2443],{"class":1688},[1132,2589,422],{"class":1159},[1132,2591,2469],{"class":1688},[1132,2593,2476],{"class":1159},[1132,2595,2511],{"class":1676},[1132,2597,2598,2600,2602,2604,2607,2609],{"class":1134,"line":732},[1132,2599,2571],{"class":1159},[1132,2601,2443],{"class":1688},[1132,2603,422],{"class":1159},[1132,2605,2606],{"class":1688},"Number",[1132,2608,2476],{"class":1159},[1132,2610,2511],{"class":1676},[18,2612,2614],{"id":2613},"_7-template-refs-with-v-for-order-is-not-guaranteed","7. Template refs with v-for — order is not guaranteed",[11,2616,2617],{},"Remember this one so you don't lose hours of debugging trying to figure out what is going on.",[11,2619,2620],{},"In the code below:",[75,2622,2624],{"className":1127,"code":2623,"language":333,"meta":83,"style":83},"\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst list = ref([1, 2, 3])\nconst itemRefs = useTemplateRef('items')\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cul>\n    \u003Cli v-for=\"item in list\" ref=\"items\" :key=\"item\">\n      {{ item }}\n    \u003C\u002Fli>\n  \u003C\u002Ful>\n\u003C\u002Ftemplate>\n",[37,2625,2626,2636,2650,2654,2685,2708,2716,2720,2728,2736,2777,2782,2790,2798],{"__ignoreMap":83},[1132,2627,2628,2630,2632,2634],{"class":1134,"line":1135},[1132,2629,1139],{"class":1138},[1132,2631,1143],{"class":1142},[1132,2633,1147],{"class":1146},[1132,2635,1150],{"class":1138},[1132,2637,2638,2640,2642,2644,2646,2648],{"class":1134,"line":312},[1132,2639,1156],{"class":1155},[1132,2641,1160],{"class":1159},[1132,2643,1163],{"class":1155},[1132,2645,1167],{"class":1166},[1132,2647,333],{"class":1170},[1132,2649,1173],{"class":1166},[1132,2651,2652],{"class":1134,"line":732},[1132,2653,1195],{"emptyLinePlaceholder":327},[1132,2655,2656,2658,2661,2663,2665,2668,2671,2674,2677,2679,2682],{"class":1134,"line":1192},[1132,2657,1202],{"class":1201},[1132,2659,2660],{"class":1205}," list",[1132,2662,1209],{"class":1201},[1132,2664,1212],{"class":1205},[1132,2666,2667],{"class":1159},"([",[1132,2669,2670],{"class":1218},"1",[1132,2672,2673],{"class":1159},",",[1132,2675,2676],{"class":1218}," 2",[1132,2678,2673],{"class":1159},[1132,2680,2681],{"class":1218}," 3",[1132,2683,2684],{"class":1159},"])\n",[1132,2686,2687,2689,2692,2694,2697,2699,2701,2704,2706],{"class":1134,"line":1198},[1132,2688,1202],{"class":1201},[1132,2690,2691],{"class":1205}," itemRefs",[1132,2693,1209],{"class":1201},[1132,2695,2696],{"class":1205}," useTemplateRef",[1132,2698,1215],{"class":1159},[1132,2700,1276],{"class":1166},[1132,2702,2703],{"class":1170},"items",[1132,2705,1276],{"class":1166},[1132,2707,1222],{"class":1159},[1132,2709,2710,2712,2714],{"class":1134,"line":1225},[1132,2711,1348],{"class":1138},[1132,2713,1143],{"class":1142},[1132,2715,1150],{"class":1138},[1132,2717,2718],{"class":1134,"line":1245},[1132,2719,1195],{"emptyLinePlaceholder":327},[1132,2721,2722,2724,2726],{"class":1134,"line":1250},[1132,2723,1139],{"class":1138},[1132,2725,1365],{"class":1142},[1132,2727,1150],{"class":1138},[1132,2729,2730,2732,2734],{"class":1134,"line":1287},[1132,2731,1373],{"class":1138},[1132,2733,26],{"class":1142},[1132,2735,1150],{"class":1138},[1132,2737,2738,2740,2742,2744,2746,2748,2751,2753,2755,2757,2759,2761,2763,2766,2768,2770,2773,2775],{"class":1134,"line":1316},[1132,2739,1873],{"class":1138},[1132,2741,29],{"class":1142},[1132,2743,1905],{"class":1146},[1132,2745,1411],{"class":1138},[1132,2747,1414],{"class":1166},[1132,2749,2750],{"class":1170},"item in list",[1132,2752,1414],{"class":1166},[1132,2754,1212],{"class":1146},[1132,2756,1411],{"class":1138},[1132,2758,1414],{"class":1166},[1132,2760,2703],{"class":1170},[1132,2762,1414],{"class":1166},[1132,2764,2765],{"class":1146}," :key",[1132,2767,1411],{"class":1138},[1132,2769,1414],{"class":1166},[1132,2771,2772],{"class":1170},"item",[1132,2774,1414],{"class":1166},[1132,2776,1150],{"class":1138},[1132,2778,2779],{"class":1134,"line":1345},[1132,2780,2781],{"class":1159},"      {{ item }}\n",[1132,2783,2784,2786,2788],{"class":1134,"line":1355},[1132,2785,1984],{"class":1138},[1132,2787,29],{"class":1142},[1132,2789,1150],{"class":1138},[1132,2791,2792,2794,2796],{"class":1134,"line":1360},[1132,2793,1993],{"class":1138},[1132,2795,26],{"class":1142},[1132,2797,1150],{"class":1138},[1132,2799,2800,2802,2804],{"class":1134,"line":1370},[1132,2801,1348],{"class":1138},[1132,2803,1365],{"class":1142},[1132,2805,1150],{"class":1138},[11,2807,2808,2809,2812,2813,2816,2817],{},"we are looping over the ",[37,2810,2811],{},"list"," array and creating the ",[37,2814,2815],{},"itemRefs"," array. ",[32,2818,2819,2820,2822,2823,2825],{},"The ",[37,2821,2815],{}," array is not guaranteed to have the same order as the ",[37,2824,2811],{}," array.",[11,2827,2828,2829,352],{},"If you want to find out more about this, you can read ",[305,2830,2833],{"href":2831,"rel":2832},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F4010",[309],"this issue",[18,2835,297],{"id":296},[11,2837,300],{},[11,2839,303,2840],{},[305,2841,310],{"href":307,"rel":2842},[309],[2160,2844,2845],{},"html pre.shiki code .s4DUm, html code.shiki .s4DUm{--shiki-default:#7FDBCA}html pre.shiki code .sOKB6, html code.shiki .sOKB6{--shiki-default:#CAECE6}html pre.shiki code .stjdN, html code.shiki .stjdN{--shiki-default:#C5E478;--shiki-default-font-style:italic}html pre.shiki code .sVyDW, html code.shiki .sVyDW{--shiki-default:#C792EA;--shiki-default-font-style:italic}html pre.shiki code .sJeqr, html code.shiki .sJeqr{--shiki-default:#D6DEEB}html pre.shiki code .stNC8, html code.shiki .stNC8{--shiki-default:#D9F5DD}html pre.shiki code .sNQAM, html code.shiki .sNQAM{--shiki-default:#ECC48D}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sywZT, html code.shiki .sywZT{--shiki-default:#82AAFF;--shiki-default-font-style:italic}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .sIpC3, html code.shiki .sIpC3{--shiki-default:#FF5874;--shiki-default-font-style:italic}html pre.shiki code .slldn, html code.shiki .slldn{--shiki-default:#7FDBCA;--shiki-default-font-style:italic}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 .s8J1n, html code.shiki .s8J1n{--shiki-default:#637777;--shiki-default-font-style:italic}html pre.shiki code .sasy7, html code.shiki .sasy7{--shiki-default:#D7DBE0}html pre.shiki code .s34Dl, html code.shiki .s34Dl{--shiki-default:#C5E478}html pre.shiki code .sz1_M, html code.shiki .sz1_M{--shiki-default:#80CBC4}html pre.shiki code .sddte, html code.shiki .sddte{--shiki-default:#BAEBE2}",{"title":83,"searchDepth":312,"depth":312,"links":2847},[2848,2849,2850,2851,2852,2853,2854,2855],{"id":1085,"depth":312,"text":1086},{"id":1575,"depth":312,"text":1576},{"id":1826,"depth":312,"text":1827},{"id":2018,"depth":312,"text":2019},{"id":2399,"depth":312,"text":2400},{"id":2436,"depth":312,"text":2437},{"id":2613,"depth":312,"text":2614},{"id":296,"depth":312,"text":297},"2026-08-24","Vue3 has a lot of hidden gems. Here are seven tips and tricks that most developers don't know about — but definitely should.",{},"\u002Fblog\u002Fvue3-tips-tricks",{"title":1077,"description":2857},"blog\u002Fvue3-tips-tricks",[333,820],"bGCIHWQg4bntJz20duIdoqModGgzRzoacYOlUrvf_5U",1790011947327]