[{"data":1,"prerenderedAt":25005},["ShallowReactive",2],{"blog-list":3},[4,3166,4085,4337,6015,7451,8691,10297,11432,11538,11638,15643,16222,16370,16466,18554,18647,18732,18821,18933,19227,19354,19479,19571,20830,21059,21133,21200,21357,21616,21693,21744,21883,21969],{"id":5,"title":6,"author":7,"authorSlug":8,"body":9,"date":3152,"description":3153,"extension":3154,"faq":8,"featured":3155,"image":3156,"meta":3157,"navigation":472,"path":3158,"readTime":8,"seo":3159,"stem":3160,"tags":3161,"__hash__":3165},"blog\u002Fblog\u002Fhow-to-create-medusa-js-with-nuxt-4.md","How to Build an E-Commerce Store with MedusaJS V2 and Nuxt 4","Epicmax Team",null,{"type":10,"value":11,"toc":3122},"minimark",[12,25,30,37,40,91,94,98,101,104,171,174,178,270,273,276,280,283,306,309,323,327,332,366,369,377,380,384,400,410,414,425,707,710,714,725,729,733,757,763,767,782,789,793,796,957,963,977,981,984,1060,1067,1236,1240,1244,1817,1821,2537,2541,2552,2934,2941,2945,2947,2953,2984,2988,2991,3019,3023,3030,3034,3037,3042,3068,3073,3097,3103,3107,3110,3113,3118],[13,14,15,16,20,21,24],"p",{},"Headless commerce has become the default architecture for teams that want full control over the storefront without reinventing the backend. ",[17,18,19],"strong",{},"MedusaJS V2"," and ",[17,22,23],{},"Nuxt 4"," are a natural fit: Medusa handles products, carts, orders, and payments while Nuxt 4 gives you a fast, SEO-friendly Vue-based storefront. In this guide you will set up both, wire them together, and understand how to manage your store going forward.",[26,27,29],"h2",{"id":28},"what-is-medusajs-v2","What is MedusaJS V2",[13,31,32,33,36],{},"Medusa (now at version 2.x) is an open-source, headless commerce engine built on Node.js and TypeScript. The original v1 was a monolithic Express server. ",[17,34,35],{},"V2 was redesigned from the ground up as a modular platform"," — every commerce domain (products, inventory, carts, orders, customers, promotions, payments) is an independent module that you can swap or extend without touching the rest of the system.",[13,38,39],{},"Key characteristics of Medusa V2:",[41,42,43,50,56,67,73,79,85],"ul",{},[44,45,46,49],"li",{},[17,47,48],{},"Modular architecture"," — install only the modules your store needs. A digital-goods store can skip the inventory module entirely; a B2B store can layer in company accounts.",[44,51,52,55],{},[17,53,54],{},"TypeScript-first"," — the entire codebase and its public APIs are fully typed.",[44,57,58,61,62,66],{},[17,59,60],{},"Built-in admin dashboard"," — ships at ",[63,64,65],"code",{},"\u002Fapp"," out of the box. No separate install required.",[44,68,69,72],{},[17,70,71],{},"Storefront API"," — a dedicated HTTP API surface designed for customer-facing operations: browse products, manage carts, place orders.",[44,74,75,78],{},[17,76,77],{},"Workflow engine"," — long-running operations (order fulfillment, return processing) are modeled as resumable workflows rather than synchronous request handlers.",[44,80,81,84],{},[17,82,83],{},"PostgreSQL as the default database"," — reliable, ACID-compliant, well-supported by hosting providers.",[44,86,87,90],{},[17,88,89],{},"Plugin ecosystem"," — official integrations for Stripe, PayPal, Braintree, SendGrid, S3, and more.",[13,92,93],{},"Medusa V2 runs as a standalone HTTP server. Your storefront is a completely separate application that talks to it over HTTP — which is exactly where Nuxt 4 comes in.",[26,95,97],{"id":96},"what-is-nuxt-4","What is Nuxt 4",[13,99,100],{},"Nuxt 4 is the latest major version of the Nuxt framework, built on top of Vue 3. It became the stable release in July 2025 and introduced several developer-experience improvements over Nuxt 3 while remaining backwards-compatible for most projects.",[13,102,103],{},"What changed in Nuxt 4 that matters for a commerce storefront:",[41,105,106,142,159,165],{},[44,107,108,114,115,118,119,118,122,118,125,128,129,131,132,118,135,118,138,141],{},[17,109,110,113],{},[63,111,112],{},"app\u002F"," directory"," — all application code (",[63,116,117],{},"pages\u002F",", ",[63,120,121],{},"components\u002F",[63,123,124],{},"composables\u002F",[63,126,127],{},"layouts\u002F",") now lives inside ",[63,130,112],{}," rather than the project root. Configuration files (",[63,133,134],{},"nuxt.config.ts",[63,136,137],{},"content.config.ts",[63,139,140],{},"package.json",") stay at the root.",[44,143,144,147,148,20,151,154,155,158],{},[17,145,146],{},"Smarter data fetching"," — ",[63,149,150],{},"useAsyncData",[63,152,153],{},"useFetch"," share state via a singleton cache, clean up on component unmount, and support reactive keys for automatic refetching. This is important for dynamic routes like ",[63,156,157],{},"\u002Fproducts\u002F[handle]",".",[44,160,161,164],{},[17,162,163],{},"Better TypeScript"," — Nuxt now generates separate TS configs for the app, server, and shared contexts, which reduces cross-environment type leakage.",[44,166,167,170],{},[17,168,169],{},"Faster cold starts"," — Node.js compile cache, native file watching, and socket-based communication between the CLI and Vite.",[13,172,173],{},"For a commerce storefront you get SSR + SSG out of the box, which means product pages can be pre-rendered at build time for SEO and then hydrated on the client.",[26,175,177],{"id":176},"why-this-combination-is-great","Why This Combination is Great",[179,180,181,195],"table",{},[182,183,184],"thead",{},[185,186,187,191,193],"tr",{},[188,189,190],"th",{},"Concern",[188,192,19],{},[188,194,23],{},[196,197,198,210,221,236,249,260],"tbody",{},[185,199,200,204,207],{},[201,202,203],"td",{},"Product catalog",[201,205,206],{},"✅ Managed via admin or API",[201,208,209],{},"✅ Fetched and cached at build time",[185,211,212,215,218],{},[201,213,214],{},"Cart & checkout",[201,216,217],{},"✅ Stateful cart sessions, payment integrations",[201,219,220],{},"✅ Client-side composables with SSR fallback",[185,222,223,226,229],{},[201,224,225],{},"SEO",[201,227,228],{},"❌ Not applicable (headless backend)",[201,230,231,232,235],{},"✅ SSR\u002FSSG, ",[63,233,234],{},"useSeoMeta",", sitemap",[185,237,238,241,246],{},[201,239,240],{},"Admin UI",[201,242,243,244],{},"✅ Ships at ",[63,245,65],{},[201,247,248],{},"Not needed",[185,250,251,254,257],{},[201,252,253],{},"Extensibility",[201,255,256],{},"✅ Custom modules, workflows, API routes",[201,258,259],{},"✅ Nuxt plugins, server routes, middleware",[185,261,262,265,268],{},[201,263,264],{},"TypeScript",[201,266,267],{},"✅ Full coverage",[201,269,267],{},[13,271,272],{},"The separation of concerns is clean: Medusa owns the commerce data and business logic; Nuxt owns the presentation layer. You can redesign the storefront completely without touching the backend, and you can upgrade Medusa modules without redeploying the frontend.",[13,274,275],{},"Both tools are open-source, actively maintained, and free to self-host — which keeps infrastructure costs predictable.",[26,277,279],{"id":278},"prerequisites","Prerequisites",[13,281,282],{},"Before you start, make sure you have the following installed:",[41,284,285,295,301],{},[44,286,287,290,291,294],{},[17,288,289],{},"Node.js 20+"," (check with ",[63,292,293],{},"node -v",")",[44,296,297,300],{},[17,298,299],{},"PostgreSQL 15+"," running locally or a managed database URL",[44,302,303],{},[17,304,305],{},"Git",[13,307,308],{},"You will run two separate servers during development:",[41,310,311,317],{},[44,312,313,314],{},"Medusa backend on ",[63,315,316],{},"http:\u002F\u002Flocalhost:9000",[44,318,319,320],{},"Nuxt 4 storefront on ",[63,321,322],{},"http:\u002F\u002Flocalhost:3000",[26,324,326],{"id":325},"setting-up-the-medusajs-v2-backend","Setting Up the MedusaJS V2 Backend",[328,329,331],"h3",{"id":330},"_1-create-a-new-medusa-project","1. Create a new Medusa project",[333,334,339],"pre",{"className":335,"code":336,"language":337,"meta":338,"style":338},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx create-medusa-app@latest my-store-backend\ncd my-store-backend\n","bash","",[63,340,341,357],{"__ignoreMap":338},[342,343,346,350,354],"span",{"class":344,"line":345},"line",1,[342,347,349],{"class":348},"sBMFI","npx",[342,351,353],{"class":352},"sfazB"," create-medusa-app@latest",[342,355,356],{"class":352}," my-store-backend\n",[342,358,360,364],{"class":344,"line":359},2,[342,361,363],{"class":362},"s2Zo4","cd",[342,365,356],{"class":352},[13,367,368],{},"The CLI will ask for a project name and a PostgreSQL connection string. Provide a local database URL in the format:",[333,370,375],{"className":371,"code":373,"language":374},[372],"language-text","postgres:\u002F\u002Fpostgres:password@localhost:5432\u002Fmedusa_store\n","text",[63,376,373],{"__ignoreMap":338},[13,378,379],{},"The scaffold installs dependencies, runs database migrations, and seeds demo data (a sample product catalog so you have something to work with right away).",[328,381,383],{"id":382},"_2-start-the-medusa-server","2. Start the Medusa server",[333,385,387],{"className":335,"code":386,"language":337,"meta":338,"style":338},"npm run dev\n",[63,388,389],{"__ignoreMap":338},[342,390,391,394,397],{"class":344,"line":345},[342,392,393],{"class":348},"npm",[342,395,396],{"class":352}," run",[342,398,399],{"class":352}," dev\n",[13,401,402,403,405,406,409],{},"Medusa starts on ",[63,404,316],{},". Open ",[63,407,408],{},"http:\u002F\u002Flocalhost:9000\u002Fapp"," to access the admin dashboard. Create an admin account when prompted on first launch.",[328,411,413],{"id":412},"_3-enable-cors-for-your-nuxt-storefront","3. Enable CORS for your Nuxt storefront",[13,415,416,417,420,421,424],{},"Open ",[63,418,419],{},"medusa-config.ts"," (or ",[63,422,423],{},"medusa-config.js",") in the project root and add your Nuxt dev URL to the storefront CORS list:",[333,426,430],{"className":427,"code":428,"language":429,"meta":338,"style":338},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F medusa-config.ts\nimport { defineConfig } from \"@medusajs\u002Fframework\u002Fconfig\"\n\nexport default defineConfig({\n  projectConfig: {\n    databaseUrl: process.env.DATABASE_URL,\n    http: {\n      storeCors: process.env.STORE_CORS ?? \"http:\u002F\u002Flocalhost:3000\",\n      adminCors: process.env.ADMIN_CORS ?? \"http:\u002F\u002Flocalhost:9000\",\n      authCors: process.env.AUTH_CORS ?? \"http:\u002F\u002Flocalhost:3000\",\n      jwtSecret: process.env.JWT_SECRET ?? \"supersecret\",\n      cookieSecret: process.env.COOKIE_SECRET ?? \"supersecret\",\n    },\n  },\n})\n","typescript",[63,431,432,438,467,474,491,504,528,538,569,598,627,657,686,692,698],{"__ignoreMap":338},[342,433,434],{"class":344,"line":345},[342,435,437],{"class":436},"sHwdD","\u002F\u002F medusa-config.ts\n",[342,439,440,444,448,452,455,458,461,464],{"class":344,"line":359},[342,441,443],{"class":442},"s7zQu","import",[342,445,447],{"class":446},"sMK4o"," {",[342,449,451],{"class":450},"sTEyZ"," defineConfig",[342,453,454],{"class":446}," }",[342,456,457],{"class":442}," from",[342,459,460],{"class":446}," \"",[342,462,463],{"class":352},"@medusajs\u002Fframework\u002Fconfig",[342,465,466],{"class":446},"\"\n",[342,468,470],{"class":344,"line":469},3,[342,471,473],{"emptyLinePlaceholder":472},true,"\n",[342,475,477,480,483,485,488],{"class":344,"line":476},4,[342,478,479],{"class":442},"export",[342,481,482],{"class":442}," default",[342,484,451],{"class":362},[342,486,487],{"class":450},"(",[342,489,490],{"class":446},"{\n",[342,492,494,498,501],{"class":344,"line":493},5,[342,495,497],{"class":496},"swJcz","  projectConfig",[342,499,500],{"class":446},":",[342,502,503],{"class":446}," {\n",[342,505,507,510,512,515,517,520,522,525],{"class":344,"line":506},6,[342,508,509],{"class":496},"    databaseUrl",[342,511,500],{"class":446},[342,513,514],{"class":450}," process",[342,516,158],{"class":446},[342,518,519],{"class":450},"env",[342,521,158],{"class":446},[342,523,524],{"class":450},"DATABASE_URL",[342,526,527],{"class":446},",\n",[342,529,531,534,536],{"class":344,"line":530},7,[342,532,533],{"class":496},"    http",[342,535,500],{"class":446},[342,537,503],{"class":446},[342,539,541,544,546,548,550,552,554,557,560,562,564,567],{"class":344,"line":540},8,[342,542,543],{"class":496},"      storeCors",[342,545,500],{"class":446},[342,547,514],{"class":450},[342,549,158],{"class":446},[342,551,519],{"class":450},[342,553,158],{"class":446},[342,555,556],{"class":450},"STORE_CORS ",[342,558,559],{"class":446},"??",[342,561,460],{"class":446},[342,563,322],{"class":352},[342,565,566],{"class":446},"\"",[342,568,527],{"class":446},[342,570,572,575,577,579,581,583,585,588,590,592,594,596],{"class":344,"line":571},9,[342,573,574],{"class":496},"      adminCors",[342,576,500],{"class":446},[342,578,514],{"class":450},[342,580,158],{"class":446},[342,582,519],{"class":450},[342,584,158],{"class":446},[342,586,587],{"class":450},"ADMIN_CORS ",[342,589,559],{"class":446},[342,591,460],{"class":446},[342,593,316],{"class":352},[342,595,566],{"class":446},[342,597,527],{"class":446},[342,599,601,604,606,608,610,612,614,617,619,621,623,625],{"class":344,"line":600},10,[342,602,603],{"class":496},"      authCors",[342,605,500],{"class":446},[342,607,514],{"class":450},[342,609,158],{"class":446},[342,611,519],{"class":450},[342,613,158],{"class":446},[342,615,616],{"class":450},"AUTH_CORS ",[342,618,559],{"class":446},[342,620,460],{"class":446},[342,622,322],{"class":352},[342,624,566],{"class":446},[342,626,527],{"class":446},[342,628,630,633,635,637,639,641,643,646,648,650,653,655],{"class":344,"line":629},11,[342,631,632],{"class":496},"      jwtSecret",[342,634,500],{"class":446},[342,636,514],{"class":450},[342,638,158],{"class":446},[342,640,519],{"class":450},[342,642,158],{"class":446},[342,644,645],{"class":450},"JWT_SECRET ",[342,647,559],{"class":446},[342,649,460],{"class":446},[342,651,652],{"class":352},"supersecret",[342,654,566],{"class":446},[342,656,527],{"class":446},[342,658,660,663,665,667,669,671,673,676,678,680,682,684],{"class":344,"line":659},12,[342,661,662],{"class":496},"      cookieSecret",[342,664,500],{"class":446},[342,666,514],{"class":450},[342,668,158],{"class":446},[342,670,519],{"class":450},[342,672,158],{"class":446},[342,674,675],{"class":450},"COOKIE_SECRET ",[342,677,559],{"class":446},[342,679,460],{"class":446},[342,681,652],{"class":352},[342,683,566],{"class":446},[342,685,527],{"class":446},[342,687,689],{"class":344,"line":688},13,[342,690,691],{"class":446},"    },\n",[342,693,695],{"class":344,"line":694},14,[342,696,697],{"class":446},"  },\n",[342,699,701,704],{"class":344,"line":700},15,[342,702,703],{"class":446},"}",[342,705,706],{"class":450},")\n",[13,708,709],{},"Restart the server after making config changes.",[328,711,713],{"id":712},"_4-create-an-api-key-for-the-storefront","4. Create an API key for the storefront",[13,715,716,717,720,721,724],{},"In the admin dashboard, go to ",[17,718,719],{},"Settings → API Keys"," and create a new ",[17,722,723],{},"Publishable API Key",". Copy the key — you will need it in the Nuxt project.",[26,726,728],{"id":727},"setting-up-the-nuxt-4-storefront","Setting Up the Nuxt 4 Storefront",[328,730,732],{"id":731},"_1-create-a-nuxt-4-project","1. Create a Nuxt 4 project",[333,734,736],{"className":335,"code":735,"language":337,"meta":338,"style":338},"npx nuxi@latest init my-store-frontend\ncd my-store-frontend\n",[63,737,738,751],{"__ignoreMap":338},[342,739,740,742,745,748],{"class":344,"line":345},[342,741,349],{"class":348},[342,743,744],{"class":352}," nuxi@latest",[342,746,747],{"class":352}," init",[342,749,750],{"class":352}," my-store-frontend\n",[342,752,753,755],{"class":344,"line":359},[342,754,363],{"class":362},[342,756,750],{"class":352},[13,758,759,760,762],{},"Accept the defaults. The scaffold uses Nuxt 4 by default with the ",[63,761,112],{}," directory structure.",[328,764,766],{"id":765},"_2-install-the-medusa-js-sdk","2. Install the Medusa JS SDK",[333,768,770],{"className":335,"code":769,"language":337,"meta":338,"style":338},"npm install @medusajs\u002Fjs-sdk\n",[63,771,772],{"__ignoreMap":338},[342,773,774,776,779],{"class":344,"line":345},[342,775,393],{"class":348},[342,777,778],{"class":352}," install",[342,780,781],{"class":352}," @medusajs\u002Fjs-sdk\n",[13,783,784,785,788],{},"The ",[63,786,787],{},"@medusajs\u002Fjs-sdk"," package is the official TypeScript client for the Medusa Storefront API. It handles authentication headers, cart session management, and response typing automatically.",[328,790,792],{"id":791},"_3-configure-the-medusa-client","3. Configure the Medusa client",[13,794,795],{},"Create a composable that exports a shared Medusa SDK instance:",[333,797,799],{"className":427,"code":798,"language":429,"meta":338,"style":338},"\u002F\u002F app\u002Fcomposables\u002FuseMedusa.ts\nimport Medusa from \"@medusajs\u002Fjs-sdk\"\n\nconst medusa = new Medusa({\n  baseUrl: \"http:\u002F\u002Flocalhost:9000\",\n  debug: process.env.NODE_ENV === \"development\",\n  publishableKey: import.meta.env.VITE_MEDUSA_PUBLISHABLE_KEY,\n})\n\nexport function useMedusa() {\n  return medusa\n}\n",[63,800,801,806,822,826,848,863,893,919,925,929,944,952],{"__ignoreMap":338},[342,802,803],{"class":344,"line":345},[342,804,805],{"class":436},"\u002F\u002F app\u002Fcomposables\u002FuseMedusa.ts\n",[342,807,808,810,813,816,818,820],{"class":344,"line":359},[342,809,443],{"class":442},[342,811,812],{"class":450}," Medusa ",[342,814,815],{"class":442},"from",[342,817,460],{"class":446},[342,819,787],{"class":352},[342,821,466],{"class":446},[342,823,824],{"class":344,"line":469},[342,825,473],{"emptyLinePlaceholder":472},[342,827,828,832,835,838,841,844,846],{"class":344,"line":476},[342,829,831],{"class":830},"spNyl","const",[342,833,834],{"class":450}," medusa ",[342,836,837],{"class":446},"=",[342,839,840],{"class":446}," new",[342,842,843],{"class":362}," Medusa",[342,845,487],{"class":450},[342,847,490],{"class":446},[342,849,850,853,855,857,859,861],{"class":344,"line":493},[342,851,852],{"class":496},"  baseUrl",[342,854,500],{"class":446},[342,856,460],{"class":446},[342,858,316],{"class":352},[342,860,566],{"class":446},[342,862,527],{"class":446},[342,864,865,868,870,872,874,876,878,881,884,886,889,891],{"class":344,"line":506},[342,866,867],{"class":496},"  debug",[342,869,500],{"class":446},[342,871,514],{"class":450},[342,873,158],{"class":446},[342,875,519],{"class":450},[342,877,158],{"class":446},[342,879,880],{"class":450},"NODE_ENV ",[342,882,883],{"class":446},"===",[342,885,460],{"class":446},[342,887,888],{"class":352},"development",[342,890,566],{"class":446},[342,892,527],{"class":446},[342,894,895,898,900,903,905,908,910,912,914,917],{"class":344,"line":530},[342,896,897],{"class":496},"  publishableKey",[342,899,500],{"class":446},[342,901,902],{"class":442}," import",[342,904,158],{"class":446},[342,906,907],{"class":450},"meta",[342,909,158],{"class":446},[342,911,519],{"class":450},[342,913,158],{"class":446},[342,915,916],{"class":450},"VITE_MEDUSA_PUBLISHABLE_KEY",[342,918,527],{"class":446},[342,920,921,923],{"class":344,"line":540},[342,922,703],{"class":446},[342,924,706],{"class":450},[342,926,927],{"class":344,"line":571},[342,928,473],{"emptyLinePlaceholder":472},[342,930,931,933,936,939,942],{"class":344,"line":600},[342,932,479],{"class":442},[342,934,935],{"class":830}," function",[342,937,938],{"class":362}," useMedusa",[342,940,941],{"class":446},"()",[342,943,503],{"class":446},[342,945,946,949],{"class":344,"line":629},[342,947,948],{"class":442},"  return",[342,950,951],{"class":450}," medusa\n",[342,953,954],{"class":344,"line":659},[342,955,956],{"class":446},"}\n",[13,958,959,960,500],{},"Add your publishable key to ",[63,961,962],{},".env",[333,964,966],{"className":335,"code":965,"language":337,"meta":338,"style":338},"VITE_MEDUSA_PUBLISHABLE_KEY=pk_...your_key_here...\n",[63,967,968],{"__ignoreMap":338},[342,969,970,972,974],{"class":344,"line":345},[342,971,916],{"class":450},[342,973,837],{"class":446},[342,975,976],{"class":352},"pk_...your_key_here...\n",[328,978,980],{"id":979},"_4-set-the-region","4. Set the region",[13,982,983],{},"Medusa V2 requires all store requests to target a specific region. Fetch the available regions and store the active region ID in a composable:",[333,985,987],{"className":427,"code":986,"language":429,"meta":338,"style":338},"\u002F\u002F app\u002Fcomposables\u002FuseRegion.ts\nexport const useRegion = () => {\n  return useState\u003Cstring | null>(\"regionId\", () => null)\n}\n",[63,988,989,994,1014,1056],{"__ignoreMap":338},[342,990,991],{"class":344,"line":345},[342,992,993],{"class":436},"\u002F\u002F app\u002Fcomposables\u002FuseRegion.ts\n",[342,995,996,998,1001,1004,1006,1009,1012],{"class":344,"line":359},[342,997,479],{"class":442},[342,999,1000],{"class":830}," const",[342,1002,1003],{"class":450}," useRegion ",[342,1005,837],{"class":446},[342,1007,1008],{"class":446}," ()",[342,1010,1011],{"class":830}," =>",[342,1013,503],{"class":446},[342,1015,1016,1018,1021,1024,1027,1030,1033,1036,1038,1040,1043,1045,1048,1050,1052,1054],{"class":344,"line":469},[342,1017,948],{"class":442},[342,1019,1020],{"class":362}," useState",[342,1022,1023],{"class":446},"\u003C",[342,1025,1026],{"class":348},"string",[342,1028,1029],{"class":446}," |",[342,1031,1032],{"class":348}," null",[342,1034,1035],{"class":446},">",[342,1037,487],{"class":496},[342,1039,566],{"class":446},[342,1041,1042],{"class":352},"regionId",[342,1044,566],{"class":446},[342,1046,1047],{"class":446},",",[342,1049,1008],{"class":446},[342,1051,1011],{"class":830},[342,1053,1032],{"class":446},[342,1055,706],{"class":496},[342,1057,1058],{"class":344,"line":476},[342,1059,956],{"class":446},[13,1061,1062,1063,1066],{},"Then resolve the region once in ",[63,1064,1065],{},"app.vue"," or a plugin:",[333,1068,1070],{"className":427,"code":1069,"language":429,"meta":338,"style":338},"\u002F\u002F app\u002Fplugins\u002Fregion.ts\nexport default defineNuxtPlugin(async () => {\n  const medusa = useMedusa()\n  const regionId = useRegion()\n\n  if (!regionId.value) {\n    const { regions } = await medusa.store.region.list()\n    regionId.value = regions[0]?.id ?? null\n  }\n})\n",[63,1071,1072,1077,1097,1113,1127,1131,1154,1190,1225,1230],{"__ignoreMap":338},[342,1073,1074],{"class":344,"line":345},[342,1075,1076],{"class":436},"\u002F\u002F app\u002Fplugins\u002Fregion.ts\n",[342,1078,1079,1081,1083,1086,1088,1091,1093,1095],{"class":344,"line":359},[342,1080,479],{"class":442},[342,1082,482],{"class":442},[342,1084,1085],{"class":362}," defineNuxtPlugin",[342,1087,487],{"class":450},[342,1089,1090],{"class":830},"async",[342,1092,1008],{"class":446},[342,1094,1011],{"class":830},[342,1096,503],{"class":446},[342,1098,1099,1102,1105,1108,1110],{"class":344,"line":469},[342,1100,1101],{"class":830},"  const",[342,1103,1104],{"class":450}," medusa",[342,1106,1107],{"class":446}," =",[342,1109,938],{"class":362},[342,1111,1112],{"class":496},"()\n",[342,1114,1115,1117,1120,1122,1125],{"class":344,"line":476},[342,1116,1101],{"class":830},[342,1118,1119],{"class":450}," regionId",[342,1121,1107],{"class":446},[342,1123,1124],{"class":362}," useRegion",[342,1126,1112],{"class":496},[342,1128,1129],{"class":344,"line":493},[342,1130,473],{"emptyLinePlaceholder":472},[342,1132,1133,1136,1139,1142,1144,1146,1149,1152],{"class":344,"line":506},[342,1134,1135],{"class":442},"  if",[342,1137,1138],{"class":496}," (",[342,1140,1141],{"class":446},"!",[342,1143,1042],{"class":450},[342,1145,158],{"class":446},[342,1147,1148],{"class":450},"value",[342,1150,1151],{"class":496},") ",[342,1153,490],{"class":446},[342,1155,1156,1159,1161,1164,1166,1168,1171,1173,1175,1178,1180,1183,1185,1188],{"class":344,"line":530},[342,1157,1158],{"class":830},"    const",[342,1160,447],{"class":446},[342,1162,1163],{"class":450}," regions",[342,1165,454],{"class":446},[342,1167,1107],{"class":446},[342,1169,1170],{"class":442}," await",[342,1172,1104],{"class":450},[342,1174,158],{"class":446},[342,1176,1177],{"class":450},"store",[342,1179,158],{"class":446},[342,1181,1182],{"class":450},"region",[342,1184,158],{"class":446},[342,1186,1187],{"class":362},"list",[342,1189,1112],{"class":496},[342,1191,1192,1195,1197,1199,1201,1203,1206,1210,1213,1216,1219,1222],{"class":344,"line":540},[342,1193,1194],{"class":450},"    regionId",[342,1196,158],{"class":446},[342,1198,1148],{"class":450},[342,1200,1107],{"class":446},[342,1202,1163],{"class":450},[342,1204,1205],{"class":496},"[",[342,1207,1209],{"class":1208},"sbssI","0",[342,1211,1212],{"class":496},"]",[342,1214,1215],{"class":446},"?.",[342,1217,1218],{"class":450},"id",[342,1220,1221],{"class":446}," ??",[342,1223,1224],{"class":446}," null\n",[342,1226,1227],{"class":344,"line":571},[342,1228,1229],{"class":446},"  }\n",[342,1231,1232,1234],{"class":344,"line":600},[342,1233,703],{"class":446},[342,1235,706],{"class":450},[26,1237,1239],{"id":1238},"fetching-products-and-building-pages","Fetching Products and Building Pages",[328,1241,1243],{"id":1242},"product-listing-page","Product listing page",[333,1245,1249],{"className":1246,"code":1247,"language":1248,"meta":338,"style":338},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- app\u002Fpages\u002Fproducts\u002Findex.vue -->\n\u003Cscript setup lang=\"ts\">\nconst medusa = useMedusa()\nconst regionId = useRegion()\n\nconst { data } = await useAsyncData(\"products\", () =>\n  medusa.store.product.list({\n    region_id: regionId.value ?? undefined,\n    limit: 20,\n  })\n)\n\nconst products = computed(() => data.value?.products ?? [])\n\nuseSeoMeta({\n  title: \"All Products\",\n  description: \"Browse our full product catalog.\",\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"container mx-auto px-4 py-12\">\n    \u003Ch1 class=\"text-4xl font-bold mb-8\">Products\u003C\u002Fh1>\n    \u003Cdiv class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\n      \u003CNuxtLink\n        v-for=\"product in products\"\n        :key=\"product.id\"\n        :to=\"`\u002Fproducts\u002F${product.handle}`\"\n        class=\"group\"\n      >\n        \u003CNuxtImg\n          v-if=\"product.thumbnail\"\n          :src=\"product.thumbnail\"\n          :alt=\"product.title\"\n          class=\"w-full aspect-square object-cover rounded-lg mb-3\"\n        \u002F>\n        \u003Cp class=\"font-semibold group-hover:underline\">{{ product.title }}\u003C\u002Fp>\n      \u003C\u002FNuxtLink>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[63,1250,1251,1256,1281,1293,1306,1310,1344,1366,1385,1397,1404,1408,1412,1447,1451,1459,1476,1493,1500,1510,1515,1525,1548,1579,1599,1608,1623,1638,1653,1668,1674,1683,1698,1712,1727,1742,1748,1777,1788,1798,1808],{"__ignoreMap":338},[342,1252,1253],{"class":344,"line":345},[342,1254,1255],{"class":436},"\u003C!-- app\u002Fpages\u002Fproducts\u002Findex.vue -->\n",[342,1257,1258,1260,1263,1266,1269,1271,1273,1276,1278],{"class":344,"line":359},[342,1259,1023],{"class":446},[342,1261,1262],{"class":496},"script",[342,1264,1265],{"class":830}," setup",[342,1267,1268],{"class":830}," lang",[342,1270,837],{"class":446},[342,1272,566],{"class":446},[342,1274,1275],{"class":352},"ts",[342,1277,566],{"class":446},[342,1279,1280],{"class":446},">\n",[342,1282,1283,1285,1287,1289,1291],{"class":344,"line":469},[342,1284,831],{"class":830},[342,1286,834],{"class":450},[342,1288,837],{"class":446},[342,1290,938],{"class":362},[342,1292,1112],{"class":450},[342,1294,1295,1297,1300,1302,1304],{"class":344,"line":476},[342,1296,831],{"class":830},[342,1298,1299],{"class":450}," regionId ",[342,1301,837],{"class":446},[342,1303,1124],{"class":362},[342,1305,1112],{"class":450},[342,1307,1308],{"class":344,"line":493},[342,1309,473],{"emptyLinePlaceholder":472},[342,1311,1312,1314,1316,1319,1321,1323,1325,1328,1330,1332,1335,1337,1339,1341],{"class":344,"line":506},[342,1313,831],{"class":830},[342,1315,447],{"class":446},[342,1317,1318],{"class":450}," data ",[342,1320,703],{"class":446},[342,1322,1107],{"class":446},[342,1324,1170],{"class":442},[342,1326,1327],{"class":362}," useAsyncData",[342,1329,487],{"class":450},[342,1331,566],{"class":446},[342,1333,1334],{"class":352},"products",[342,1336,566],{"class":446},[342,1338,1047],{"class":446},[342,1340,1008],{"class":446},[342,1342,1343],{"class":830}," =>\n",[342,1345,1346,1349,1351,1353,1355,1358,1360,1362,1364],{"class":344,"line":530},[342,1347,1348],{"class":450},"  medusa",[342,1350,158],{"class":446},[342,1352,1177],{"class":450},[342,1354,158],{"class":446},[342,1356,1357],{"class":450},"product",[342,1359,158],{"class":446},[342,1361,1187],{"class":362},[342,1363,487],{"class":450},[342,1365,490],{"class":446},[342,1367,1368,1371,1373,1375,1377,1380,1382],{"class":344,"line":540},[342,1369,1370],{"class":496},"    region_id",[342,1372,500],{"class":446},[342,1374,1119],{"class":450},[342,1376,158],{"class":446},[342,1378,1379],{"class":450},"value ",[342,1381,559],{"class":446},[342,1383,1384],{"class":446}," undefined,\n",[342,1386,1387,1390,1392,1395],{"class":344,"line":571},[342,1388,1389],{"class":496},"    limit",[342,1391,500],{"class":446},[342,1393,1394],{"class":1208}," 20",[342,1396,527],{"class":446},[342,1398,1399,1402],{"class":344,"line":600},[342,1400,1401],{"class":446},"  }",[342,1403,706],{"class":450},[342,1405,1406],{"class":344,"line":629},[342,1407,706],{"class":450},[342,1409,1410],{"class":344,"line":659},[342,1411,473],{"emptyLinePlaceholder":472},[342,1413,1414,1416,1419,1421,1424,1426,1428,1430,1433,1435,1437,1439,1442,1444],{"class":344,"line":688},[342,1415,831],{"class":830},[342,1417,1418],{"class":450}," products ",[342,1420,837],{"class":446},[342,1422,1423],{"class":362}," computed",[342,1425,487],{"class":450},[342,1427,941],{"class":446},[342,1429,1011],{"class":830},[342,1431,1432],{"class":450}," data",[342,1434,158],{"class":446},[342,1436,1148],{"class":450},[342,1438,1215],{"class":446},[342,1440,1441],{"class":450},"products ",[342,1443,559],{"class":446},[342,1445,1446],{"class":450}," [])\n",[342,1448,1449],{"class":344,"line":694},[342,1450,473],{"emptyLinePlaceholder":472},[342,1452,1453,1455,1457],{"class":344,"line":700},[342,1454,234],{"class":362},[342,1456,487],{"class":450},[342,1458,490],{"class":446},[342,1460,1462,1465,1467,1469,1472,1474],{"class":344,"line":1461},16,[342,1463,1464],{"class":496},"  title",[342,1466,500],{"class":446},[342,1468,460],{"class":446},[342,1470,1471],{"class":352},"All Products",[342,1473,566],{"class":446},[342,1475,527],{"class":446},[342,1477,1479,1482,1484,1486,1489,1491],{"class":344,"line":1478},17,[342,1480,1481],{"class":496},"  description",[342,1483,500],{"class":446},[342,1485,460],{"class":446},[342,1487,1488],{"class":352},"Browse our full product catalog.",[342,1490,566],{"class":446},[342,1492,527],{"class":446},[342,1494,1496,1498],{"class":344,"line":1495},18,[342,1497,703],{"class":446},[342,1499,706],{"class":450},[342,1501,1503,1506,1508],{"class":344,"line":1502},19,[342,1504,1505],{"class":446},"\u003C\u002F",[342,1507,1262],{"class":496},[342,1509,1280],{"class":446},[342,1511,1513],{"class":344,"line":1512},20,[342,1514,473],{"emptyLinePlaceholder":472},[342,1516,1518,1520,1523],{"class":344,"line":1517},21,[342,1519,1023],{"class":446},[342,1521,1522],{"class":496},"template",[342,1524,1280],{"class":446},[342,1526,1528,1531,1534,1537,1539,1541,1544,1546],{"class":344,"line":1527},22,[342,1529,1530],{"class":446},"  \u003C",[342,1532,1533],{"class":496},"div",[342,1535,1536],{"class":830}," class",[342,1538,837],{"class":446},[342,1540,566],{"class":446},[342,1542,1543],{"class":352},"container mx-auto px-4 py-12",[342,1545,566],{"class":446},[342,1547,1280],{"class":446},[342,1549,1551,1554,1557,1559,1561,1563,1566,1568,1570,1573,1575,1577],{"class":344,"line":1550},23,[342,1552,1553],{"class":446},"    \u003C",[342,1555,1556],{"class":496},"h1",[342,1558,1536],{"class":830},[342,1560,837],{"class":446},[342,1562,566],{"class":446},[342,1564,1565],{"class":352},"text-4xl font-bold mb-8",[342,1567,566],{"class":446},[342,1569,1035],{"class":446},[342,1571,1572],{"class":450},"Products",[342,1574,1505],{"class":446},[342,1576,1556],{"class":496},[342,1578,1280],{"class":446},[342,1580,1582,1584,1586,1588,1590,1592,1595,1597],{"class":344,"line":1581},24,[342,1583,1553],{"class":446},[342,1585,1533],{"class":496},[342,1587,1536],{"class":830},[342,1589,837],{"class":446},[342,1591,566],{"class":446},[342,1593,1594],{"class":352},"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6",[342,1596,566],{"class":446},[342,1598,1280],{"class":446},[342,1600,1602,1605],{"class":344,"line":1601},25,[342,1603,1604],{"class":446},"      \u003C",[342,1606,1607],{"class":496},"NuxtLink\n",[342,1609,1611,1614,1616,1618,1621],{"class":344,"line":1610},26,[342,1612,1613],{"class":830},"        v-for",[342,1615,837],{"class":446},[342,1617,566],{"class":446},[342,1619,1620],{"class":352},"product in products",[342,1622,466],{"class":446},[342,1624,1626,1629,1631,1633,1636],{"class":344,"line":1625},27,[342,1627,1628],{"class":830},"        :key",[342,1630,837],{"class":446},[342,1632,566],{"class":446},[342,1634,1635],{"class":352},"product.id",[342,1637,466],{"class":446},[342,1639,1641,1644,1646,1648,1651],{"class":344,"line":1640},28,[342,1642,1643],{"class":830},"        :to",[342,1645,837],{"class":446},[342,1647,566],{"class":446},[342,1649,1650],{"class":352},"`\u002Fproducts\u002F${product.handle}`",[342,1652,466],{"class":446},[342,1654,1656,1659,1661,1663,1666],{"class":344,"line":1655},29,[342,1657,1658],{"class":830},"        class",[342,1660,837],{"class":446},[342,1662,566],{"class":446},[342,1664,1665],{"class":352},"group",[342,1667,466],{"class":446},[342,1669,1671],{"class":344,"line":1670},30,[342,1672,1673],{"class":446},"      >\n",[342,1675,1677,1680],{"class":344,"line":1676},31,[342,1678,1679],{"class":446},"        \u003C",[342,1681,1682],{"class":496},"NuxtImg\n",[342,1684,1686,1689,1691,1693,1696],{"class":344,"line":1685},32,[342,1687,1688],{"class":830},"          v-if",[342,1690,837],{"class":446},[342,1692,566],{"class":446},[342,1694,1695],{"class":352},"product.thumbnail",[342,1697,466],{"class":446},[342,1699,1701,1704,1706,1708,1710],{"class":344,"line":1700},33,[342,1702,1703],{"class":830},"          :src",[342,1705,837],{"class":446},[342,1707,566],{"class":446},[342,1709,1695],{"class":352},[342,1711,466],{"class":446},[342,1713,1715,1718,1720,1722,1725],{"class":344,"line":1714},34,[342,1716,1717],{"class":830},"          :alt",[342,1719,837],{"class":446},[342,1721,566],{"class":446},[342,1723,1724],{"class":352},"product.title",[342,1726,466],{"class":446},[342,1728,1730,1733,1735,1737,1740],{"class":344,"line":1729},35,[342,1731,1732],{"class":830},"          class",[342,1734,837],{"class":446},[342,1736,566],{"class":446},[342,1738,1739],{"class":352},"w-full aspect-square object-cover rounded-lg mb-3",[342,1741,466],{"class":446},[342,1743,1745],{"class":344,"line":1744},36,[342,1746,1747],{"class":446},"        \u002F>\n",[342,1749,1751,1753,1755,1757,1759,1761,1764,1766,1768,1771,1773,1775],{"class":344,"line":1750},37,[342,1752,1679],{"class":446},[342,1754,13],{"class":496},[342,1756,1536],{"class":830},[342,1758,837],{"class":446},[342,1760,566],{"class":446},[342,1762,1763],{"class":352},"font-semibold group-hover:underline",[342,1765,566],{"class":446},[342,1767,1035],{"class":446},[342,1769,1770],{"class":450},"{{ product.title }}",[342,1772,1505],{"class":446},[342,1774,13],{"class":496},[342,1776,1280],{"class":446},[342,1778,1780,1783,1786],{"class":344,"line":1779},38,[342,1781,1782],{"class":446},"      \u003C\u002F",[342,1784,1785],{"class":496},"NuxtLink",[342,1787,1280],{"class":446},[342,1789,1791,1794,1796],{"class":344,"line":1790},39,[342,1792,1793],{"class":446},"    \u003C\u002F",[342,1795,1533],{"class":496},[342,1797,1280],{"class":446},[342,1799,1801,1804,1806],{"class":344,"line":1800},40,[342,1802,1803],{"class":446},"  \u003C\u002F",[342,1805,1533],{"class":496},[342,1807,1280],{"class":446},[342,1809,1811,1813,1815],{"class":344,"line":1810},41,[342,1812,1505],{"class":446},[342,1814,1522],{"class":496},[342,1816,1280],{"class":446},[328,1818,1820],{"id":1819},"product-detail-page","Product detail page",[333,1822,1824],{"className":1246,"code":1823,"language":1248,"meta":338,"style":338},"\u003C!-- app\u002Fpages\u002Fproducts\u002F[handle].vue -->\n\u003Cscript setup lang=\"ts\">\nconst route = useRoute()\nconst medusa = useMedusa()\nconst regionId = useRegion()\n\nconst { data } = await useAsyncData(`product-${route.params.handle}`, () =>\n  medusa.store.product.list({\n    handle: route.params.handle as string,\n    region_id: regionId.value ?? undefined,\n  })\n)\n\nconst product = computed(() => data.value?.products[0])\n\nif (!product.value) {\n  throw createError({ statusCode: 404, message: \"Product not found\" })\n}\n\nconst selectedVariantId = ref(product.value?.variants?.[0]?.id ?? null)\n\nuseSeoMeta({\n  title: product.value?.title,\n  description: product.value?.description ?? \"\",\n  ogImage: product.value?.thumbnail ?? undefined,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-if=\"product\" class=\"container mx-auto px-4 py-12\">\n    \u003Cdiv class=\"grid grid-cols-1 md:grid-cols-2 gap-12\">\n      \u003CNuxtImg\n        v-if=\"product.thumbnail\"\n        :src=\"product.thumbnail\"\n        :alt=\"product.title\"\n        class=\"w-full rounded-lg\"\n      \u002F>\n      \u003Cdiv>\n        \u003Ch1 class=\"text-3xl font-bold mb-4\">{{ product.title }}\u003C\u002Fh1>\n        \u003Cp class=\"text-gray-600 mb-6\">{{ product.description }}\u003C\u002Fp>\n        \u003Cbutton\n          class=\"bg-black text-white px-8 py-3 rounded-lg hover:bg-gray-800 transition-colors\"\n          @click=\"addToCart\"\n        >\n          Add to cart\n        \u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[63,1825,1826,1831,1851,1865,1877,1889,1893,1942,1962,1989,2005,2011,2015,2019,2052,2056,2074,2113,2117,2121,2164,2168,2176,2196,2220,2242,2248,2256,2260,2268,2297,2316,2322,2335,2348,2361,2374,2379,2387,2414,2442,2449,2463,2478,2484,2490,2501,2510,2519,2528],{"__ignoreMap":338},[342,1827,1828],{"class":344,"line":345},[342,1829,1830],{"class":436},"\u003C!-- app\u002Fpages\u002Fproducts\u002F[handle].vue -->\n",[342,1832,1833,1835,1837,1839,1841,1843,1845,1847,1849],{"class":344,"line":359},[342,1834,1023],{"class":446},[342,1836,1262],{"class":496},[342,1838,1265],{"class":830},[342,1840,1268],{"class":830},[342,1842,837],{"class":446},[342,1844,566],{"class":446},[342,1846,1275],{"class":352},[342,1848,566],{"class":446},[342,1850,1280],{"class":446},[342,1852,1853,1855,1858,1860,1863],{"class":344,"line":469},[342,1854,831],{"class":830},[342,1856,1857],{"class":450}," route ",[342,1859,837],{"class":446},[342,1861,1862],{"class":362}," useRoute",[342,1864,1112],{"class":450},[342,1866,1867,1869,1871,1873,1875],{"class":344,"line":476},[342,1868,831],{"class":830},[342,1870,834],{"class":450},[342,1872,837],{"class":446},[342,1874,938],{"class":362},[342,1876,1112],{"class":450},[342,1878,1879,1881,1883,1885,1887],{"class":344,"line":493},[342,1880,831],{"class":830},[342,1882,1299],{"class":450},[342,1884,837],{"class":446},[342,1886,1124],{"class":362},[342,1888,1112],{"class":450},[342,1890,1891],{"class":344,"line":506},[342,1892,473],{"emptyLinePlaceholder":472},[342,1894,1895,1897,1899,1901,1903,1905,1907,1909,1911,1914,1917,1920,1923,1925,1928,1930,1933,1936,1938,1940],{"class":344,"line":530},[342,1896,831],{"class":830},[342,1898,447],{"class":446},[342,1900,1318],{"class":450},[342,1902,703],{"class":446},[342,1904,1107],{"class":446},[342,1906,1170],{"class":442},[342,1908,1327],{"class":362},[342,1910,487],{"class":450},[342,1912,1913],{"class":446},"`",[342,1915,1916],{"class":352},"product-",[342,1918,1919],{"class":446},"${",[342,1921,1922],{"class":450},"route",[342,1924,158],{"class":446},[342,1926,1927],{"class":450},"params",[342,1929,158],{"class":446},[342,1931,1932],{"class":450},"handle",[342,1934,1935],{"class":446},"}`",[342,1937,1047],{"class":446},[342,1939,1008],{"class":446},[342,1941,1343],{"class":830},[342,1943,1944,1946,1948,1950,1952,1954,1956,1958,1960],{"class":344,"line":540},[342,1945,1348],{"class":450},[342,1947,158],{"class":446},[342,1949,1177],{"class":450},[342,1951,158],{"class":446},[342,1953,1357],{"class":450},[342,1955,158],{"class":446},[342,1957,1187],{"class":362},[342,1959,487],{"class":450},[342,1961,490],{"class":446},[342,1963,1964,1967,1969,1972,1974,1976,1978,1981,1984,1987],{"class":344,"line":571},[342,1965,1966],{"class":496},"    handle",[342,1968,500],{"class":446},[342,1970,1971],{"class":450}," route",[342,1973,158],{"class":446},[342,1975,1927],{"class":450},[342,1977,158],{"class":446},[342,1979,1980],{"class":450},"handle ",[342,1982,1983],{"class":442},"as",[342,1985,1986],{"class":348}," string",[342,1988,527],{"class":446},[342,1990,1991,1993,1995,1997,1999,2001,2003],{"class":344,"line":600},[342,1992,1370],{"class":496},[342,1994,500],{"class":446},[342,1996,1119],{"class":450},[342,1998,158],{"class":446},[342,2000,1379],{"class":450},[342,2002,559],{"class":446},[342,2004,1384],{"class":446},[342,2006,2007,2009],{"class":344,"line":629},[342,2008,1401],{"class":446},[342,2010,706],{"class":450},[342,2012,2013],{"class":344,"line":659},[342,2014,706],{"class":450},[342,2016,2017],{"class":344,"line":688},[342,2018,473],{"emptyLinePlaceholder":472},[342,2020,2021,2023,2026,2028,2030,2032,2034,2036,2038,2040,2042,2044,2047,2049],{"class":344,"line":694},[342,2022,831],{"class":830},[342,2024,2025],{"class":450}," product ",[342,2027,837],{"class":446},[342,2029,1423],{"class":362},[342,2031,487],{"class":450},[342,2033,941],{"class":446},[342,2035,1011],{"class":830},[342,2037,1432],{"class":450},[342,2039,158],{"class":446},[342,2041,1148],{"class":450},[342,2043,1215],{"class":446},[342,2045,2046],{"class":450},"products[",[342,2048,1209],{"class":1208},[342,2050,2051],{"class":450},"])\n",[342,2053,2054],{"class":344,"line":700},[342,2055,473],{"emptyLinePlaceholder":472},[342,2057,2058,2061,2063,2065,2067,2069,2072],{"class":344,"line":1461},[342,2059,2060],{"class":442},"if",[342,2062,1138],{"class":450},[342,2064,1141],{"class":446},[342,2066,1357],{"class":450},[342,2068,158],{"class":446},[342,2070,2071],{"class":450},"value) ",[342,2073,490],{"class":446},[342,2075,2076,2079,2082,2084,2087,2090,2092,2095,2097,2100,2102,2104,2107,2109,2111],{"class":344,"line":1478},[342,2077,2078],{"class":442},"  throw",[342,2080,2081],{"class":362}," createError",[342,2083,487],{"class":496},[342,2085,2086],{"class":446},"{",[342,2088,2089],{"class":496}," statusCode",[342,2091,500],{"class":446},[342,2093,2094],{"class":1208}," 404",[342,2096,1047],{"class":446},[342,2098,2099],{"class":496}," message",[342,2101,500],{"class":446},[342,2103,460],{"class":446},[342,2105,2106],{"class":352},"Product not found",[342,2108,566],{"class":446},[342,2110,454],{"class":446},[342,2112,706],{"class":496},[342,2114,2115],{"class":344,"line":1495},[342,2116,956],{"class":446},[342,2118,2119],{"class":344,"line":1502},[342,2120,473],{"emptyLinePlaceholder":472},[342,2122,2123,2125,2128,2130,2133,2136,2138,2140,2142,2145,2147,2149,2151,2153,2155,2158,2160,2162],{"class":344,"line":1512},[342,2124,831],{"class":830},[342,2126,2127],{"class":450}," selectedVariantId ",[342,2129,837],{"class":446},[342,2131,2132],{"class":362}," ref",[342,2134,2135],{"class":450},"(product",[342,2137,158],{"class":446},[342,2139,1148],{"class":450},[342,2141,1215],{"class":446},[342,2143,2144],{"class":450},"variants",[342,2146,1215],{"class":446},[342,2148,1205],{"class":450},[342,2150,1209],{"class":1208},[342,2152,1212],{"class":450},[342,2154,1215],{"class":446},[342,2156,2157],{"class":450},"id ",[342,2159,559],{"class":446},[342,2161,1032],{"class":446},[342,2163,706],{"class":450},[342,2165,2166],{"class":344,"line":1517},[342,2167,473],{"emptyLinePlaceholder":472},[342,2169,2170,2172,2174],{"class":344,"line":1527},[342,2171,234],{"class":362},[342,2173,487],{"class":450},[342,2175,490],{"class":446},[342,2177,2178,2180,2182,2185,2187,2189,2191,2194],{"class":344,"line":1550},[342,2179,1464],{"class":496},[342,2181,500],{"class":446},[342,2183,2184],{"class":450}," product",[342,2186,158],{"class":446},[342,2188,1148],{"class":450},[342,2190,1215],{"class":446},[342,2192,2193],{"class":450},"title",[342,2195,527],{"class":446},[342,2197,2198,2200,2202,2204,2206,2208,2210,2213,2215,2218],{"class":344,"line":1581},[342,2199,1481],{"class":496},[342,2201,500],{"class":446},[342,2203,2184],{"class":450},[342,2205,158],{"class":446},[342,2207,1148],{"class":450},[342,2209,1215],{"class":446},[342,2211,2212],{"class":450},"description ",[342,2214,559],{"class":446},[342,2216,2217],{"class":446}," \"\"",[342,2219,527],{"class":446},[342,2221,2222,2225,2227,2229,2231,2233,2235,2238,2240],{"class":344,"line":1601},[342,2223,2224],{"class":496},"  ogImage",[342,2226,500],{"class":446},[342,2228,2184],{"class":450},[342,2230,158],{"class":446},[342,2232,1148],{"class":450},[342,2234,1215],{"class":446},[342,2236,2237],{"class":450},"thumbnail ",[342,2239,559],{"class":446},[342,2241,1384],{"class":446},[342,2243,2244,2246],{"class":344,"line":1610},[342,2245,703],{"class":446},[342,2247,706],{"class":450},[342,2249,2250,2252,2254],{"class":344,"line":1625},[342,2251,1505],{"class":446},[342,2253,1262],{"class":496},[342,2255,1280],{"class":446},[342,2257,2258],{"class":344,"line":1640},[342,2259,473],{"emptyLinePlaceholder":472},[342,2261,2262,2264,2266],{"class":344,"line":1655},[342,2263,1023],{"class":446},[342,2265,1522],{"class":496},[342,2267,1280],{"class":446},[342,2269,2270,2272,2274,2277,2279,2281,2283,2285,2287,2289,2291,2293,2295],{"class":344,"line":1670},[342,2271,1530],{"class":446},[342,2273,1533],{"class":496},[342,2275,2276],{"class":830}," v-if",[342,2278,837],{"class":446},[342,2280,566],{"class":446},[342,2282,1357],{"class":352},[342,2284,566],{"class":446},[342,2286,1536],{"class":830},[342,2288,837],{"class":446},[342,2290,566],{"class":446},[342,2292,1543],{"class":352},[342,2294,566],{"class":446},[342,2296,1280],{"class":446},[342,2298,2299,2301,2303,2305,2307,2309,2312,2314],{"class":344,"line":1676},[342,2300,1553],{"class":446},[342,2302,1533],{"class":496},[342,2304,1536],{"class":830},[342,2306,837],{"class":446},[342,2308,566],{"class":446},[342,2310,2311],{"class":352},"grid grid-cols-1 md:grid-cols-2 gap-12",[342,2313,566],{"class":446},[342,2315,1280],{"class":446},[342,2317,2318,2320],{"class":344,"line":1685},[342,2319,1604],{"class":446},[342,2321,1682],{"class":496},[342,2323,2324,2327,2329,2331,2333],{"class":344,"line":1700},[342,2325,2326],{"class":830},"        v-if",[342,2328,837],{"class":446},[342,2330,566],{"class":446},[342,2332,1695],{"class":352},[342,2334,466],{"class":446},[342,2336,2337,2340,2342,2344,2346],{"class":344,"line":1714},[342,2338,2339],{"class":830},"        :src",[342,2341,837],{"class":446},[342,2343,566],{"class":446},[342,2345,1695],{"class":352},[342,2347,466],{"class":446},[342,2349,2350,2353,2355,2357,2359],{"class":344,"line":1729},[342,2351,2352],{"class":830},"        :alt",[342,2354,837],{"class":446},[342,2356,566],{"class":446},[342,2358,1724],{"class":352},[342,2360,466],{"class":446},[342,2362,2363,2365,2367,2369,2372],{"class":344,"line":1744},[342,2364,1658],{"class":830},[342,2366,837],{"class":446},[342,2368,566],{"class":446},[342,2370,2371],{"class":352},"w-full rounded-lg",[342,2373,466],{"class":446},[342,2375,2376],{"class":344,"line":1750},[342,2377,2378],{"class":446},"      \u002F>\n",[342,2380,2381,2383,2385],{"class":344,"line":1779},[342,2382,1604],{"class":446},[342,2384,1533],{"class":496},[342,2386,1280],{"class":446},[342,2388,2389,2391,2393,2395,2397,2399,2402,2404,2406,2408,2410,2412],{"class":344,"line":1790},[342,2390,1679],{"class":446},[342,2392,1556],{"class":496},[342,2394,1536],{"class":830},[342,2396,837],{"class":446},[342,2398,566],{"class":446},[342,2400,2401],{"class":352},"text-3xl font-bold mb-4",[342,2403,566],{"class":446},[342,2405,1035],{"class":446},[342,2407,1770],{"class":450},[342,2409,1505],{"class":446},[342,2411,1556],{"class":496},[342,2413,1280],{"class":446},[342,2415,2416,2418,2420,2422,2424,2426,2429,2431,2433,2436,2438,2440],{"class":344,"line":1800},[342,2417,1679],{"class":446},[342,2419,13],{"class":496},[342,2421,1536],{"class":830},[342,2423,837],{"class":446},[342,2425,566],{"class":446},[342,2427,2428],{"class":352},"text-gray-600 mb-6",[342,2430,566],{"class":446},[342,2432,1035],{"class":446},[342,2434,2435],{"class":450},"{{ product.description }}",[342,2437,1505],{"class":446},[342,2439,13],{"class":496},[342,2441,1280],{"class":446},[342,2443,2444,2446],{"class":344,"line":1810},[342,2445,1679],{"class":446},[342,2447,2448],{"class":496},"button\n",[342,2450,2452,2454,2456,2458,2461],{"class":344,"line":2451},42,[342,2453,1732],{"class":830},[342,2455,837],{"class":446},[342,2457,566],{"class":446},[342,2459,2460],{"class":352},"bg-black text-white px-8 py-3 rounded-lg hover:bg-gray-800 transition-colors",[342,2462,466],{"class":446},[342,2464,2466,2469,2471,2473,2476],{"class":344,"line":2465},43,[342,2467,2468],{"class":830},"          @click",[342,2470,837],{"class":446},[342,2472,566],{"class":446},[342,2474,2475],{"class":352},"addToCart",[342,2477,466],{"class":446},[342,2479,2481],{"class":344,"line":2480},44,[342,2482,2483],{"class":446},"        >\n",[342,2485,2487],{"class":344,"line":2486},45,[342,2488,2489],{"class":450},"          Add to cart\n",[342,2491,2493,2496,2499],{"class":344,"line":2492},46,[342,2494,2495],{"class":446},"        \u003C\u002F",[342,2497,2498],{"class":496},"button",[342,2500,1280],{"class":446},[342,2502,2504,2506,2508],{"class":344,"line":2503},47,[342,2505,1782],{"class":446},[342,2507,1533],{"class":496},[342,2509,1280],{"class":446},[342,2511,2513,2515,2517],{"class":344,"line":2512},48,[342,2514,1793],{"class":446},[342,2516,1533],{"class":496},[342,2518,1280],{"class":446},[342,2520,2522,2524,2526],{"class":344,"line":2521},49,[342,2523,1803],{"class":446},[342,2525,1533],{"class":496},[342,2527,1280],{"class":446},[342,2529,2531,2533,2535],{"class":344,"line":2530},50,[342,2532,1505],{"class":446},[342,2534,1522],{"class":496},[342,2536,1280],{"class":446},[26,2538,2540],{"id":2539},"managing-the-cart","Managing the Cart",[13,2542,2543,2544,2547,2548,2551],{},"Medusa V2 uses a ",[17,2545,2546],{},"cart session"," identified by a cart ID that you store client-side (in a cookie or ",[63,2549,2550],{},"localStorage","). The SDK makes this straightforward.",[333,2553,2555],{"className":427,"code":2554,"language":429,"meta":338,"style":338},"\u002F\u002F app\u002Fcomposables\u002FuseCart.ts\nexport const useCartId = () => useState\u003Cstring | null>(\"cartId\", () => null)\n\nexport function useCart() {\n  const medusa = useMedusa()\n  const regionId = useRegion()\n  const cartId = useCartId()\n\n  async function getOrCreateCart() {\n    if (cartId.value) return cartId.value\n\n    const { cart } = await medusa.store.cart.create({\n      region_id: regionId.value ?? undefined,\n    })\n    cartId.value = cart.id\n    return cart.id\n  }\n\n  async function addItem(variantId: string, quantity = 1) {\n    const id = await getOrCreateCart()\n    await medusa.store.cart.createLineItem(id, {\n      variant_id: variantId,\n      quantity,\n    })\n  }\n\n  return { getOrCreateCart, addItem }\n}\n",[63,2556,2557,2562,2608,2612,2625,2637,2649,2663,2667,2681,2706,2710,2745,2762,2769,2787,2798,2802,2806,2839,2854,2882,2894,2901,2907,2911,2915,2930],{"__ignoreMap":338},[342,2558,2559],{"class":344,"line":345},[342,2560,2561],{"class":436},"\u002F\u002F app\u002Fcomposables\u002FuseCart.ts\n",[342,2563,2564,2566,2568,2571,2573,2575,2577,2579,2581,2583,2585,2587,2589,2591,2593,2596,2598,2600,2602,2604,2606],{"class":344,"line":359},[342,2565,479],{"class":442},[342,2567,1000],{"class":830},[342,2569,2570],{"class":450}," useCartId ",[342,2572,837],{"class":446},[342,2574,1008],{"class":446},[342,2576,1011],{"class":830},[342,2578,1020],{"class":362},[342,2580,1023],{"class":446},[342,2582,1026],{"class":348},[342,2584,1029],{"class":446},[342,2586,1032],{"class":348},[342,2588,1035],{"class":446},[342,2590,487],{"class":450},[342,2592,566],{"class":446},[342,2594,2595],{"class":352},"cartId",[342,2597,566],{"class":446},[342,2599,1047],{"class":446},[342,2601,1008],{"class":446},[342,2603,1011],{"class":830},[342,2605,1032],{"class":446},[342,2607,706],{"class":450},[342,2609,2610],{"class":344,"line":469},[342,2611,473],{"emptyLinePlaceholder":472},[342,2613,2614,2616,2618,2621,2623],{"class":344,"line":476},[342,2615,479],{"class":442},[342,2617,935],{"class":830},[342,2619,2620],{"class":362}," useCart",[342,2622,941],{"class":446},[342,2624,503],{"class":446},[342,2626,2627,2629,2631,2633,2635],{"class":344,"line":493},[342,2628,1101],{"class":830},[342,2630,1104],{"class":450},[342,2632,1107],{"class":446},[342,2634,938],{"class":362},[342,2636,1112],{"class":496},[342,2638,2639,2641,2643,2645,2647],{"class":344,"line":506},[342,2640,1101],{"class":830},[342,2642,1119],{"class":450},[342,2644,1107],{"class":446},[342,2646,1124],{"class":362},[342,2648,1112],{"class":496},[342,2650,2651,2653,2656,2658,2661],{"class":344,"line":530},[342,2652,1101],{"class":830},[342,2654,2655],{"class":450}," cartId",[342,2657,1107],{"class":446},[342,2659,2660],{"class":362}," useCartId",[342,2662,1112],{"class":496},[342,2664,2665],{"class":344,"line":540},[342,2666,473],{"emptyLinePlaceholder":472},[342,2668,2669,2672,2674,2677,2679],{"class":344,"line":571},[342,2670,2671],{"class":830},"  async",[342,2673,935],{"class":830},[342,2675,2676],{"class":362}," getOrCreateCart",[342,2678,941],{"class":446},[342,2680,503],{"class":446},[342,2682,2683,2686,2688,2690,2692,2694,2696,2699,2701,2703],{"class":344,"line":600},[342,2684,2685],{"class":442},"    if",[342,2687,1138],{"class":496},[342,2689,2595],{"class":450},[342,2691,158],{"class":446},[342,2693,1148],{"class":450},[342,2695,1151],{"class":496},[342,2697,2698],{"class":442},"return",[342,2700,2655],{"class":450},[342,2702,158],{"class":446},[342,2704,2705],{"class":450},"value\n",[342,2707,2708],{"class":344,"line":629},[342,2709,473],{"emptyLinePlaceholder":472},[342,2711,2712,2714,2716,2719,2721,2723,2725,2727,2729,2731,2733,2736,2738,2741,2743],{"class":344,"line":659},[342,2713,1158],{"class":830},[342,2715,447],{"class":446},[342,2717,2718],{"class":450}," cart",[342,2720,454],{"class":446},[342,2722,1107],{"class":446},[342,2724,1170],{"class":442},[342,2726,1104],{"class":450},[342,2728,158],{"class":446},[342,2730,1177],{"class":450},[342,2732,158],{"class":446},[342,2734,2735],{"class":450},"cart",[342,2737,158],{"class":446},[342,2739,2740],{"class":362},"create",[342,2742,487],{"class":496},[342,2744,490],{"class":446},[342,2746,2747,2750,2752,2754,2756,2758,2760],{"class":344,"line":688},[342,2748,2749],{"class":496},"      region_id",[342,2751,500],{"class":446},[342,2753,1119],{"class":450},[342,2755,158],{"class":446},[342,2757,1148],{"class":450},[342,2759,1221],{"class":446},[342,2761,1384],{"class":446},[342,2763,2764,2767],{"class":344,"line":694},[342,2765,2766],{"class":446},"    }",[342,2768,706],{"class":496},[342,2770,2771,2774,2776,2778,2780,2782,2784],{"class":344,"line":700},[342,2772,2773],{"class":450},"    cartId",[342,2775,158],{"class":446},[342,2777,1148],{"class":450},[342,2779,1107],{"class":446},[342,2781,2718],{"class":450},[342,2783,158],{"class":446},[342,2785,2786],{"class":450},"id\n",[342,2788,2789,2792,2794,2796],{"class":344,"line":1461},[342,2790,2791],{"class":442},"    return",[342,2793,2718],{"class":450},[342,2795,158],{"class":446},[342,2797,2786],{"class":450},[342,2799,2800],{"class":344,"line":1478},[342,2801,1229],{"class":446},[342,2803,2804],{"class":344,"line":1495},[342,2805,473],{"emptyLinePlaceholder":472},[342,2807,2808,2810,2812,2815,2817,2821,2823,2825,2827,2830,2832,2835,2837],{"class":344,"line":1502},[342,2809,2671],{"class":830},[342,2811,935],{"class":830},[342,2813,2814],{"class":362}," addItem",[342,2816,487],{"class":446},[342,2818,2820],{"class":2819},"sHdIc","variantId",[342,2822,500],{"class":446},[342,2824,1986],{"class":348},[342,2826,1047],{"class":446},[342,2828,2829],{"class":2819}," quantity",[342,2831,1107],{"class":446},[342,2833,2834],{"class":1208}," 1",[342,2836,294],{"class":446},[342,2838,503],{"class":446},[342,2840,2841,2843,2846,2848,2850,2852],{"class":344,"line":1512},[342,2842,1158],{"class":830},[342,2844,2845],{"class":450}," id",[342,2847,1107],{"class":446},[342,2849,1170],{"class":442},[342,2851,2676],{"class":362},[342,2853,1112],{"class":496},[342,2855,2856,2859,2861,2863,2865,2867,2869,2871,2874,2876,2878,2880],{"class":344,"line":1517},[342,2857,2858],{"class":442},"    await",[342,2860,1104],{"class":450},[342,2862,158],{"class":446},[342,2864,1177],{"class":450},[342,2866,158],{"class":446},[342,2868,2735],{"class":450},[342,2870,158],{"class":446},[342,2872,2873],{"class":362},"createLineItem",[342,2875,487],{"class":496},[342,2877,1218],{"class":450},[342,2879,1047],{"class":446},[342,2881,503],{"class":446},[342,2883,2884,2887,2889,2892],{"class":344,"line":1527},[342,2885,2886],{"class":496},"      variant_id",[342,2888,500],{"class":446},[342,2890,2891],{"class":450}," variantId",[342,2893,527],{"class":446},[342,2895,2896,2899],{"class":344,"line":1550},[342,2897,2898],{"class":450},"      quantity",[342,2900,527],{"class":446},[342,2902,2903,2905],{"class":344,"line":1581},[342,2904,2766],{"class":446},[342,2906,706],{"class":496},[342,2908,2909],{"class":344,"line":1601},[342,2910,1229],{"class":446},[342,2912,2913],{"class":344,"line":1610},[342,2914,473],{"emptyLinePlaceholder":472},[342,2916,2917,2919,2921,2923,2925,2927],{"class":344,"line":1625},[342,2918,948],{"class":442},[342,2920,447],{"class":446},[342,2922,2676],{"class":450},[342,2924,1047],{"class":446},[342,2926,2814],{"class":450},[342,2928,2929],{"class":446}," }\n",[342,2931,2932],{"class":344,"line":1640},[342,2933,956],{"class":446},[13,2935,2936,2937,2940],{},"Use ",[63,2938,2939],{},"addItem"," from any product page to add variants to the cart.",[26,2942,2944],{"id":2943},"managing-products-and-orders-in-the-admin","Managing Products and Orders in the Admin",[328,2946,1572],{"id":1334},[13,2948,2949,2950,2952],{},"From the Medusa admin dashboard (",[63,2951,408],{},"):",[2954,2955,2956,2966,2972,2978],"ol",{},[44,2957,2958,2961,2962,2965],{},[17,2959,2960],{},"Create a product"," — go to ",[17,2963,2964],{},"Products → New product",". Fill in the title, description, and add variants (e.g., size S\u002FM\u002FL, color). Each variant gets a SKU, price per region, and optional inventory tracking.",[44,2967,2968,2971],{},[17,2969,2970],{},"Upload images"," — drag images directly in the product editor. Medusa stores them in your configured file storage (local filesystem in development, S3\u002FGCS in production).",[44,2973,2974,2977],{},[17,2975,2976],{},"Manage inventory"," — if you have the inventory module enabled, set stock levels per variant and per location.",[44,2979,2980,2983],{},[17,2981,2982],{},"Publish"," — toggle the product status from \"Draft\" to \"Published\" to make it visible via the Storefront API.",[328,2985,2987],{"id":2986},"orders","Orders",[13,2989,2990],{},"When a customer completes checkout:",[2954,2992,2993,2999,3006,3016],{},[44,2994,2995,2996,2998],{},"The order appears under ",[17,2997,2987],{}," in the admin.",[44,3000,3001,3002,3005],{},"You can ",[17,3003,3004],{},"fulfill"," items (mark as shipped, add tracking numbers).",[44,3007,3001,3008,3011,3012,3015],{},[17,3009,3010],{},"issue refunds"," or ",[17,3013,3014],{},"create returns"," directly from the order detail page.",[44,3017,3018],{},"The workflow engine handles multi-step operations (e.g., partial fulfillments, split shipments) without custom code.",[328,3020,3022],{"id":3021},"regions-and-currencies","Regions and Currencies",[13,3024,3025,3026,3029],{},"Under ",[17,3027,3028],{},"Settings → Regions",", configure which countries belong to each region and which currency and payment providers apply. This is important if you sell internationally — product prices are set per region, so you can localize pricing without duplicating products.",[26,3031,3033],{"id":3032},"deploying-to-production","Deploying to Production",[13,3035,3036],{},"When you are ready to deploy:",[13,3038,3039],{},[17,3040,3041],{},"Medusa backend:",[41,3043,3044,3047,3065],{},[44,3045,3046],{},"Use a managed PostgreSQL service (e.g., Supabase, Neon, Railway, AWS RDS).",[44,3048,3049,3050,118,3052,118,3055,118,3058,118,3061,3064],{},"Set ",[63,3051,524],{},[63,3053,3054],{},"JWT_SECRET",[63,3056,3057],{},"COOKIE_SECRET",[63,3059,3060],{},"STORE_CORS",[63,3062,3063],{},"ADMIN_CORS"," as environment variables.",[44,3066,3067],{},"Deploy the Node.js server to Railway, Render, Fly.io, or a VPS.",[13,3069,3070],{},[17,3071,3072],{},"Nuxt 4 storefront:",[41,3074,3075,3083,3094],{},[44,3076,3049,3077,20,3079,3082],{},[63,3078,916],{},[63,3080,3081],{},"NUXT_PUBLIC_MEDUSA_URL"," pointing to your production Medusa URL.",[44,3084,3085,3086,3089,3090,3093],{},"Run ",[63,3087,3088],{},"npm run generate"," for a fully static build or ",[63,3091,3092],{},"npm run build"," for SSR.",[44,3095,3096],{},"Deploy to Netlify, Vercel, or Cloudflare Pages.",[13,3098,3099,3100,3102],{},"Update ",[63,3101,3060],{}," in your Medusa config to include the production Nuxt domain.",[26,3104,3106],{"id":3105},"conclusion","Conclusion",[13,3108,3109],{},"MedusaJS V2 and Nuxt 4 give you a modern, fully typed, open-source e-commerce stack where each layer does exactly one job. Medusa owns the commerce logic — catalog, cart, checkout, order management — and exposes it through a clean Storefront API. Nuxt 4 owns the presentation layer, bringing SSR, SEO, and a great Vue developer experience. The two communicate over HTTP, which means you can scale, replace, or upgrade each side independently as your store grows.",[13,3111,3112],{},"If you want help architecting or building your storefront, the Epicmax team has deep experience with Vue 3 and Nuxt projects of all sizes.",[3114,3115],"blog-button",{"label":3116,"to":3117},"Talk to an engineer","https:\u002F\u002Fcalendar.app.google\u002FXwRzASZitZRSVLxq6",[3119,3120,3121],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":338,"searchDepth":469,"depth":469,"links":3123},[3124,3125,3126,3127,3128,3134,3140,3144,3145,3150,3151],{"id":28,"depth":359,"text":29},{"id":96,"depth":359,"text":97},{"id":176,"depth":359,"text":177},{"id":278,"depth":359,"text":279},{"id":325,"depth":359,"text":326,"children":3129},[3130,3131,3132,3133],{"id":330,"depth":469,"text":331},{"id":382,"depth":469,"text":383},{"id":412,"depth":469,"text":413},{"id":712,"depth":469,"text":713},{"id":727,"depth":359,"text":728,"children":3135},[3136,3137,3138,3139],{"id":731,"depth":469,"text":732},{"id":765,"depth":469,"text":766},{"id":791,"depth":469,"text":792},{"id":979,"depth":469,"text":980},{"id":1238,"depth":359,"text":1239,"children":3141},[3142,3143],{"id":1242,"depth":469,"text":1243},{"id":1819,"depth":469,"text":1820},{"id":2539,"depth":359,"text":2540},{"id":2943,"depth":359,"text":2944,"children":3146},[3147,3148,3149],{"id":1334,"depth":469,"text":1572},{"id":2986,"depth":469,"text":2987},{"id":3021,"depth":469,"text":3022},{"id":3032,"depth":359,"text":3033},{"id":3105,"depth":359,"text":3106},"2026-06-03","Step-by-step guide to building a headless e-commerce store with MedusaJS V2 as the backend and Nuxt 4 as the storefront. Covers setup, API integration, and product management.","md",false,"\u002Fimages\u002Fblog\u002Fhow-to-build-an-e-commerce-store-with-medusajs-v2-and-nuxt-4\u002Fcover.png",{},"\u002Fblog\u002Fhow-to-create-medusa-js-with-nuxt-4",{"title":6,"description":3153},"blog\u002Fhow-to-create-medusa-js-with-nuxt-4",[23,3162,3163,3164],"MedusaJS","E-commerce","Vue.js","Gr3KJ0A2BoFKE2CVXhv9gs9iz1O-4GK712OqHG2u-es",{"id":3167,"title":3168,"author":7,"authorSlug":8,"body":3169,"date":3152,"description":4075,"extension":3154,"faq":8,"featured":3155,"image":8,"meta":4076,"navigation":472,"path":4077,"readTime":4078,"seo":4079,"stem":4080,"tags":4081,"__hash__":4084},"blog\u002Fblog\u002Fnuxt-layers.md","Nuxt Layers in Nuxt 3 – What It Is and Why It Matters",{"type":10,"value":3170,"toc":4058},[3171,3177,3180,3184,3191,3194,3245,3251,3257,3261,3264,3400,3410,3414,3417,3434,3440,3446,3520,3524,3527,3552,3555,3616,3620,3624,3634,3640,3643,3647,3650,3653,3657,3660,3666,3670,3673,3678,3700,3706,3728,3733,3755,3769,3773,3776,3810,3824,3828,3834,3840,3846,3855,3859,3865,3871,3877,3883,3886,3890,3893,3918,3926,3970,3980,4032,4050,4053,4055],[13,3172,3173,3174,3176],{},"Nuxt Layers is one of the most architecturally significant features in Nuxt 3 — and one of the least understood outside teams that have already run into the scaling walls it solves. For a single self-contained project, layers may never come up. But the moment you find yourself copy-pasting ",[63,3175,134],{}," entries across repos, maintaining a shared component library, or trying to keep three different apps on the same design system, the absence of a proper layering strategy starts showing up as real maintenance overhead.",[13,3178,3179],{},"This article explains what Nuxt Layers are at an architectural level, how they differ from modules and standard Nuxt project structure, where they are genuinely useful, and what trade-offs you should weigh before adopting them.",[26,3181,3183],{"id":3182},"what-are-nuxt-layers","What Are Nuxt Layers?",[13,3185,3186,3187,3190],{},"Nuxt Layers is a composition system that lets you ",[17,3188,3189],{},"extend one Nuxt application on top of another",". A layer is itself a partial Nuxt application — it can include components, composables, pages, plugins, layouts, server routes, middleware, configuration, and even other layers. When a Nuxt project extends a layer, it inherits and merges all of those artifacts as if they were authored locally in the project.",[13,3192,3193],{},"At the configuration level, extending a layer is a single line:",[333,3195,3197],{"className":427,"code":3196,"language":429,"meta":338,"style":338},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  extends: ['.\u002Fbase-layer'],\n})\n",[63,3198,3199,3204,3217,3239],{"__ignoreMap":338},[342,3200,3201],{"class":344,"line":345},[342,3202,3203],{"class":436},"\u002F\u002F nuxt.config.ts\n",[342,3205,3206,3208,3210,3213,3215],{"class":344,"line":359},[342,3207,479],{"class":442},[342,3209,482],{"class":442},[342,3211,3212],{"class":362}," defineNuxtConfig",[342,3214,487],{"class":450},[342,3216,490],{"class":446},[342,3218,3219,3222,3224,3227,3230,3233,3235,3237],{"class":344,"line":469},[342,3220,3221],{"class":496},"  extends",[342,3223,500],{"class":446},[342,3225,3226],{"class":450}," [",[342,3228,3229],{"class":446},"'",[342,3231,3232],{"class":352},".\u002Fbase-layer",[342,3234,3229],{"class":446},[342,3236,1212],{"class":450},[342,3238,527],{"class":446},[342,3240,3241,3243],{"class":344,"line":476},[342,3242,703],{"class":446},[342,3244,706],{"class":450},[13,3246,3247,3248,3250],{},"This tells Nuxt to resolve the ",[63,3249,3232],{}," directory, merge its auto-scanned directories into the current project, and make everything from the layer available as if it belonged to the project itself.",[13,3252,3253,3254,3256],{},"A layer is minimal by design. The only required file is a ",[63,3255,134],{},". Everything else — components, composables, pages, utils, server routes, assets — is optional. You include only what the layer needs to provide.",[328,3258,3260],{"id":3259},"what-gets-merged","What Gets Merged",[13,3262,3263],{},"When a layer is extended, Nuxt automatically scans and merges these directories:",[179,3265,3266,3279],{},[182,3267,3268],{},[185,3269,3270,3273,3276],{},[188,3271,3272],{},"Directory",[188,3274,3275],{},"What the layer provides",[188,3277,3278],{},"Project can override",[196,3280,3281,3293,3304,3315,3327,3338,3351,3364,3376,3389],{},[185,3282,3283,3287,3290],{},[201,3284,3285],{},[63,3286,121],{},[201,3288,3289],{},"Shared UI components",[201,3291,3292],{},"By same filename",[185,3294,3295,3299,3302],{},[201,3296,3297],{},[63,3298,124],{},[201,3300,3301],{},"Shared reactive logic",[201,3303,3292],{},[185,3305,3306,3310,3313],{},[201,3307,3308],{},[63,3309,127],{},[201,3311,3312],{},"Base layouts",[201,3314,3292],{},[185,3316,3317,3322,3325],{},[201,3318,3319],{},[63,3320,3321],{},"middleware\u002F",[201,3323,3324],{},"Route middleware",[201,3326,3292],{},[185,3328,3329,3333,3336],{},[201,3330,3331],{},[63,3332,117],{},[201,3334,3335],{},"Route pages",[201,3337,3292],{},[185,3339,3340,3345,3348],{},[201,3341,3342],{},[63,3343,3344],{},"plugins\u002F",[201,3346,3347],{},"Vue\u002FNuxt plugins",[201,3349,3350],{},"Merged in registration order",[185,3352,3353,3358,3361],{},[201,3354,3355],{},[63,3356,3357],{},"server\u002F",[201,3359,3360],{},"API routes, middleware",[201,3362,3363],{},"By same path",[185,3365,3366,3371,3374],{},[201,3367,3368],{},[63,3369,3370],{},"utils\u002F",[201,3372,3373],{},"Utility functions",[201,3375,3292],{},[185,3377,3378,3383,3386],{},[201,3379,3380],{},[63,3381,3382],{},"app.config.ts",[201,3384,3385],{},"App-level config",[201,3387,3388],{},"Deep-merged",[185,3390,3391,3395,3398],{},[201,3392,3393],{},[63,3394,134],{},[201,3396,3397],{},"Modules, runtime config",[201,3399,3388],{},[13,3401,3402,3403,3406,3407,3409],{},"The project always wins. If the project defines ",[63,3404,3405],{},"components\u002FAppHeader.vue"," and the layer also provides ",[63,3408,3405],{},", the project version takes precedence. This makes layers safe to consume — you can always override individual pieces without modifying the layer.",[26,3411,3413],{"id":3412},"layers-vs-modules-vs-local-structure","Layers vs. Modules vs. Local Structure",[13,3415,3416],{},"Before going further, it helps to draw a clear distinction between three mechanisms that can be confused.",[13,3418,3419,3422,3423,118,3426,3429,3430,3433],{},[17,3420,3421],{},"Nuxt Modules"," are plugins for the build system. They hook into Nuxt's build lifecycle to add features: auto-importing utilities, injecting Vite plugins, generating types, configuring routes. ",[63,3424,3425],{},"@nuxt\u002Fcontent",[63,3427,3428],{},"@nuxt\u002Fimage",", and ",[63,3431,3432],{},"@nuxt\u002Fui"," are all modules. Modules operate at build time and typically do not ship runtime Vue components directly — they configure and extend the framework itself.",[13,3435,3436,3439],{},[17,3437,3438],{},"Nuxt Layers"," operate at the application level. They ship actual runtime code: components, composables, pages, layouts, server routes. A layer is something you could deploy as a standalone Nuxt app. A module generally cannot be.",[13,3441,3442,3445],{},[17,3443,3444],{},"Standard project structure"," (components, composables, and pages within the same project) is correct for logic that belongs to a single application. Layers become the right tool when that logic needs to be shared across multiple applications or encapsulated as a reusable base.",[179,3447,3448,3463],{},[182,3449,3450],{},[185,3451,3452,3454,3457,3460],{},[188,3453],{},[188,3455,3456],{},"Module",[188,3458,3459],{},"Layer",[188,3461,3462],{},"Local structure",[196,3464,3465,3479,3493,3506],{},[185,3466,3467,3470,3473,3476],{},[201,3468,3469],{},"Operates at",[201,3471,3472],{},"Build time",[201,3474,3475],{},"Runtime + build",[201,3477,3478],{},"Runtime",[185,3480,3481,3484,3487,3490],{},[201,3482,3483],{},"Provides",[201,3485,3486],{},"Framework integrations",[201,3488,3489],{},"Full app artifacts",[201,3491,3492],{},"App artifacts",[185,3494,3495,3498,3500,3503],{},[201,3496,3497],{},"Shareable via",[201,3499,393],{},[201,3501,3502],{},"npm, git, local path",[201,3504,3505],{},"Local only",[185,3507,3508,3511,3514,3517],{},[201,3509,3510],{},"Right for",[201,3512,3513],{},"Tooling, DX features",[201,3515,3516],{},"Shared app logic",[201,3518,3519],{},"Single-app code",[26,3521,3523],{"id":3522},"how-layer-priority-works","How Layer Priority Works",[13,3525,3526],{},"When multiple layers extend each other, Nuxt establishes a clear precedence order:",[2954,3528,3529,3535,3543],{},[44,3530,3531,3534],{},[17,3532,3533],{},"Project files"," — highest priority",[44,3536,3537,3542],{},[17,3538,3539,113],{},[63,3540,3541],{},"~~\u002Flayers\u002F"," — auto-scanned, sorted alphabetically in reverse (Z before A); numeric prefixes let you control order explicitly",[44,3544,3545,3551],{},[17,3546,3547,3550],{},[63,3548,3549],{},"extends"," array"," — first entry has higher priority than subsequent entries",[13,3553,3554],{},"This means you can rely on the \"project always wins\" guarantee when building base layers. A consumer project can override any single component, page, or composable from a layer without forking the layer or managing merge conflicts. Modules that need to be disabled in a consuming project can be turned off individually:",[333,3556,3558],{"className":427,"code":3557,"language":429,"meta":338,"style":338},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  extends: ['.\u002Fbase-layer'],\n  image: false, \u002F\u002F disable @nuxt\u002Fimage inherited from the layer\n})\n",[63,3559,3560,3564,3576,3594,3610],{"__ignoreMap":338},[342,3561,3562],{"class":344,"line":345},[342,3563,3203],{"class":436},[342,3565,3566,3568,3570,3572,3574],{"class":344,"line":359},[342,3567,479],{"class":442},[342,3569,482],{"class":442},[342,3571,3212],{"class":362},[342,3573,487],{"class":450},[342,3575,490],{"class":446},[342,3577,3578,3580,3582,3584,3586,3588,3590,3592],{"class":344,"line":469},[342,3579,3221],{"class":496},[342,3581,500],{"class":446},[342,3583,3226],{"class":450},[342,3585,3229],{"class":446},[342,3587,3232],{"class":352},[342,3589,3229],{"class":446},[342,3591,1212],{"class":450},[342,3593,527],{"class":446},[342,3595,3596,3599,3601,3605,3607],{"class":344,"line":476},[342,3597,3598],{"class":496},"  image",[342,3600,500],{"class":446},[342,3602,3604],{"class":3603},"sfNiH"," false",[342,3606,1047],{"class":446},[342,3608,3609],{"class":436}," \u002F\u002F disable @nuxt\u002Fimage inherited from the layer\n",[342,3611,3612,3614],{"class":344,"line":493},[342,3613,703],{"class":446},[342,3615,706],{"class":450},[26,3617,3619],{"id":3618},"real-world-use-cases","Real-World Use Cases",[328,3621,3623],{"id":3622},"multi-tenant-saas-applications","Multi-Tenant SaaS Applications",[13,3625,3626,3627,3630,3631,3633],{},"In a SaaS product where each customer receives a branded variant of the same application, layers provide a clean separation between the core product and tenant-specific customizations. The base layer contains application logic, data fetching patterns, and a default UI. Each tenant layer extends the base and overrides only what differs: a branded ",[63,3628,3629],{},"AppHeader.vue",", a different default layout, and tenant-specific ",[63,3632,3382],{}," values.",[333,3635,3638],{"className":3636,"code":3637,"language":374},[372],"packages\u002F\n  core-layer\u002F            ← shared product logic\n    components\u002F\n    composables\u002F\n    pages\u002F\n    nuxt.config.ts\n\n  tenant-a\u002F              ← extends core-layer\n    components\u002F          ← brand-specific overrides\n    app.config.ts        ← tenant theme, copy, feature flags\n    nuxt.config.ts       ← extends: ['..\u002Fcore-layer']\n\n  tenant-b\u002F\n    ...\n",[63,3639,3637],{"__ignoreMap":338},[13,3641,3642],{},"This keeps the per-tenant surface area small while giving each tenant full flexibility where it matters.",[328,3644,3646],{"id":3645},"shared-design-system","Shared Design System",[13,3648,3649],{},"For organizations running multiple frontend applications — a marketing site, a customer portal, an internal dashboard — a shared design system layer distributes a component library without the overhead of publishing and versioning an npm package on every change.",[13,3651,3652],{},"The design system layer ships components, CSS tokens, and default configurations. Each consuming application extends it and overrides specific components only when its context requires deviation from the system. Because the layer lives in the same monorepo, changes propagate immediately without a publish\u002Finstall cycle.",[328,3654,3656],{"id":3655},"enterprise-scale-frontend-architecture","Enterprise-Scale Frontend Architecture",[13,3658,3659],{},"Large engineering teams often split domain ownership across squads. A platform team maintains the base layer: authentication flows, error boundary handling, analytics instrumentation, and shared API utilities. Product squads extend the base layer with their own domain-specific pages and composables. This eliminates configuration drift between squads without requiring a centralized monolithic shared package:",[333,3661,3664],{"className":3662,"code":3663,"language":374},[372],"layers\u002F\n  platform\u002F              ← auth, error handling, analytics\n    composables\u002F\n    plugins\u002F\n    server\u002F\n    nuxt.config.ts\n\n  design-system\u002F         ← UI components, tokens\n    components\u002F\n    assets\u002F\n    nuxt.config.ts\n\napps\u002F\n  portal\u002F\n    nuxt.config.ts       ← extends: ['..\u002F..\u002Flayers\u002Fplatform', '..\u002F..\u002Flayers\u002Fdesign-system']\n\n  marketing\u002F\n    nuxt.config.ts\n",[63,3665,3663],{"__ignoreMap":338},[26,3667,3669],{"id":3668},"layer-distribution","Layer Distribution",[13,3671,3672],{},"Layers are not limited to local paths. Nuxt supports three distribution methods:",[13,3674,3675],{},[17,3676,3677],{},"Local path (monorepo)",[333,3679,3681],{"className":427,"code":3680,"language":429,"meta":338,"style":338},"extends: ['.\u002Fbase-layer']\n",[63,3682,3683],{"__ignoreMap":338},[342,3684,3685,3687,3689,3691,3693,3695,3697],{"class":344,"line":345},[342,3686,3549],{"class":348},[342,3688,500],{"class":446},[342,3690,3226],{"class":450},[342,3692,3229],{"class":446},[342,3694,3232],{"class":352},[342,3696,3229],{"class":446},[342,3698,3699],{"class":450},"]\n",[13,3701,3702,3705],{},[17,3703,3704],{},"Git repository"," (GitHub, GitLab, Bitbucket with branch or tag pinning)",[333,3707,3709],{"className":427,"code":3708,"language":429,"meta":338,"style":338},"extends: ['github:org\u002Fnuxt-base-layer#v2.1.0']\n",[63,3710,3711],{"__ignoreMap":338},[342,3712,3713,3715,3717,3719,3721,3724,3726],{"class":344,"line":345},[342,3714,3549],{"class":348},[342,3716,500],{"class":446},[342,3718,3226],{"class":450},[342,3720,3229],{"class":446},[342,3722,3723],{"class":352},"github:org\u002Fnuxt-base-layer#v2.1.0",[342,3725,3229],{"class":446},[342,3727,3699],{"class":450},[13,3729,3730],{},[17,3731,3732],{},"npm package",[333,3734,3736],{"className":427,"code":3735,"language":429,"meta":338,"style":338},"extends: ['@your-org\u002Fnuxt-base-layer']\n",[63,3737,3738],{"__ignoreMap":338},[342,3739,3740,3742,3744,3746,3748,3751,3753],{"class":344,"line":345},[342,3741,3549],{"class":348},[342,3743,500],{"class":446},[342,3745,3226],{"class":450},[342,3747,3229],{"class":446},[342,3749,3750],{"class":352},"@your-org\u002Fnuxt-base-layer",[342,3752,3229],{"class":446},[342,3754,3699],{"class":450},[13,3756,3757,3758,3760,3761,3764,3765,3768],{},"For npm packages, the ",[63,3759,140],{}," should set ",[63,3762,3763],{},"\"main\": \".\u002Fnuxt.config.ts\""," so Nuxt resolves the layer entry point correctly. Access to private git repositories requires the ",[63,3766,3767],{},"GIGET_AUTH=\u003Ctoken>"," environment variable.",[26,3770,3772],{"id":3771},"dependency-and-path-resolution","Dependency and Path Resolution",[13,3774,3775],{},"Two areas require attention when authoring layers.",[13,3777,3778,3781,3782,20,3785,3788,3789,3792,3793,3795,3796,3798,3799,3802,3803,3805,3806,3809],{},[17,3779,3780],{},"Aliases resolve to the consumer project, not the layer."," The ",[63,3783,3784],{},"~\u002F",[63,3786,3787],{},"@\u002F"," aliases always point to the root of the project consuming the layer, not to the layer's own root. If a composable inside a layer imports ",[63,3790,3791],{},"~\u002Futils\u002Fformat",", it resolves against the consumer's ",[63,3794,3370],{}," directory. This is intentional — it allows consumer projects to provide implementations that layers can rely on — but it produces confusing resolution failures if you assume ",[63,3797,3784],{}," refers to the layer itself. For internal layer imports, use relative paths or set a named alias by adding ",[63,3800,3801],{},"$meta: { name: 'my-layer' }"," to the layer's ",[63,3804,134],{},", which generates a ",[63,3807,3808],{},"#layers\u002Fmy-layer"," alias that works across boundaries.",[13,3811,3812,3815,3816,3819,3820,3823],{},[17,3813,3814],{},"Remote layer dependencies must be production dependencies."," Remote layers installed from npm or git cannot hoist ",[63,3817,3818],{},"devDependencies"," into the consumer project. Any package a layer imports at runtime must be listed in ",[63,3821,3822],{},"dependencies"," so the consumer's package manager installs it.",[26,3825,3827],{"id":3826},"benefits-from-an-engineering-perspective","Benefits from an Engineering Perspective",[13,3829,3830,3833],{},[17,3831,3832],{},"Maintainability at scale."," Shared logic lives in one place. A bug fix or interface change in a base composable propagates to all consuming applications automatically, rather than requiring synchronized updates across repositories.",[13,3835,3836,3839],{},[17,3837,3838],{},"Separation of concerns."," Platform-level concerns — authentication, telemetry, error boundaries — are cleanly separated from product features. This mirrors the architectural principle of isolating stable infrastructure from volatile business logic.",[13,3841,3842,3845],{},[17,3843,3844],{},"Team autonomy."," Each squad owns its layer. Shared layers are maintained by the team responsible for the shared surface, and changes propagate through the dependency graph rather than via manual copy-paste coordination.",[13,3847,3848,3851,3852,3854],{},[17,3849,3850],{},"Consistent defaults."," A shared ",[63,3853,134],{}," in the base layer ensures all consuming applications start from the same module configuration, runtime config shape, and build settings. Configuration drift is opt-in, not accidental.",[26,3856,3858],{"id":3857},"limitations-and-trade-offs","Limitations and Trade-offs",[13,3860,3861,3864],{},[17,3862,3863],{},"Complexity overhead."," Layers introduce indirection. When a component or composable is not in the local project directory, developers need to know which layer provided it and where that layer lives. Debugging resolution-order issues can be non-obvious, especially when multiple layers extend each other.",[13,3866,3867,3870],{},[17,3868,3869],{},"Architectural discipline required."," The \"project always wins\" model is powerful but places responsibility on the layer author to design clear contracts. A layer that exposes too much internal coupling makes selective overriding difficult. Good layer design requires the same discipline as good library design — clear public surfaces, minimal internal dependencies, stable interfaces.",[13,3872,3873,3876],{},[17,3874,3875],{},"Build tooling setup."," In monorepos, configuring TypeScript path mappings and IDE auto-import discovery to correctly understand layer aliases requires deliberate setup. HMR behavior across layer boundaries can also be slower than changes within a single project.",[13,3878,3879,3882],{},[17,3880,3881],{},"Ecosystem coverage varies."," Not all Nuxt modules have been tested extensively against layer configurations. Edge cases with module initialization order and layer-provided configurations exist and may require workarounds in complex setups.",[13,3884,3885],{},"For single-application projects, this level of abstraction rarely pays off. Layers are most clearly worth the investment when you have multiple applications sharing a consistent base, or when you want to enforce organizational standards programmatically rather than through documentation alone.",[26,3887,3889],{"id":3888},"getting-started","Getting Started",[13,3891,3892],{},"A minimal working layer is a single file:",[333,3894,3896],{"className":427,"code":3895,"language":429,"meta":338,"style":338},"\u002F\u002F base-layer\u002Fnuxt.config.ts\nexport default defineNuxtConfig({})\n",[63,3897,3898,3903],{"__ignoreMap":338},[342,3899,3900],{"class":344,"line":345},[342,3901,3902],{"class":436},"\u002F\u002F base-layer\u002Fnuxt.config.ts\n",[342,3904,3905,3907,3909,3911,3913,3916],{"class":344,"line":359},[342,3906,479],{"class":442},[342,3908,482],{"class":442},[342,3910,3212],{"class":362},[342,3912,487],{"class":450},[342,3914,3915],{"class":446},"{}",[342,3917,706],{"class":450},[13,3919,3920,3921,3923,3924,500],{},"From there, add components, composables, or pages as the use case requires. The consumer project references it via the ",[63,3922,3549],{}," array in its own ",[63,3925,134],{},[333,3927,3928],{"className":427,"code":3196,"language":429,"meta":338,"style":338},[63,3929,3930,3934,3946,3964],{"__ignoreMap":338},[342,3931,3932],{"class":344,"line":345},[342,3933,3203],{"class":436},[342,3935,3936,3938,3940,3942,3944],{"class":344,"line":359},[342,3937,479],{"class":442},[342,3939,482],{"class":442},[342,3941,3212],{"class":362},[342,3943,487],{"class":450},[342,3945,490],{"class":446},[342,3947,3948,3950,3952,3954,3956,3958,3960,3962],{"class":344,"line":469},[342,3949,3221],{"class":496},[342,3951,500],{"class":446},[342,3953,3226],{"class":450},[342,3955,3229],{"class":446},[342,3957,3232],{"class":352},[342,3959,3229],{"class":446},[342,3961,1212],{"class":450},[342,3963,527],{"class":446},[342,3965,3966,3968],{"class":344,"line":476},[342,3967,703],{"class":446},[342,3969,706],{"class":450},[13,3971,3972,3973,3976,3977,500],{},"To control the name and generate a stable ",[63,3974,3975],{},"#layers\u002F\u003Cname>"," alias, add ",[63,3978,3979],{},"$meta",[333,3981,3983],{"className":427,"code":3982,"language":429,"meta":338,"style":338},"\u002F\u002F base-layer\u002Fnuxt.config.ts\nexport default defineNuxtConfig({\n  $meta: { name: 'base' },\n})\n",[63,3984,3985,3989,4001,4026],{"__ignoreMap":338},[342,3986,3987],{"class":344,"line":345},[342,3988,3902],{"class":436},[342,3990,3991,3993,3995,3997,3999],{"class":344,"line":359},[342,3992,479],{"class":442},[342,3994,482],{"class":442},[342,3996,3212],{"class":362},[342,3998,487],{"class":450},[342,4000,490],{"class":446},[342,4002,4003,4006,4008,4010,4013,4015,4018,4021,4023],{"class":344,"line":469},[342,4004,4005],{"class":496},"  $meta",[342,4007,500],{"class":446},[342,4009,447],{"class":446},[342,4011,4012],{"class":496}," name",[342,4014,500],{"class":446},[342,4016,4017],{"class":446}," '",[342,4019,4020],{"class":352},"base",[342,4022,3229],{"class":446},[342,4024,4025],{"class":446}," },\n",[342,4027,4028,4030],{"class":344,"line":476},[342,4029,703],{"class":446},[342,4031,706],{"class":450},[13,4033,4034,4035,4042,4043,4045,4046,4049],{},"The official documentation at ",[4036,4037,4041],"a",{"href":4038,"rel":4039},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fguide\u002Fgoing-further\u002Flayers",[4040],"nofollow","nuxt.com\u002Fdocs\u002Fguide\u002Fgoing-further\u002Flayers"," covers the full list of auto-scanned directories, advanced ",[63,4044,3979],{}," configuration, the ",[63,4047,4048],{},"getLayerDirectories()"," Kit utility for module authors, and examples of npm-published layers.",[13,4051,4052],{},"If you are evaluating Nuxt Layers as part of a larger frontend architecture decision — monorepo setup, multi-app consistency, or team-scale component sharing — and want an experienced perspective, the Epicmax team has worked on large Nuxt projects across a range of industries.",[3114,4054],{"label":3116,"to":3117},[3119,4056,4057],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":338,"searchDepth":469,"depth":469,"links":4059},[4060,4063,4064,4065,4070,4071,4072,4073,4074],{"id":3182,"depth":359,"text":3183,"children":4061},[4062],{"id":3259,"depth":469,"text":3260},{"id":3412,"depth":359,"text":3413},{"id":3522,"depth":359,"text":3523},{"id":3618,"depth":359,"text":3619,"children":4066},[4067,4068,4069],{"id":3622,"depth":469,"text":3623},{"id":3645,"depth":469,"text":3646},{"id":3655,"depth":469,"text":3656},{"id":3668,"depth":359,"text":3669},{"id":3771,"depth":359,"text":3772},{"id":3826,"depth":359,"text":3827},{"id":3857,"depth":359,"text":3858},{"id":3888,"depth":359,"text":3889},"A technical guide to Nuxt Layers: how this architectural feature enables code reuse, modular design, and scalable frontend architecture across Nuxt 3 applications.",{},"\u002Fblog\u002Fnuxt-layers","10 min read",{"title":3168,"description":4075},"blog\u002Fnuxt-layers",[4082,4083,3164],"Nuxt 3","Architecture","zN9lCJI0jv20C0NYzlK1xyLGuj0qpn_wbmjna9qpr-g",{"id":4086,"title":4087,"author":7,"authorSlug":8,"body":4088,"date":4329,"description":4330,"extension":3154,"faq":8,"featured":3155,"image":4331,"meta":4332,"navigation":472,"path":4333,"readTime":8,"seo":4334,"stem":4335,"tags":8,"__hash__":4336},"blog\u002Fblog\u002Fnuxt4-migration.md","Time to Migrate Your App to Nuxt 4 Before January 2026",{"type":10,"value":4089,"toc":4321},[4090,4097,4100,4104,4107,4117,4121,4124,4156,4160,4163,4168,4215,4219,4230,4234,4240,4243,4248,4265,4270,4284,4288,4308,4319],[13,4091,4092,4093,4096],{},"If you are reading this in December 2025, you have about ",[17,4094,4095],{},"one and a half months left"," before official maintenance for Nuxt 3 ends. This makes upgrading to Nuxt 4 not just a good idea but a necessary step for companies that care about long term stability, security, and compatibility.",[13,4098,4099],{},"Below is a complete overview of what changed in Nuxt 4, the official lifecycle timeline, and why migrating now is the safest strategic move for any serious Vue and Nuxt development team.",[26,4101,4103],{"id":4102},"evolution-from-nuxt-3-to-nuxt-4","Evolution from Nuxt 3 to Nuxt 4",[13,4105,4106],{},"Nuxt 3 (released in late 2022) established a strong foundation for modern applications with Vue 3, Nitro, Vite, the Composition API, DevTools, and built in TypeScript.",[13,4108,4109,4110,158],{},"Nuxt 4 builds on this foundation and improves the developer experience through cleaner defaults, stronger type safety, and faster tooling. These enhancements are not feature bloat but practical improvements that make Nuxt upgrades smoother and long term project maintenance more reliable, which is especially important for teams planning Nuxt migration or investing in ",[17,4111,4112],{},[4036,4113,4116],{"href":4114,"rel":4115},"https:\u002F\u002Fepicmax.co\u002Fnuxt-development-services",[4040],"Nuxt development services",[26,4118,4120],{"id":4119},"whats-new-in-nuxt-4","What's New in Nuxt 4",[13,4122,4123],{},"Here’s what sets Nuxt 4 apart:",[41,4125,4126,4132,4138,4144,4150],{},[44,4127,4128,4131],{},[17,4129,4130],{},"Cleaner Project Structure",": The new app\u002F directory separates app logic from configs, public assets, and node modules - making modules faster to watch and IDE support more accurate.",[44,4133,4134,4137],{},[17,4135,4136],{},"Smarter Data Fetching",": Data hooks (useAsyncData, useFetch) now share data via a singleton layer, clean up after unmounting, and support reactive keys for auto-refetching with better caching strategies.",[44,4139,4140,4143],{},[17,4141,4142],{},"Better TypeScript Integration",": Nuxt now generates separate TS configs for app, server, shared, and builder contexts. This reduces cross-environment errors and simplifies type safety.",[44,4145,4146,4149],{},[17,4147,4148],{},"Faster CLI & Workflow",": Cold-starts are quicker, thanks to Node.js compile cache, native file watching, and socket-based communication between CLI and Vite. The result: noticeably smoother dev experience.",[44,4151,4152,4155],{},[17,4153,4154],{},"Other Enhancements",": Improved default UI templates, normalized component naming, refactored head management with Unhead v2, prerendering via Nitro, and performance-conscious defaults (shallow reactivity, granular caching, etc.).",[26,4157,4159],{"id":4158},"nuxt-3-support-timeline-only-15-months-left","Nuxt 3 Support Timeline: Only 1.5 Months Left",[13,4161,4162],{},"Here is what the Nuxt team officially states in the public roadmap.",[13,4164,4165],{},[17,4166,4167],{},"Official Nuxt Lifecycle",[41,4169,4170,4178,4185,4193,4199,4212],{},[44,4171,4172,4174,4175,158],{},[17,4173,4082],{}," is in maintenance mode and will continue receiving fixes and backports ",[17,4176,4177],{},"until January 31, 2026",[44,4179,4180,4181,4184],{},"After that date, ",[17,4182,4183],{},"Nuxt 3 enters EOL"," (End of Life). No more fixes or compatibility updates.",[44,4186,4187,4189,4190,158],{},[17,4188,23],{}," became the new stable major version on ",[17,4191,4192],{},"July 15, 2025",[44,4194,4195,4196,158],{},"According to Nuxt’s support policy, each major version receives at least ",[17,4197,4198],{},"6 months of support after the next major version is released",[44,4200,4201,4204,4205,4208,4209,158],{},[17,4202,4203],{},"Nuxt 5"," is scheduled for ",[17,4206,4207],{},"Q4 2025",", which means ",[17,4210,4211],{},"Nuxt 4 will remain supported at least until mid 2026",[44,4213,4214],{},"Nuxt 5 is expected to become the next long term stable baseline with an extended support model similar to LTS.",[26,4216,4218],{"id":4217},"what-this-means-for-nuxt-migration-and-long-term-stability","What This Means for Nuxt Migration and Long Term Stability",[41,4220,4221,4224,4227],{},[44,4222,4223],{},"If you are on Nuxt 3 today, support ends in January 2026. After that, your project runs on an unsupported version with no security or maintenance updates.",[44,4225,4226],{},"Migrating to Nuxt 4 gives you a supported and actively maintained framework with guaranteed stability into mid 2026 and beyond.",[44,4228,4229],{},"Once Nuxt 5 becomes stable, you will have a clear upgrade path from Nuxt 4 with strong ecosystem support.",[26,4231,4233],{"id":4232},"should-you-migrate-from-nuxt-3-to-nuxt-4-now","Should You Migrate from Nuxt 3 to Nuxt 4 Now?",[13,4235,4236,4237,158],{},"In December 2025 the answer is simple: ",[17,4238,4239],{},"yes, it is time to migrate",[13,4241,4242],{},"There is no longer a long runway. Waiting until January risks facing module incompatibilities, missing security patches, and unnecessary tech debt.",[13,4244,4245],{},[17,4246,4247],{},"Migrate to Nuxt 4 now if you want:",[41,4249,4250,4253,4256,4259,4262],{},[44,4251,4252],{},"the improved app\u002F directory structure",[44,4254,4255],{},"smarter data fetching and predictable caching",[44,4257,4258],{},"stronger TypeScript ergonomics",[44,4260,4261],{},"faster development tooling",[44,4263,4264],{},"an officially supported version past January 2026",[13,4266,4267],{},[17,4268,4269],{},"You can delay slightly only if:",[41,4271,4272,4275,4278,4281],{},[44,4273,4274],{},"your app is in a critical release window",[44,4276,4277],{},"you use modules or custom layers that still need updates",[44,4279,4280],{},"you are already scheduling the migration for early 2026",[44,4282,4283],{},"But delaying until after January 31, 2026 is not recommended, because Nuxt 3 becomes fully unsupported.",[26,4285,4287],{"id":4286},"recommended-path-for-new-and-existing-projects","Recommended Path for New and Existing Projects",[41,4289,4290,4296,4302],{},[44,4291,4292,4295],{},[17,4293,4294],{},"New projects"," should start on Nuxt 4 or Nuxt 5 (if stable at the time).",[44,4297,4298,4301],{},[17,4299,4300],{},"Existing Nuxt 3 projects"," should migrate within the next few weeks.",[44,4303,4304,4307],{},[17,4305,4306],{},"Teams prioritizing long term stability"," should monitor the Nuxt 5 release and plan a follow up upgrade once the ecosystem stabilizes.",[13,4309,4310],{},[17,4311,4312,4313,4318],{},"If you need expert Nuxt consulting, migration planning, or custom ",[4036,4314,4317],{"href":4315,"rel":4316},"https:\u002F\u002Fepicmax.co\u002F",[4040],"Vue and Nuxt development",", book a free call with our tech lead.",[3114,4320],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":4322},[4323,4324,4325,4326,4327,4328],{"id":4102,"depth":359,"text":4103},{"id":4119,"depth":359,"text":4120},{"id":4158,"depth":359,"text":4159},{"id":4217,"depth":359,"text":4218},{"id":4232,"depth":359,"text":4233},{"id":4286,"depth":359,"text":4287},"2025-12-10","This guide covers everything you need to know about Nuxt 4 migration — what changed, and how to upgrade app before it runs on an unsupported version.","\u002Fimages\u002Fblog\u002Fnuxt4-migration\u002Fcard.png",{},"\u002Fblog\u002Fnuxt4-migration",{"title":4087,"description":4330},"blog\u002Fnuxt4-migration","Wvckn-h27BYVOyRmNQvoR7mgRfbOPK6gfanZGZNnzl4",{"id":4338,"title":4339,"author":7,"authorSlug":8,"body":4340,"date":6007,"description":6008,"extension":3154,"faq":8,"featured":3155,"image":6009,"meta":6010,"navigation":472,"path":6011,"readTime":8,"seo":6012,"stem":6013,"tags":8,"__hash__":6014},"blog\u002Fblog\u002Fcomposition-api.md","Should You Refactor to Vue 3 Composition API? Practical Guide for Modern Vue Development",{"type":10,"value":4341,"toc":5986},[4342,4345,4352,4359,4362,4365,4385,4389,4410,4414,4486,4492,4496,4499,4679,4682,4837,4840,4844,4847,4865,4958,4961,4965,4973,5135,5140,5144,5147,5158,5164,5167,5171,5174,5191,5194,5198,5201,5212,5241,5244,5326,5329,5333,5336,5347,5350,5354,5365,5371,5376,5452,5455,5459,5462,5470,5473,5477,5480,5491,5494,5498,5501,5504,5811,5814,5818,5832,5836,5847,5851,5854,5865,5870,5874,5877,5891,5894,5899,5910,5915,5919,5922,5925,5945,5949,5968,5971,5981,5983],[13,4343,4344],{},"In Vue 3, the Composition API emerged as a more flexible and scalable alternative to the traditional Options API. It offers better maintainability and modularity, especially for larger or fast-growing applications.",[13,4346,4347,4348,4351],{},"But here’s the key point: the ",[17,4349,4350],{},"Options API isn’t deprecated",". It remains fully supported and works perfectly well for many real-world use cases.",[13,4353,4354,4355,4358],{},"If you are currently ",[17,4356,4357],{},"migrating to Vue 3",", this information is especially relevant. Understanding when to keep the Options API and when to adopt the Composition API can save you weeks of unnecessary refactoring and help you design a cleaner long-term architecture.",[13,4360,4361],{},"So the big question is: should you refactor your existing Vue 3 codebase from the Options API to the Composition API?",[13,4363,4364],{},"This guide is here to help tech leads, senior engineers, and curious developers evaluate the trade-offs with clarity and practical context. Let’s explore what you stand to gain, and what risks or complexities may appear, when switching to the Composition API.",[13,4366,4367,4368,158,4374,4377,4378],{},"This guide is written by Epicmax, a ",[17,4369,4370],{},[4036,4371,4373],{"href":4315,"rel":4372},[4040],"Vue.js development agency",[4375,4376],"br",{},"\nIf you need help with frontend migration, book a ",[17,4379,4380],{},[4036,4381,4384],{"href":4382,"rel":4383},"https:\u002F\u002Fepicmax.co\u002Fcontacts",[4040],"consultation with our Vue.js expert.",[26,4386,4388],{"id":4387},"tldr","TL;DR",[41,4390,4391,4394,4397,4400,4403],{},[44,4392,4393],{},"✅ Composition API gives better scalability, reusability, and DX",[44,4395,4396],{},"🛡️ Options API still makes sense for small or stable projects",[44,4398,4399],{},"🤪 Composables make code more testable and modular",[44,4401,4402],{},"🔧 Composition API is TypeScript-native and future-proof",[44,4404,4405,4406,4409],{},"⚖️ You can ",[17,4407,4408],{},"migrate Vue.js app gradually"," without rewriting your whole app",[26,4411,4413],{"id":4412},"key-differences-options-vs-composition","🧠 Key Differences: Options vs Composition",[179,4415,4416,4429],{},[182,4417,4418],{},[185,4419,4420,4423,4426],{},[188,4421,4422],{},"Feature",[188,4424,4425],{},"Options API",[188,4427,4428],{},"Composition API",[196,4430,4431,4442,4453,4464,4475],{},[185,4432,4433,4436,4439],{},[201,4434,4435],{},"Code Organization",[201,4437,4438],{},"Structured by option type (data, etc.)",[201,4440,4441],{},"Grouped by feature logic",[185,4443,4444,4447,4450],{},[201,4445,4446],{},"Logic Reuse",[201,4448,4449],{},"Mixins (risk of conflicts)",[201,4451,4452],{},"Composables (modular, testable)",[185,4454,4455,4458,4461],{},[201,4456,4457],{},"TypeScript Support",[201,4459,4460],{},"More boilerplate, less inference",[201,4462,4463],{},"Strong inference, clean typing",[185,4465,4466,4469,4472],{},[201,4467,4468],{},"Learning Curve",[201,4470,4471],{},"Beginner-friendly",[201,4473,4474],{},"Needs JS\u002Freactivity understanding",[185,4476,4477,4480,4483],{},[201,4478,4479],{},"Ecosystem Support",[201,4481,4482],{},"Works, but fading",[201,4484,4485],{},"Native to Nuxt 3, Vue Router, Pinia, etc.",[13,4487,4488,4491],{},[17,4489,4490],{},"Official guidance:"," For small\u002Fmedium apps, Options API is fine. For full-scale applications, Composition API is preferred.",[26,4493,4495],{"id":4494},"maintainability-group-logic-by-feature","🔍 Maintainability: Group Logic by Feature",[13,4497,4498],{},"Options API forces you to split related logic:",[333,4500,4504],{"className":4501,"code":4502,"language":4503,"meta":338,"style":338},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Options API\nexport default {\n  data() {\n    return { name: '', email: '' }\n  },\n  computed: {\n    isValid() { return this.name && this.email.includes('@') }\n  },\n  methods: {\n    submit() {\n      if (this.isValid) alert(`Submitting: ${this.name}`)\n    }\n  }\n}\n","js",[63,4505,4506,4511,4519,4528,4552,4556,4565,4609,4613,4622,4631,4666,4671,4675],{"__ignoreMap":338},[342,4507,4508],{"class":344,"line":345},[342,4509,4510],{"class":436},"\u002F\u002F Options API\n",[342,4512,4513,4515,4517],{"class":344,"line":359},[342,4514,479],{"class":442},[342,4516,482],{"class":442},[342,4518,503],{"class":446},[342,4520,4521,4524,4526],{"class":344,"line":469},[342,4522,4523],{"class":496},"  data",[342,4525,941],{"class":446},[342,4527,503],{"class":446},[342,4529,4530,4532,4534,4536,4538,4541,4543,4546,4548,4550],{"class":344,"line":476},[342,4531,2791],{"class":442},[342,4533,447],{"class":446},[342,4535,4012],{"class":496},[342,4537,500],{"class":446},[342,4539,4540],{"class":446}," ''",[342,4542,1047],{"class":446},[342,4544,4545],{"class":496}," email",[342,4547,500],{"class":446},[342,4549,4540],{"class":446},[342,4551,2929],{"class":446},[342,4553,4554],{"class":344,"line":493},[342,4555,697],{"class":446},[342,4557,4558,4561,4563],{"class":344,"line":506},[342,4559,4560],{"class":496},"  computed",[342,4562,500],{"class":446},[342,4564,503],{"class":446},[342,4566,4567,4570,4572,4574,4577,4580,4583,4586,4588,4591,4593,4596,4598,4600,4603,4605,4607],{"class":344,"line":530},[342,4568,4569],{"class":496},"    isValid",[342,4571,941],{"class":446},[342,4573,447],{"class":446},[342,4575,4576],{"class":442}," return",[342,4578,4579],{"class":446}," this.",[342,4581,4582],{"class":450},"name",[342,4584,4585],{"class":446}," &&",[342,4587,4579],{"class":446},[342,4589,4590],{"class":450},"email",[342,4592,158],{"class":446},[342,4594,4595],{"class":362},"includes",[342,4597,487],{"class":496},[342,4599,3229],{"class":446},[342,4601,4602],{"class":352},"@",[342,4604,3229],{"class":446},[342,4606,1151],{"class":496},[342,4608,956],{"class":446},[342,4610,4611],{"class":344,"line":540},[342,4612,697],{"class":446},[342,4614,4615,4618,4620],{"class":344,"line":571},[342,4616,4617],{"class":496},"  methods",[342,4619,500],{"class":446},[342,4621,503],{"class":446},[342,4623,4624,4627,4629],{"class":344,"line":600},[342,4625,4626],{"class":496},"    submit",[342,4628,941],{"class":446},[342,4630,503],{"class":446},[342,4632,4633,4636,4638,4641,4644,4646,4649,4651,4653,4656,4658,4660,4662,4664],{"class":344,"line":629},[342,4634,4635],{"class":442},"      if",[342,4637,1138],{"class":496},[342,4639,4640],{"class":446},"this.",[342,4642,4643],{"class":450},"isValid",[342,4645,1151],{"class":496},[342,4647,4648],{"class":362},"alert",[342,4650,487],{"class":496},[342,4652,1913],{"class":446},[342,4654,4655],{"class":352},"Submitting: ",[342,4657,1919],{"class":446},[342,4659,4640],{"class":446},[342,4661,4582],{"class":450},[342,4663,1935],{"class":446},[342,4665,706],{"class":496},[342,4667,4668],{"class":344,"line":659},[342,4669,4670],{"class":446},"    }\n",[342,4672,4673],{"class":344,"line":688},[342,4674,1229],{"class":446},[342,4676,4677],{"class":344,"line":694},[342,4678,956],{"class":446},[13,4680,4681],{},"Composition API lets you keep everything in one place:",[333,4683,4685],{"className":1246,"code":4684,"language":1248,"meta":338,"style":338},"\u003Cscript setup>\nconst name = ref('')\nconst email = ref('')\nconst isValid = computed(() => name.value && email.value.includes('@'))\nfunction submit() {\n  if (isValid.value) alert(`Submitting: ${name.value}`)\n}\n\u003C\u002Fscript>\n",[63,4686,4687,4697,4715,4732,4779,4791,4825,4829],{"__ignoreMap":338},[342,4688,4689,4691,4693,4695],{"class":344,"line":345},[342,4690,1023],{"class":446},[342,4692,1262],{"class":496},[342,4694,1265],{"class":830},[342,4696,1280],{"class":446},[342,4698,4699,4701,4704,4706,4708,4710,4713],{"class":344,"line":359},[342,4700,831],{"class":830},[342,4702,4703],{"class":450}," name ",[342,4705,837],{"class":446},[342,4707,2132],{"class":362},[342,4709,487],{"class":450},[342,4711,4712],{"class":446},"''",[342,4714,706],{"class":450},[342,4716,4717,4719,4722,4724,4726,4728,4730],{"class":344,"line":469},[342,4718,831],{"class":830},[342,4720,4721],{"class":450}," email ",[342,4723,837],{"class":446},[342,4725,2132],{"class":362},[342,4727,487],{"class":450},[342,4729,4712],{"class":446},[342,4731,706],{"class":450},[342,4733,4734,4736,4739,4741,4743,4745,4747,4749,4751,4753,4755,4758,4760,4762,4764,4766,4768,4770,4772,4774,4776],{"class":344,"line":476},[342,4735,831],{"class":830},[342,4737,4738],{"class":450}," isValid ",[342,4740,837],{"class":446},[342,4742,1423],{"class":362},[342,4744,487],{"class":450},[342,4746,941],{"class":446},[342,4748,1011],{"class":830},[342,4750,4012],{"class":450},[342,4752,158],{"class":446},[342,4754,1379],{"class":450},[342,4756,4757],{"class":446},"&&",[342,4759,4545],{"class":450},[342,4761,158],{"class":446},[342,4763,1148],{"class":450},[342,4765,158],{"class":446},[342,4767,4595],{"class":362},[342,4769,487],{"class":450},[342,4771,3229],{"class":446},[342,4773,4602],{"class":352},[342,4775,3229],{"class":446},[342,4777,4778],{"class":450},"))\n",[342,4780,4781,4784,4787,4789],{"class":344,"line":493},[342,4782,4783],{"class":830},"function",[342,4785,4786],{"class":362}," submit",[342,4788,941],{"class":446},[342,4790,503],{"class":446},[342,4792,4793,4795,4797,4799,4801,4803,4805,4807,4809,4811,4813,4815,4817,4819,4821,4823],{"class":344,"line":506},[342,4794,1135],{"class":442},[342,4796,1138],{"class":496},[342,4798,4643],{"class":450},[342,4800,158],{"class":446},[342,4802,1148],{"class":450},[342,4804,1151],{"class":496},[342,4806,4648],{"class":362},[342,4808,487],{"class":496},[342,4810,1913],{"class":446},[342,4812,4655],{"class":352},[342,4814,1919],{"class":446},[342,4816,4582],{"class":450},[342,4818,158],{"class":446},[342,4820,1148],{"class":450},[342,4822,1935],{"class":446},[342,4824,706],{"class":496},[342,4826,4827],{"class":344,"line":530},[342,4828,956],{"class":446},[342,4830,4831,4833,4835],{"class":344,"line":540},[342,4832,1505],{"class":446},[342,4834,1262],{"class":496},[342,4836,1280],{"class":446},[13,4838,4839],{},"Related code stays close — making it easier to read, debug, and extract.",[26,4841,4843],{"id":4842},"️-real-world-wins-modularity-reuse","🛠️ Real-World Wins: Modularity & Reuse",[13,4845,4846],{},"Mixins in Options API are often messy:",[41,4848,4849,4852,4855,4858],{},[44,4850,4851],{},"Hidden dependencies",[44,4853,4854],{},"Naming conflicts",[44,4856,4857],{},"Hard to trace",[44,4859,4860,4861,4864],{},"With Composition API, ",[17,4862,4863],{},"composables"," are just functions:",[333,4866,4868],{"className":4501,"code":4867,"language":4503,"meta":338,"style":338},"\u002F\u002F useFormValidation.ts\nexport function useFormValidation() {\n  const name = ref('')\n  const isValid = computed(() => name.value.length > 2)\n  return { name, isValid }\n}\n",[63,4869,4870,4875,4888,4904,4940,4954],{"__ignoreMap":338},[342,4871,4872],{"class":344,"line":345},[342,4873,4874],{"class":436},"\u002F\u002F useFormValidation.ts\n",[342,4876,4877,4879,4881,4884,4886],{"class":344,"line":359},[342,4878,479],{"class":442},[342,4880,935],{"class":830},[342,4882,4883],{"class":362}," useFormValidation",[342,4885,941],{"class":446},[342,4887,503],{"class":446},[342,4889,4890,4892,4894,4896,4898,4900,4902],{"class":344,"line":469},[342,4891,1101],{"class":830},[342,4893,4012],{"class":450},[342,4895,1107],{"class":446},[342,4897,2132],{"class":362},[342,4899,487],{"class":496},[342,4901,4712],{"class":446},[342,4903,706],{"class":496},[342,4905,4906,4908,4911,4913,4915,4917,4919,4921,4923,4925,4927,4929,4932,4935,4938],{"class":344,"line":476},[342,4907,1101],{"class":830},[342,4909,4910],{"class":450}," isValid",[342,4912,1107],{"class":446},[342,4914,1423],{"class":362},[342,4916,487],{"class":496},[342,4918,941],{"class":446},[342,4920,1011],{"class":830},[342,4922,4012],{"class":450},[342,4924,158],{"class":446},[342,4926,1148],{"class":450},[342,4928,158],{"class":446},[342,4930,4931],{"class":450},"length",[342,4933,4934],{"class":446}," >",[342,4936,4937],{"class":1208}," 2",[342,4939,706],{"class":496},[342,4941,4942,4944,4946,4948,4950,4952],{"class":344,"line":493},[342,4943,948],{"class":442},[342,4945,447],{"class":446},[342,4947,4012],{"class":450},[342,4949,1047],{"class":446},[342,4951,4910],{"class":450},[342,4953,2929],{"class":446},[342,4955,4956],{"class":344,"line":506},[342,4957,956],{"class":446},[13,4959,4960],{},"Now you can reuse logic across components, test in isolation, and eliminate conflicts.",[26,4962,4964],{"id":4963},"testing-more-predictable-with-composables","🤪 Testing: More Predictable with Composables",[41,4966,4967,4970],{},[44,4968,4969],{},"Options API: test everything via the component",[44,4971,4972],{},"Composition API: test logic directly via function",[333,4974,4976],{"className":4501,"code":4975,"language":4503,"meta":338,"style":338},"\u002F\u002F useCounter.ts\nexport function useCounter() {\n  const count = ref(0)\n  const increment = () => count.value++\n  return { count, increment }\n}\n\n\u002F\u002F useCounter.spec.ts\nconst { count, increment } = useCounter()\nexpect(count.value).toBe(0)\nincrement()\nexpect(count.value).toBe(1)\n",[63,4977,4978,4983,4996,5013,5035,5049,5053,5057,5062,5083,5107,5114],{"__ignoreMap":338},[342,4979,4980],{"class":344,"line":345},[342,4981,4982],{"class":436},"\u002F\u002F useCounter.ts\n",[342,4984,4985,4987,4989,4992,4994],{"class":344,"line":359},[342,4986,479],{"class":442},[342,4988,935],{"class":830},[342,4990,4991],{"class":362}," useCounter",[342,4993,941],{"class":446},[342,4995,503],{"class":446},[342,4997,4998,5000,5003,5005,5007,5009,5011],{"class":344,"line":469},[342,4999,1101],{"class":830},[342,5001,5002],{"class":450}," count",[342,5004,1107],{"class":446},[342,5006,2132],{"class":362},[342,5008,487],{"class":496},[342,5010,1209],{"class":1208},[342,5012,706],{"class":496},[342,5014,5015,5017,5020,5022,5024,5026,5028,5030,5032],{"class":344,"line":476},[342,5016,1101],{"class":830},[342,5018,5019],{"class":450}," increment",[342,5021,1107],{"class":446},[342,5023,1008],{"class":446},[342,5025,1011],{"class":830},[342,5027,5002],{"class":450},[342,5029,158],{"class":446},[342,5031,1148],{"class":450},[342,5033,5034],{"class":446},"++\n",[342,5036,5037,5039,5041,5043,5045,5047],{"class":344,"line":493},[342,5038,948],{"class":442},[342,5040,447],{"class":446},[342,5042,5002],{"class":450},[342,5044,1047],{"class":446},[342,5046,5019],{"class":450},[342,5048,2929],{"class":446},[342,5050,5051],{"class":344,"line":506},[342,5052,956],{"class":446},[342,5054,5055],{"class":344,"line":530},[342,5056,473],{"emptyLinePlaceholder":472},[342,5058,5059],{"class":344,"line":540},[342,5060,5061],{"class":436},"\u002F\u002F useCounter.spec.ts\n",[342,5063,5064,5066,5068,5070,5072,5075,5077,5079,5081],{"class":344,"line":571},[342,5065,831],{"class":830},[342,5067,447],{"class":446},[342,5069,5002],{"class":450},[342,5071,1047],{"class":446},[342,5073,5074],{"class":450}," increment ",[342,5076,703],{"class":446},[342,5078,1107],{"class":446},[342,5080,4991],{"class":362},[342,5082,1112],{"class":450},[342,5084,5085,5088,5091,5093,5096,5098,5101,5103,5105],{"class":344,"line":600},[342,5086,5087],{"class":362},"expect",[342,5089,5090],{"class":450},"(count",[342,5092,158],{"class":446},[342,5094,5095],{"class":450},"value)",[342,5097,158],{"class":446},[342,5099,5100],{"class":362},"toBe",[342,5102,487],{"class":450},[342,5104,1209],{"class":1208},[342,5106,706],{"class":450},[342,5108,5109,5112],{"class":344,"line":629},[342,5110,5111],{"class":362},"increment",[342,5113,1112],{"class":450},[342,5115,5116,5118,5120,5122,5124,5126,5128,5130,5133],{"class":344,"line":659},[342,5117,5087],{"class":362},[342,5119,5090],{"class":450},[342,5121,158],{"class":446},[342,5123,5095],{"class":450},[342,5125,158],{"class":446},[342,5127,5100],{"class":362},[342,5129,487],{"class":450},[342,5131,5132],{"class":1208},"1",[342,5134,706],{"class":450},[41,5136,5137],{},[44,5138,5139],{},"Smaller, faster, and cleaner tests.",[26,5141,5143],{"id":5142},"scalability-cleaner-refactors-better-architecture","📈 Scalability: Cleaner Refactors, Better Architecture",[13,5145,5146],{},"When your app grows, Composition API makes it easy to:",[41,5148,5149,5152,5155],{},[44,5150,5151],{},"Extract feature logic to composables",[44,5153,5154],{},"Avoid bloated components",[44,5156,5157],{},"Reorganize without rewriting everything",[13,5159,5160],{},[5161,5162,5163],"em",{},"“After migrating our enterprise app, feature velocity increased and onboarding time dropped.”",[13,5165,5166],{},"— Monterail Team",[26,5168,5170],{"id":5169},"ecosystem-fit-composition-is-the-default","🔐 Ecosystem Fit: Composition Is the Default",[13,5172,5173],{},"Most new Vue tools assume Composition API:",[41,5175,5176,5182,5185,5188],{},[44,5177,5178,5179],{},"✅ Nuxt 3: ",[1262,5180,5181],{"setup":338}," is default",[44,5183,5184],{},"✅ Pinia: Composable stores",[44,5186,5187],{},"✅ Vue Router 4: useRoute() and useRouter()",[44,5189,5190],{},"✅ VueUse: 200+ utility composables",[13,5192,5193],{},"While Options API still works, many libraries assume you're using Composition API, leading to extra boilerplate when integrating.",[26,5195,5197],{"id":5196},"typescript-fewer-hacks-more-safety","🔧 TypeScript: Fewer Hacks, More Safety",[13,5199,5200],{},"Composition API:",[41,5202,5203,5206,5209],{},[44,5204,5205],{},"Native TS support",[44,5207,5208],{},"Less boilerplate",[44,5210,5211],{},"Better autocomplete",[333,5213,5215],{"className":4501,"code":5214,"language":4503,"meta":338,"style":338},"const count = ref\u003Cnumber>(0)\n",[63,5216,5217],{"__ignoreMap":338},[342,5218,5219,5221,5224,5226,5228,5230,5233,5235,5237,5239],{"class":344,"line":345},[342,5220,831],{"class":830},[342,5222,5223],{"class":450}," count ",[342,5225,837],{"class":446},[342,5227,2132],{"class":362},[342,5229,1023],{"class":446},[342,5231,5232],{"class":348},"number",[342,5234,1035],{"class":446},[342,5236,487],{"class":450},[342,5238,1209],{"class":1208},[342,5240,706],{"class":450},[13,5242,5243],{},"Options API supports TypeScript, but it requires more boilerplate and doesn't provide the same inference and generics support as Composition API:",[333,5245,5247],{"className":4501,"code":5246,"language":4503,"meta":338,"style":338},"import { defineComponent, PropType } from 'vue'\n\ndefineComponent({\n  props: {\n    count: Number as PropType\u003Cnumber>\n  }\n})\n",[63,5248,5249,5274,5278,5287,5296,5316,5320],{"__ignoreMap":338},[342,5250,5251,5253,5255,5258,5260,5263,5265,5267,5269,5271],{"class":344,"line":345},[342,5252,443],{"class":442},[342,5254,447],{"class":446},[342,5256,5257],{"class":450}," defineComponent",[342,5259,1047],{"class":446},[342,5261,5262],{"class":450}," PropType",[342,5264,454],{"class":446},[342,5266,457],{"class":442},[342,5268,4017],{"class":446},[342,5270,1248],{"class":352},[342,5272,5273],{"class":446},"'\n",[342,5275,5276],{"class":344,"line":359},[342,5277,473],{"emptyLinePlaceholder":472},[342,5279,5280,5283,5285],{"class":344,"line":469},[342,5281,5282],{"class":362},"defineComponent",[342,5284,487],{"class":450},[342,5286,490],{"class":446},[342,5288,5289,5292,5294],{"class":344,"line":476},[342,5290,5291],{"class":496},"  props",[342,5293,500],{"class":446},[342,5295,503],{"class":446},[342,5297,5298,5301,5303,5306,5308,5310,5312,5314],{"class":344,"line":493},[342,5299,5300],{"class":496},"    count",[342,5302,500],{"class":446},[342,5304,5305],{"class":450}," Number ",[342,5307,1983],{"class":442},[342,5309,5262],{"class":348},[342,5311,1023],{"class":446},[342,5313,5232],{"class":348},[342,5315,1280],{"class":446},[342,5317,5318],{"class":344,"line":506},[342,5319,1229],{"class":446},[342,5321,5322,5324],{"class":344,"line":530},[342,5323,703],{"class":446},[342,5325,706],{"class":450},[13,5327,5328],{},"You’ll write less code and get better DX.",[26,5330,5332],{"id":5331},"️-caveats-of-flexibility","⚖️ Caveats of Flexibility",[13,5334,5335],{},"Composition API gives you power — but with great freedom comes potential chaos:",[41,5337,5338,5341,5344],{},[44,5339,5340],{},"Devs may organize code differently without conventions",[44,5342,5343],{},"Too much logic in setup() can get messy",[44,5345,5346],{},"Onboarding becomes harder without structure",[13,5348,5349],{},"✅ Use ESLint rules, layered structure, and naming conventions to stay sane.",[26,5351,5353],{"id":5352},"reactivity-gotchas","🤯 Reactivity Gotchas",[41,5355,5356,5359,5362],{},[44,5357,5358],{},"ref() vs reactive() can confuse beginners",[44,5360,5361],{},"Destructuring removes reactivity",[44,5363,5364],{},"Don’t forget .value when using ref",[13,5366,5367,5370],{},[17,5368,5369],{},"Tip:"," Use ref for primitives and reactive for objects. Avoid destructuring unless using toRefs() or storeToRefs().",[13,5372,5373],{},[17,5374,5375],{},"Bonus:",[333,5377,5379],{"className":4501,"code":5378,"language":4503,"meta":338,"style":338},"const state = reactive({ count: 0 })\nconst { count } = state \u002F\u002F breaks reactivity\n\n\u002F\u002F Instead, use:\nconst { count } = toRefs(state)\n",[63,5380,5381,5408,5425,5429,5434],{"__ignoreMap":338},[342,5382,5383,5385,5388,5390,5393,5395,5397,5399,5401,5404,5406],{"class":344,"line":345},[342,5384,831],{"class":830},[342,5386,5387],{"class":450}," state ",[342,5389,837],{"class":446},[342,5391,5392],{"class":362}," reactive",[342,5394,487],{"class":450},[342,5396,2086],{"class":446},[342,5398,5002],{"class":496},[342,5400,500],{"class":446},[342,5402,5403],{"class":1208}," 0",[342,5405,454],{"class":446},[342,5407,706],{"class":450},[342,5409,5410,5412,5414,5416,5418,5420,5422],{"class":344,"line":359},[342,5411,831],{"class":830},[342,5413,447],{"class":446},[342,5415,5223],{"class":450},[342,5417,703],{"class":446},[342,5419,1107],{"class":446},[342,5421,5387],{"class":450},[342,5423,5424],{"class":436},"\u002F\u002F breaks reactivity\n",[342,5426,5427],{"class":344,"line":469},[342,5428,473],{"emptyLinePlaceholder":472},[342,5430,5431],{"class":344,"line":476},[342,5432,5433],{"class":436},"\u002F\u002F Instead, use:\n",[342,5435,5436,5438,5440,5442,5444,5446,5449],{"class":344,"line":493},[342,5437,831],{"class":830},[342,5439,447],{"class":446},[342,5441,5223],{"class":450},[342,5443,703],{"class":446},[342,5445,1107],{"class":446},[342,5447,5448],{"class":362}," toRefs",[342,5450,5451],{"class":450},"(state)\n",[13,5453,5454],{},"Learn more in the official Vue reactivity documentation.",[26,5456,5458],{"id":5457},"team-skillset-onboarding","🧠 Team Skillset & Onboarding",[13,5460,5461],{},"Composition API assumes:",[41,5463,5464,5467],{},[44,5465,5466],{},"Deeper JS knowledge (closures, reactivity, function scope)",[44,5468,5469],{},"Familiarity with typing patterns (TS)",[13,5471,5472],{},"For junior-heavy teams or short-term projects, Options API may still be faster.",[26,5474,5476],{"id":5475},"migration-strategy-no-need-for-a-big-bang","🔄 Migration Strategy: No Need for a Big Bang",[13,5478,5479],{},"Vue 3 lets you mix both APIs. Start small:",[41,5481,5482,5485,5488],{},[44,5483,5484],{},"Write new features with Composition API",[44,5486,5487],{},"Refactor old components only when touched",[44,5489,5490],{},"Add setup() to Options components to ease in",[13,5492,5493],{},"Bonus: you can train the team gradually — no rewrite required.",[26,5495,5497],{"id":5496},"️-using-composables-in-options-api-components-hybrid-pain","🛡️ Using Composables in Options API Components (Hybrid Pain)",[13,5499,5500],{},"In Vue 3, composables replaced mixins — and they only work inside setup().",[13,5502,5503],{},"If your codebase still uses the Options API, using VueUse, Vue Router, Pinia, or even Nuxt utilities becomes awkward. You’ll end up duplicating state, breaking reactivity, or writing glue code just to access core features.",[333,5505,5507],{"className":1246,"code":5506,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cp>Current route: {{ currentRoute.path }}\u003C\u002Fp>\n    \u003Cp>Window is {{ isLargeScreen ? 'large' : 'small' }}\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { useRoute } from 'vue-router'\nimport { useMediaQuery } from '@vueuse\u002Fcore'\n\nexport default {\n  setup() {\n    const route = useRoute()\n    const isLargeScreen = useMediaQuery('(min-width: 1024px)')\n    return {\n      currentRoute: route,\n      isLargeScreen\n    }\n  },\n\n  data() {\n    return {\n      foo: 'bar'\n    }\n  },\n\n  created() {\n    \u002F\u002F Want to access `currentRoute` or `isLargeScreen` here?\n    \u002F\u002F Nope — it's not on `this`, it's in the setup return.\n    console.log('Cannot access composables from here')\n  }\n}\n\u003C\u002Fscript>\n",[63,5508,5509,5517,5525,5542,5559,5567,5575,5579,5587,5606,5626,5630,5638,5647,5659,5681,5687,5698,5703,5707,5711,5715,5723,5729,5743,5747,5751,5755,5764,5769,5774,5795,5799,5803],{"__ignoreMap":338},[342,5510,5511,5513,5515],{"class":344,"line":345},[342,5512,1023],{"class":446},[342,5514,1522],{"class":496},[342,5516,1280],{"class":446},[342,5518,5519,5521,5523],{"class":344,"line":359},[342,5520,1530],{"class":446},[342,5522,1533],{"class":496},[342,5524,1280],{"class":446},[342,5526,5527,5529,5531,5533,5536,5538,5540],{"class":344,"line":469},[342,5528,1553],{"class":446},[342,5530,13],{"class":496},[342,5532,1035],{"class":446},[342,5534,5535],{"class":450},"Current route: {{ currentRoute.path }}",[342,5537,1505],{"class":446},[342,5539,13],{"class":496},[342,5541,1280],{"class":446},[342,5543,5544,5546,5548,5550,5553,5555,5557],{"class":344,"line":476},[342,5545,1553],{"class":446},[342,5547,13],{"class":496},[342,5549,1035],{"class":446},[342,5551,5552],{"class":450},"Window is {{ isLargeScreen ? 'large' : 'small' }}",[342,5554,1505],{"class":446},[342,5556,13],{"class":496},[342,5558,1280],{"class":446},[342,5560,5561,5563,5565],{"class":344,"line":493},[342,5562,1803],{"class":446},[342,5564,1533],{"class":496},[342,5566,1280],{"class":446},[342,5568,5569,5571,5573],{"class":344,"line":506},[342,5570,1505],{"class":446},[342,5572,1522],{"class":496},[342,5574,1280],{"class":446},[342,5576,5577],{"class":344,"line":530},[342,5578,473],{"emptyLinePlaceholder":472},[342,5580,5581,5583,5585],{"class":344,"line":540},[342,5582,1023],{"class":446},[342,5584,1262],{"class":496},[342,5586,1280],{"class":446},[342,5588,5589,5591,5593,5595,5597,5599,5601,5604],{"class":344,"line":571},[342,5590,443],{"class":442},[342,5592,447],{"class":446},[342,5594,1862],{"class":450},[342,5596,454],{"class":446},[342,5598,457],{"class":442},[342,5600,4017],{"class":446},[342,5602,5603],{"class":352},"vue-router",[342,5605,5273],{"class":446},[342,5607,5608,5610,5612,5615,5617,5619,5621,5624],{"class":344,"line":600},[342,5609,443],{"class":442},[342,5611,447],{"class":446},[342,5613,5614],{"class":450}," useMediaQuery",[342,5616,454],{"class":446},[342,5618,457],{"class":442},[342,5620,4017],{"class":446},[342,5622,5623],{"class":352},"@vueuse\u002Fcore",[342,5625,5273],{"class":446},[342,5627,5628],{"class":344,"line":629},[342,5629,473],{"emptyLinePlaceholder":472},[342,5631,5632,5634,5636],{"class":344,"line":659},[342,5633,479],{"class":442},[342,5635,482],{"class":442},[342,5637,503],{"class":446},[342,5639,5640,5643,5645],{"class":344,"line":688},[342,5641,5642],{"class":496},"  setup",[342,5644,941],{"class":446},[342,5646,503],{"class":446},[342,5648,5649,5651,5653,5655,5657],{"class":344,"line":694},[342,5650,1158],{"class":830},[342,5652,1971],{"class":450},[342,5654,1107],{"class":446},[342,5656,1862],{"class":362},[342,5658,1112],{"class":496},[342,5660,5661,5663,5666,5668,5670,5672,5674,5677,5679],{"class":344,"line":700},[342,5662,1158],{"class":830},[342,5664,5665],{"class":450}," isLargeScreen",[342,5667,1107],{"class":446},[342,5669,5614],{"class":362},[342,5671,487],{"class":496},[342,5673,3229],{"class":446},[342,5675,5676],{"class":352},"(min-width: 1024px)",[342,5678,3229],{"class":446},[342,5680,706],{"class":496},[342,5682,5683,5685],{"class":344,"line":1461},[342,5684,2791],{"class":442},[342,5686,503],{"class":446},[342,5688,5689,5692,5694,5696],{"class":344,"line":1478},[342,5690,5691],{"class":496},"      currentRoute",[342,5693,500],{"class":446},[342,5695,1971],{"class":450},[342,5697,527],{"class":446},[342,5699,5700],{"class":344,"line":1495},[342,5701,5702],{"class":450},"      isLargeScreen\n",[342,5704,5705],{"class":344,"line":1502},[342,5706,4670],{"class":446},[342,5708,5709],{"class":344,"line":1512},[342,5710,697],{"class":446},[342,5712,5713],{"class":344,"line":1517},[342,5714,473],{"emptyLinePlaceholder":472},[342,5716,5717,5719,5721],{"class":344,"line":1527},[342,5718,4523],{"class":496},[342,5720,941],{"class":446},[342,5722,503],{"class":446},[342,5724,5725,5727],{"class":344,"line":1550},[342,5726,2791],{"class":442},[342,5728,503],{"class":446},[342,5730,5731,5734,5736,5738,5741],{"class":344,"line":1581},[342,5732,5733],{"class":496},"      foo",[342,5735,500],{"class":446},[342,5737,4017],{"class":446},[342,5739,5740],{"class":352},"bar",[342,5742,5273],{"class":446},[342,5744,5745],{"class":344,"line":1601},[342,5746,4670],{"class":446},[342,5748,5749],{"class":344,"line":1610},[342,5750,697],{"class":446},[342,5752,5753],{"class":344,"line":1625},[342,5754,473],{"emptyLinePlaceholder":472},[342,5756,5757,5760,5762],{"class":344,"line":1640},[342,5758,5759],{"class":496},"  created",[342,5761,941],{"class":446},[342,5763,503],{"class":446},[342,5765,5766],{"class":344,"line":1655},[342,5767,5768],{"class":436},"    \u002F\u002F Want to access `currentRoute` or `isLargeScreen` here?\n",[342,5770,5771],{"class":344,"line":1670},[342,5772,5773],{"class":436},"    \u002F\u002F Nope — it's not on `this`, it's in the setup return.\n",[342,5775,5776,5779,5781,5784,5786,5788,5791,5793],{"class":344,"line":1676},[342,5777,5778],{"class":450},"    console",[342,5780,158],{"class":446},[342,5782,5783],{"class":362},"log",[342,5785,487],{"class":496},[342,5787,3229],{"class":446},[342,5789,5790],{"class":352},"Cannot access composables from here",[342,5792,3229],{"class":446},[342,5794,706],{"class":496},[342,5796,5797],{"class":344,"line":1685},[342,5798,1229],{"class":446},[342,5800,5801],{"class":344,"line":1700},[342,5802,956],{"class":446},[342,5804,5805,5807,5809],{"class":344,"line":1714},[342,5806,1505],{"class":446},[342,5808,1262],{"class":496},[342,5810,1280],{"class":446},[13,5812,5813],{},"Note: You can access composables in the template and setup() return, but not inside created() or methods() via this. That's because setup() and Options parts run in parallel and are not merged.",[26,5815,5817],{"id":5816},"why-this-feels-painful","🔥 Why This Feels Painful",[41,5819,5820,5823,5826,5829],{},[44,5821,5822],{},"Composables can’t be used in data(), created(), or methods() unless you restructure your code to use setup() too.",[44,5824,5825],{},"You lose access to this, so you end up in a weird hybrid state.",[44,5827,5828],{},"You’re forced to migrate incrementally anyway, or wrap composables in extra glue code.",[44,5830,5831],{},"You might end up duplicating state (some in data(), some from ref()), which creates bugs.",[26,5833,5835],{"id":5834},"how-teams-typically-respond","✅ How Teams Typically Respond",[41,5837,5838,5841,5844],{},[44,5839,5840],{},"Copy\u002Fpaste the composable logic into a method or mixin (bad DX)",[44,5842,5843],{},"Wrap composables inside plugins or workarounds (gross)",[44,5845,5846],{},"Start using setup() in Options components, which leads to full Composition migration",[26,5848,5850],{"id":5849},"️-when-to-stick-with-options-api","🛠️ When to Stick with Options API",[13,5852,5853],{},"Keep Options API if:",[41,5855,5856,5859,5862],{},[44,5857,5858],{},"Your project is simple and stable",[44,5860,5861],{},"Your team is mostly juniors",[44,5863,5864],{},"You don’t use advanced Vue 3 features",[13,5866,5867],{},[17,5868,5869],{},"Composition API is not a must. It’s a tool — use it when it solves problems.",[26,5871,5873],{"id":5872},"final-thoughts","✅ Final Thoughts",[13,5875,5876],{},"Refactoring from Options to Composition API can bring:",[41,5878,5879,5882,5885,5888],{},[44,5880,5881],{},"💡 Better modularity & testability",[44,5883,5884],{},"⚙️ Clean, scalable architecture",[44,5886,5887],{},"🗖️ Native ecosystem support",[44,5889,5890],{},"✨ Modern tooling & TS integration",[13,5892,5893],{},"But you don’t need to do it all at once.",[13,5895,5896],{},[17,5897,5898],{},"Recommended approach:",[41,5900,5901,5904,5907],{},[44,5902,5903],{},"New code? ✅ Use Composition API",[44,5905,5906],{},"Existing code? ♻️ Migrate when relevant",[44,5908,5909],{},"Simple legacy parts? 🛡️ Leave as-is",[13,5911,5912],{},[17,5913,5914],{},"Vue gives you the flexibility to evolve at your own pace.",[26,5916,5918],{"id":5917},"try-it-yourself","🚀 Try It Yourself",[13,5920,5921],{},"Refactor one component. Create one composable.",[13,5923,5924],{},"You’ll see why more teams are choosing Composition API in 2025 and beyond.",[333,5926,5928],{"className":335,"code":5927,"language":337,"meta":338,"style":338},"npm create vue@latest\n# Choose: \"Composition API + \u003Cscript setup>\"\n",[63,5929,5930,5940],{"__ignoreMap":338},[342,5931,5932,5934,5937],{"class":344,"line":345},[342,5933,393],{"class":348},[342,5935,5936],{"class":352}," create",[342,5938,5939],{"class":352}," vue@latest\n",[342,5941,5942],{"class":344,"line":359},[342,5943,5944],{"class":436},"# Choose: \"Composition API + \u003Cscript setup>\"\n",[26,5946,5948],{"id":5947},"get-started-stay-modern-grow-with-vue","Get started. Stay modern. Grow with Vue.",[13,5950,5951,5952,5959,5960,5967],{},"If you’re planning a ",[17,5953,5954],{},[4036,5955,5958],{"href":5956,"rel":5957},"https:\u002F\u002Fepicmax.co\u002Fvue-3-migration",[4040],"Vue 3 migration",", refactoring legacy Options API code, looking to ",[17,5961,5962],{},[4036,5963,5966],{"href":5964,"rel":5965},"https:\u002F\u002Fepicmax.co\u002Fnuxt-migration",[4040],"upgrade Nuxt",", or want a second opinion on your frontend architecture, the Epicmax team is happy to help.",[13,5969,5970],{},"For over 8 years, we’ve worked exclusively with Vue and helped teams modernize large codebases safely and incrementally.",[13,5972,5973,5974,5980],{},"Book a ",[17,5975,5976],{},[4036,5977,5979],{"href":3117,"rel":5978},[4040],"free Vue.js consultation"," to discuss your case.",[3114,5982],{"label":3116,"to":3117},[3119,5984,5985],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":338,"searchDepth":469,"depth":469,"links":5987},[5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000,6001,6002,6003,6004,6005,6006],{"id":4387,"depth":359,"text":4388},{"id":4412,"depth":359,"text":4413},{"id":4494,"depth":359,"text":4495},{"id":4842,"depth":359,"text":4843},{"id":4963,"depth":359,"text":4964},{"id":5142,"depth":359,"text":5143},{"id":5169,"depth":359,"text":5170},{"id":5196,"depth":359,"text":5197},{"id":5331,"depth":359,"text":5332},{"id":5352,"depth":359,"text":5353},{"id":5457,"depth":359,"text":5458},{"id":5475,"depth":359,"text":5476},{"id":5496,"depth":359,"text":5497},{"id":5816,"depth":359,"text":5817},{"id":5834,"depth":359,"text":5835},{"id":5849,"depth":359,"text":5850},{"id":5872,"depth":359,"text":5873},{"id":5917,"depth":359,"text":5918},{"id":5947,"depth":359,"text":5948},"2025-12-08","Learn when to refactor Vue 3 apps from Options API to Composition API. Benefits, pitfalls, real examples, scalability insights, and a practical migration strategy.","\u002Fimages\u002Fblog\u002Fcomposition-api\u002Fcard.png",{},"\u002Fblog\u002Fcomposition-api",{"title":4339,"description":6008},"blog\u002Fcomposition-api","wViN87a-fVFae8U5ImA7F0ZEGaO5tbMrf_2ojdghfhg",{"id":6016,"title":6017,"author":7,"authorSlug":8,"body":6018,"date":6007,"description":7444,"extension":3154,"faq":8,"featured":3155,"image":7445,"meta":7446,"navigation":472,"path":7447,"readTime":8,"seo":7448,"stem":7449,"tags":8,"__hash__":7450},"blog\u002Fblog\u002Fvite-vs-webpack.md","Why Vite Is Better for Vue.js Development: Benchmarks, Build Speed and Migration Guide",{"type":10,"value":6019,"toc":7426},[6020,6023,6026,6040,6043,6048,6056,6062,6064,6136,6139,6145,6156,6161,6172,6176,6191,6214,6227,6230,6233,6237,6240,6248,6356,6359,6366,6883,6886,6893,6897,6900,6969,6975,6979,6982,6988,7005,7009,7012,7015,7030,7033,7037,7106,7109,7113,7116,7130,7133,7137,7140,7143,7175,7186,7194,7199,7204,7227,7231,7234,7237,7265,7268,7272,7275,7301,7304,7308,7311,7328,7332,7400,7402,7405,7409,7412,7418,7421,7423],[13,6021,6022],{},"👋 How do you choose a build tool for a modern Vue 3 project? Which one is better — Webpack or Vite?",[13,6024,6025],{},"In this article, you’ll find a comparison of Vite and Webpack based on real usage, focused on performance, developer experience (DX), SSR, plugin ecosystem, and CI\u002FCD speed.",[13,6027,6028,6029,6035,6036,6039],{},"At Epicmax, we’ve been providing ",[17,6030,6031],{},[4036,6032,6034],{"href":4315,"rel":6033},[4040],"Vue.js development services"," for the past 8 years, and we use Vite for many of our Vue.js projects. But we never push tools just for the sake of it — in this article, we compare both options fairly and provide a clear checklist to help you understand when to ",[17,6037,6038],{},"migrate to Vite"," and when Webpack is still the better choice.",[13,6041,6042],{},"We’re not claiming Vite is universally better. The goal is to show when switching is worth the effort and when it’s not.",[13,6044,6045],{},[17,6046,6047],{},"Key differences between Vite and Webpack:",[41,6049,6050,6053],{},[44,6051,6052],{},"Vite serves native ES modules and uses esbuild, written in Go — fast and parallel.",[44,6054,6055],{},"Webpack offers deep extensibility, proven stability, and native Module Federation.",[13,6057,6058,6059,6061],{},"If you're maintaining a complex monorepo with legacy setups, Webpack may still be the safer choice.",[4375,6060],{},"\nBut for most new Vue 3 applications, especially in Nuxt 3, Vite delivers better results with less configuration and much faster iteration.",[26,6063,4388],{"id":4387},[179,6065,6066,6079],{},[182,6067,6068],{},[185,6069,6070,6073,6076],{},[188,6071,6072],{},"What you gain",[188,6074,6075],{},"Vite (esbuild)",[188,6077,6078],{},"Webpack",[196,6080,6081,6092,6103,6114,6125],{},[185,6082,6083,6086,6089],{},[201,6084,6085],{},"Dev-server cold start",[201,6087,6088],{},"≈ 0.10 s",[201,6090,6091],{},"≈ 10–30 s",[185,6093,6094,6097,6100],{},[201,6095,6096],{},"HMR latency",[201,6098,6099],{},"≈ 50 ms",[201,6101,6102],{},"≈ 3–5 s",[185,6104,6105,6108,6111],{},[201,6106,6107],{},"Prod build time (TS project)",[201,6109,6110],{},"≈ 0.10–0.39 s",[201,6112,6113],{},"≈ 16–41 s",[185,6115,6116,6119,6122],{},[201,6117,6118],{},"Bundle size (default config)",[201,6120,6121],{},"832.99 KiB (81.82 gz)",[201,6123,6124],{},"868.34 KiB (83.20 gz)",[185,6126,6127,6130,6133],{},[201,6128,6129],{},"Built-in Module Federation",[201,6131,6132],{},"via plugin (vite-plugin-federation)",[201,6134,6135],{},"native",[13,6137,6138],{},"Note: Production build times are taken from esbuild's official benchmark and reflect what's possible under ideal, JS-only conditions. Real-world Vue apps will see slightly longer build times due to additional processing steps—but the overall trend of significantly faster builds with Vite holds consistently true.",[13,6140,6141,6144],{},[17,6142,6143],{},"Stay with Vite"," if:",[41,6146,6147,6150,6153],{},[44,6148,6149],{},"You’re starting a Vue 3 \u002F Nuxt 3 app",[44,6151,6152],{},"Iteration speed and simpler DX matter",[44,6154,6155],{},"You don’t need custom Webpack plugins",[13,6157,6158,6144],{},[17,6159,6160],{},"Stay with Webpack",[41,6162,6163,6166,6169],{},[44,6164,6165],{},"You use Module Federation at scale",[44,6167,6168],{},"You rely on custom loaders or legacy CI",[44,6170,6171],{},"Your project spans many apps\u002Fpackages built around Webpack",[26,6173,6175],{"id":6174},"benchmarks-methodology","📊 Benchmarks & Methodology",[13,6177,6178,6179,6184,6185,6190],{},"We’ve based our comparison on ",[4036,6180,6183],{"href":6181,"rel":6182},"https:\u002F\u002Fesbuild.github.io\u002Ffaq\u002F#benchmark-details",[4040],"esbuild’s official benchmark"," and the excellent ",[4036,6186,6189],{"href":6187,"rel":6188},"https:\u002F\u002Fgithub.com\u002Fs1owjke\u002Fjs-bundler-benchmark",[4040],"js-bundler-benchmark by s1owjke",". These are reproducible, open-source benchmarks that you can try yourself.",[41,6192,6193,6200,6207],{},[44,6194,6195,6196,6199],{},"All tests use ",[17,6197,6198],{},"default configs"," for both Vite and Webpack.",[44,6201,6202,6203,6206],{},"For Webpack, the setup uses ",[17,6204,6205],{},"Babel + Terser",", which is common in Vue CLI projects.",[44,6208,6209,6210,6213],{},"For Vite, the build is powered by ",[17,6211,6212],{},"Rollup",", known for excellent tree-shaking.",[13,6215,6216,6217,118,6220,3429,6223,6226],{},"Even when tuned, Webpack still struggles to match Vite’s performance in ",[17,6218,6219],{},"cold starts",[17,6221,6222],{},"HMR",[17,6224,6225],{},"build times",". The difference in bundle size is small (~4%), but consistent — thanks to Rollup’s aggressive tree-shaking.",[13,6228,6229],{},"These benchmarks aren’t Vue-specific — they use real-world projects like React apps, Rome, and three.js — but still reflect general bundler performance and trends that apply across modern frontend stacks.",[13,6231,6232],{},"✅ We chose these sources because they’re open, easy to reproduce, and reflect actual project structure. If you’re curious, clone the repo and test it on your machine.",[26,6234,6236],{"id":6235},"️-configuration-developer-experience","🛠️ Configuration & Developer Experience",[13,6238,6239],{},"Speed isn’t the only thing that matters — developer experience plays a big role in how productive and enjoyable your workflow is. Let’s look at a real example of how Vite and Webpack configs typically compare in Vue projects.",[41,6241,6242],{},[44,6243,6244,6247],{},[17,6245,6246],{},"Vite",": With ~30–60 lines of vite.config.ts, you get Vue SFC support, TypeScript, CSS modules, asset handling, and source maps out of the box. It uses modern standards like native ESM and import.meta.env, which simplify both code and tooling.",[333,6249,6251],{"className":4501,"code":6250,"language":4503,"meta":338,"style":338},"\u002F\u002F vite.config.ts\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs\u002Fplugin-vue'\n\nexport default defineConfig({\n  plugins: [vue()],\n  build: {\n    sourcemap: true,\n  },\n})\n",[63,6252,6253,6258,6277,6293,6297,6309,6325,6334,6346,6350],{"__ignoreMap":338},[342,6254,6255],{"class":344,"line":345},[342,6256,6257],{"class":436},"\u002F\u002F vite.config.ts\n",[342,6259,6260,6262,6264,6266,6268,6270,6272,6275],{"class":344,"line":359},[342,6261,443],{"class":442},[342,6263,447],{"class":446},[342,6265,451],{"class":450},[342,6267,454],{"class":446},[342,6269,457],{"class":442},[342,6271,4017],{"class":446},[342,6273,6274],{"class":352},"vite",[342,6276,5273],{"class":446},[342,6278,6279,6281,6284,6286,6288,6291],{"class":344,"line":469},[342,6280,443],{"class":442},[342,6282,6283],{"class":450}," vue ",[342,6285,815],{"class":442},[342,6287,4017],{"class":446},[342,6289,6290],{"class":352},"@vitejs\u002Fplugin-vue",[342,6292,5273],{"class":446},[342,6294,6295],{"class":344,"line":476},[342,6296,473],{"emptyLinePlaceholder":472},[342,6298,6299,6301,6303,6305,6307],{"class":344,"line":493},[342,6300,479],{"class":442},[342,6302,482],{"class":442},[342,6304,451],{"class":362},[342,6306,487],{"class":450},[342,6308,490],{"class":446},[342,6310,6311,6314,6316,6318,6320,6323],{"class":344,"line":506},[342,6312,6313],{"class":496},"  plugins",[342,6315,500],{"class":446},[342,6317,3226],{"class":450},[342,6319,1248],{"class":362},[342,6321,6322],{"class":450},"()]",[342,6324,527],{"class":446},[342,6326,6327,6330,6332],{"class":344,"line":530},[342,6328,6329],{"class":496},"  build",[342,6331,500],{"class":446},[342,6333,503],{"class":446},[342,6335,6336,6339,6341,6344],{"class":344,"line":540},[342,6337,6338],{"class":496},"    sourcemap",[342,6340,500],{"class":446},[342,6342,6343],{"class":3603}," true",[342,6345,527],{"class":446},[342,6347,6348],{"class":344,"line":571},[342,6349,697],{"class":446},[342,6351,6352,6354],{"class":344,"line":600},[342,6353,703],{"class":446},[342,6355,706],{"class":450},[13,6357,6358],{},"✅ Covers most use cases without extra setup.",[41,6360,6361],{},[44,6362,6363,6365],{},[17,6364,6078],{},": The same setup requires 200–300+ lines. You'll likely use vue-loader, ts-loader, Babel, PostCSS, asset rules, and plugins for type checking, optimization, and dev server config. This can make maintenance harder, especially in large teams or older projects.",[333,6367,6369],{"className":4501,"code":6368,"language":4503,"meta":338,"style":338},"\u002F\u002F webpack.config.js\nconst path = require('path')\nconst { VueLoaderPlugin } = require('vue-loader')\nconst TerserPlugin = require('terser-webpack-plugin')\n\nmodule.exports = {\n  entry: '.\u002Fsrc\u002Fmain.js',\n  output: {\n    path: path.resolve(__dirname, 'dist'),\n    filename: 'bundle.js',\n  },\n  module: {\n    rules: [\n      { test: \u002F\\.vue$\u002F, loader: 'vue-loader' },\n      { test: \u002F\\.ts$\u002F, loader: 'ts-loader' },\n      { test: \u002F\\.css$\u002F, use: ['style-loader', 'css-loader', 'postcss-loader'] },\n      { test: \u002F\\.(png|svg|jpg|gif)$\u002F, type: 'asset' },\n    ],\n  },\n  resolve: {\n    extensions: ['.js', '.ts', '.vue'],\n  },\n  plugins: [new VueLoaderPlugin()],\n  optimization: {\n    minimize: true,\n    minimizer: [new TerserPlugin()],\n  },\n  devtool: 'source-map',\n}\n",[63,6370,6371,6376,6399,6425,6447,6451,6460,6476,6485,6516,6532,6536,6545,6555,6594,6627,6686,6741,6748,6752,6761,6799,6803,6821,6830,6841,6859,6863,6879],{"__ignoreMap":338},[342,6372,6373],{"class":344,"line":345},[342,6374,6375],{"class":436},"\u002F\u002F webpack.config.js\n",[342,6377,6378,6380,6383,6385,6388,6390,6392,6395,6397],{"class":344,"line":359},[342,6379,831],{"class":830},[342,6381,6382],{"class":450}," path ",[342,6384,837],{"class":446},[342,6386,6387],{"class":362}," require",[342,6389,487],{"class":450},[342,6391,3229],{"class":446},[342,6393,6394],{"class":352},"path",[342,6396,3229],{"class":446},[342,6398,706],{"class":450},[342,6400,6401,6403,6405,6408,6410,6412,6414,6416,6418,6421,6423],{"class":344,"line":469},[342,6402,831],{"class":830},[342,6404,447],{"class":446},[342,6406,6407],{"class":450}," VueLoaderPlugin ",[342,6409,703],{"class":446},[342,6411,1107],{"class":446},[342,6413,6387],{"class":362},[342,6415,487],{"class":450},[342,6417,3229],{"class":446},[342,6419,6420],{"class":352},"vue-loader",[342,6422,3229],{"class":446},[342,6424,706],{"class":450},[342,6426,6427,6429,6432,6434,6436,6438,6440,6443,6445],{"class":344,"line":476},[342,6428,831],{"class":830},[342,6430,6431],{"class":450}," TerserPlugin ",[342,6433,837],{"class":446},[342,6435,6387],{"class":362},[342,6437,487],{"class":450},[342,6439,3229],{"class":446},[342,6441,6442],{"class":352},"terser-webpack-plugin",[342,6444,3229],{"class":446},[342,6446,706],{"class":450},[342,6448,6449],{"class":344,"line":493},[342,6450,473],{"emptyLinePlaceholder":472},[342,6452,6453,6456,6458],{"class":344,"line":506},[342,6454,6455],{"class":446},"module.exports",[342,6457,1107],{"class":446},[342,6459,503],{"class":446},[342,6461,6462,6465,6467,6469,6472,6474],{"class":344,"line":530},[342,6463,6464],{"class":496},"  entry",[342,6466,500],{"class":446},[342,6468,4017],{"class":446},[342,6470,6471],{"class":352},".\u002Fsrc\u002Fmain.js",[342,6473,3229],{"class":446},[342,6475,527],{"class":446},[342,6477,6478,6481,6483],{"class":344,"line":540},[342,6479,6480],{"class":496},"  output",[342,6482,500],{"class":446},[342,6484,503],{"class":446},[342,6486,6487,6490,6492,6495,6497,6500,6503,6505,6507,6510,6512,6514],{"class":344,"line":571},[342,6488,6489],{"class":496},"    path",[342,6491,500],{"class":446},[342,6493,6494],{"class":450}," path",[342,6496,158],{"class":446},[342,6498,6499],{"class":362},"resolve",[342,6501,6502],{"class":450},"(__dirname",[342,6504,1047],{"class":446},[342,6506,4017],{"class":446},[342,6508,6509],{"class":352},"dist",[342,6511,3229],{"class":446},[342,6513,294],{"class":450},[342,6515,527],{"class":446},[342,6517,6518,6521,6523,6525,6528,6530],{"class":344,"line":600},[342,6519,6520],{"class":496},"    filename",[342,6522,500],{"class":446},[342,6524,4017],{"class":446},[342,6526,6527],{"class":352},"bundle.js",[342,6529,3229],{"class":446},[342,6531,527],{"class":446},[342,6533,6534],{"class":344,"line":629},[342,6535,697],{"class":446},[342,6537,6538,6541,6543],{"class":344,"line":659},[342,6539,6540],{"class":496},"  module",[342,6542,500],{"class":446},[342,6544,503],{"class":446},[342,6546,6547,6550,6552],{"class":344,"line":688},[342,6548,6549],{"class":496},"    rules",[342,6551,500],{"class":446},[342,6553,6554],{"class":450}," [\n",[342,6556,6557,6560,6563,6565,6568,6571,6573,6576,6579,6581,6584,6586,6588,6590,6592],{"class":344,"line":694},[342,6558,6559],{"class":446},"      {",[342,6561,6562],{"class":496}," test",[342,6564,500],{"class":446},[342,6566,6567],{"class":446}," \u002F",[342,6569,6570],{"class":450},"\\.",[342,6572,1248],{"class":352},[342,6574,6575],{"class":442},"$",[342,6577,6578],{"class":446},"\u002F",[342,6580,1047],{"class":446},[342,6582,6583],{"class":496}," loader",[342,6585,500],{"class":446},[342,6587,4017],{"class":446},[342,6589,6420],{"class":352},[342,6591,3229],{"class":446},[342,6593,4025],{"class":446},[342,6595,6596,6598,6600,6602,6604,6606,6608,6610,6612,6614,6616,6618,6620,6623,6625],{"class":344,"line":700},[342,6597,6559],{"class":446},[342,6599,6562],{"class":496},[342,6601,500],{"class":446},[342,6603,6567],{"class":446},[342,6605,6570],{"class":450},[342,6607,1275],{"class":352},[342,6609,6575],{"class":442},[342,6611,6578],{"class":446},[342,6613,1047],{"class":446},[342,6615,6583],{"class":496},[342,6617,500],{"class":446},[342,6619,4017],{"class":446},[342,6621,6622],{"class":352},"ts-loader",[342,6624,3229],{"class":446},[342,6626,4025],{"class":446},[342,6628,6629,6631,6633,6635,6637,6639,6642,6644,6646,6648,6651,6653,6655,6657,6660,6662,6664,6666,6669,6671,6673,6675,6678,6680,6683],{"class":344,"line":1461},[342,6630,6559],{"class":446},[342,6632,6562],{"class":496},[342,6634,500],{"class":446},[342,6636,6567],{"class":446},[342,6638,6570],{"class":450},[342,6640,6641],{"class":352},"css",[342,6643,6575],{"class":442},[342,6645,6578],{"class":446},[342,6647,1047],{"class":446},[342,6649,6650],{"class":496}," use",[342,6652,500],{"class":446},[342,6654,3226],{"class":450},[342,6656,3229],{"class":446},[342,6658,6659],{"class":352},"style-loader",[342,6661,3229],{"class":446},[342,6663,1047],{"class":446},[342,6665,4017],{"class":446},[342,6667,6668],{"class":352},"css-loader",[342,6670,3229],{"class":446},[342,6672,1047],{"class":446},[342,6674,4017],{"class":446},[342,6676,6677],{"class":352},"postcss-loader",[342,6679,3229],{"class":446},[342,6681,6682],{"class":450},"] ",[342,6684,6685],{"class":446},"},\n",[342,6687,6688,6690,6692,6694,6696,6698,6700,6703,6706,6709,6711,6714,6716,6719,6721,6723,6725,6727,6730,6732,6734,6737,6739],{"class":344,"line":1478},[342,6689,6559],{"class":446},[342,6691,6562],{"class":496},[342,6693,500],{"class":446},[342,6695,6567],{"class":446},[342,6697,6570],{"class":450},[342,6699,487],{"class":446},[342,6701,6702],{"class":352},"png",[342,6704,6705],{"class":446},"|",[342,6707,6708],{"class":352},"svg",[342,6710,6705],{"class":446},[342,6712,6713],{"class":352},"jpg",[342,6715,6705],{"class":446},[342,6717,6718],{"class":352},"gif",[342,6720,294],{"class":446},[342,6722,6575],{"class":442},[342,6724,6578],{"class":446},[342,6726,1047],{"class":446},[342,6728,6729],{"class":496}," type",[342,6731,500],{"class":446},[342,6733,4017],{"class":446},[342,6735,6736],{"class":352},"asset",[342,6738,3229],{"class":446},[342,6740,4025],{"class":446},[342,6742,6743,6746],{"class":344,"line":1495},[342,6744,6745],{"class":450},"    ]",[342,6747,527],{"class":446},[342,6749,6750],{"class":344,"line":1502},[342,6751,697],{"class":446},[342,6753,6754,6757,6759],{"class":344,"line":1512},[342,6755,6756],{"class":496},"  resolve",[342,6758,500],{"class":446},[342,6760,503],{"class":446},[342,6762,6763,6766,6768,6770,6772,6775,6777,6779,6781,6784,6786,6788,6790,6793,6795,6797],{"class":344,"line":1517},[342,6764,6765],{"class":496},"    extensions",[342,6767,500],{"class":446},[342,6769,3226],{"class":450},[342,6771,3229],{"class":446},[342,6773,6774],{"class":352},".js",[342,6776,3229],{"class":446},[342,6778,1047],{"class":446},[342,6780,4017],{"class":446},[342,6782,6783],{"class":352},".ts",[342,6785,3229],{"class":446},[342,6787,1047],{"class":446},[342,6789,4017],{"class":446},[342,6791,6792],{"class":352},".vue",[342,6794,3229],{"class":446},[342,6796,1212],{"class":450},[342,6798,527],{"class":446},[342,6800,6801],{"class":344,"line":1527},[342,6802,697],{"class":446},[342,6804,6805,6807,6809,6811,6814,6817,6819],{"class":344,"line":1550},[342,6806,6313],{"class":496},[342,6808,500],{"class":446},[342,6810,3226],{"class":450},[342,6812,6813],{"class":446},"new",[342,6815,6816],{"class":362}," VueLoaderPlugin",[342,6818,6322],{"class":450},[342,6820,527],{"class":446},[342,6822,6823,6826,6828],{"class":344,"line":1581},[342,6824,6825],{"class":496},"  optimization",[342,6827,500],{"class":446},[342,6829,503],{"class":446},[342,6831,6832,6835,6837,6839],{"class":344,"line":1601},[342,6833,6834],{"class":496},"    minimize",[342,6836,500],{"class":446},[342,6838,6343],{"class":3603},[342,6840,527],{"class":446},[342,6842,6843,6846,6848,6850,6852,6855,6857],{"class":344,"line":1610},[342,6844,6845],{"class":496},"    minimizer",[342,6847,500],{"class":446},[342,6849,3226],{"class":450},[342,6851,6813],{"class":446},[342,6853,6854],{"class":362}," TerserPlugin",[342,6856,6322],{"class":450},[342,6858,527],{"class":446},[342,6860,6861],{"class":344,"line":1625},[342,6862,697],{"class":446},[342,6864,6865,6868,6870,6872,6875,6877],{"class":344,"line":1640},[342,6866,6867],{"class":496},"  devtool",[342,6869,500],{"class":446},[342,6871,4017],{"class":446},[342,6873,6874],{"class":352},"source-map",[342,6876,3229],{"class":446},[342,6878,527],{"class":446},[342,6880,6881],{"class":344,"line":1655},[342,6882,956],{"class":446},[13,6884,6885],{},"💨 This doesn’t include .babelrc, .env config, or prod\u002Fdev mode splits — which are often needed.",[13,6887,6888,6889,6892],{},"Also worth noting: ",[17,6890,6891],{},"most Webpack plugins now have Vite equivalents",", like vite-plugin-pwa, vite-plugin-legacy, or vite-plugin-federation. You won't be stuck looking for alternatives. And because Vite uses Rollup under the hood, many Rollup plugins work seamlessly too.",[26,6894,6896],{"id":6895},"dev-performance","📈 Dev Performance",[13,6898,6899],{},"Let’s wrap this section with a head-to-head snapshot of key development metrics based on real-world usage and open-source benchmarks:",[179,6901,6902,6913],{},[182,6903,6904],{},[185,6905,6906,6909,6911],{},[188,6907,6908],{},"Metric",[188,6910,6246],{},[188,6912,6078],{},[196,6914,6915,6926,6937,6948,6959],{},[185,6916,6917,6920,6923],{},[201,6918,6919],{},"Cold Start",[201,6921,6922],{},"~0.10–0.90s",[201,6924,6925],{},"~10–30s",[185,6927,6928,6931,6934],{},[201,6929,6930],{},"HMR Patch Time",[201,6932,6933],{},"30–50ms",[201,6935,6936],{},"~3–5s",[185,6938,6939,6942,6945],{},[201,6940,6941],{},"Dev config lines",[201,6943,6944],{},"~30–60",[201,6946,6947],{},"200–300+",[185,6949,6950,6953,6956],{},[201,6951,6952],{},"TypeScript support",[201,6954,6955],{},"esbuild (fast) + plugin",[201,6957,6958],{},"ts-loader + Babel (slower)",[185,6960,6961,6963,6966],{},[201,6962,89],{},[201,6964,6965],{},"Rollup-compatible",[201,6967,6968],{},"Mature but complex",[13,6970,6971,6972,6974],{},"Try swapping your dev config — the difference in iteration speed and simplicity becomes obvious fast.",[4375,6973],{},"\nWebpack can close the gap with tuning — caching, code splitting, lazy loading — but Vite gives strong defaults out of the box.",[26,6976,6978],{"id":6977},"production-ready-features-for-both","Production-Ready Features (for Both)",[13,6980,6981],{},"While much of the attention goes to dev speed, both Vite and Webpack are well-equipped for production-grade builds:",[13,6983,6984,6985],{},"✅ ",[17,6986,6987],{},"Both support:",[41,6989,6990,6993,6996,6999,7002],{},[44,6991,6992],{},"Source maps (build.sourcemap: true)",[44,6994,6995],{},"Sentry integration (sentry-webpack-plugin vs vite-plugin-sentry)",[44,6997,6998],{},"Legacy browser fallback (vite-plugin-legacy)",[44,7000,7001],{},"Cache busting via hashed filenames",[44,7003,7004],{},"CDN-ready asset loading via base and publicDir",[26,7006,7008],{"id":7007},"ssr-and-nuxt-3","SSR and Nuxt 3",[13,7010,7011],{},"Nuxt 3 bootstraps around Vite, providing first-class SSR out of the box. Webpack is still an option (legacy Nuxt 2), yet requires additional configuration for dual bundles and streaming. If you start green-field SSR today, Vite is simpler; if you maintain a mature Nuxt 2 estate, weigh migration effort against benefits.",[26,7013,4457],{"id":7014},"typescript-support",[41,7016,7017,7022,7025],{},[44,7018,7019,7021],{},[17,7020,6246],{},": esbuild transpiles TS fast, but doesn’t check types",[44,7023,7024],{},"Add vue-tsc --noEmit in CI or vite-plugin-checker",[44,7026,7027,7029],{},[17,7028,6078],{},": Full type-checking during build (via ts-loader or Babel + fork-ts-checker-webpack-plugin), but builds are slower",[13,7031,7032],{},"Pick based on your enforcement level: fast feedback in dev or strict pipeline checks.",[26,7034,7036],{"id":7035},"plugin-ecosystem","Plugin Ecosystem",[179,7038,7039,7050],{},[182,7040,7041],{},[185,7042,7043,7046,7048],{},[188,7044,7045],{},"Capability",[188,7047,6078],{},[188,7049,6246],{},[196,7051,7052,7063,7073,7084,7095],{},[185,7053,7054,7057,7060],{},[201,7055,7056],{},"PWA",[201,7058,7059],{},"workbox-webpack-plugin",[201,7061,7062],{},"vite-plugin-pwa",[185,7064,7065,7068,7071],{},[201,7066,7067],{},"FederationvModuleFederationPlugin",[201,7069,7070],{},"vite-plugin-federation",[201,7072],{},[185,7074,7075,7078,7081],{},[201,7076,7077],{},"Legacy Browsers",[201,7079,7080],{},"Babel + config",[201,7082,7083],{},"vite-plugin-legacy",[185,7085,7086,7089,7092],{},[201,7087,7088],{},"Env Vars",[201,7090,7091],{},"process.env.X",[201,7093,7094],{},"import.meta.env.VITE_X",[185,7096,7097,7100,7103],{},[201,7098,7099],{},"SVGs inline",[201,7101,7102],{},"svg-inline-loader",[201,7104,7105],{},"vite-svg-loader",[13,7107,7108],{},"Rollup compatibility gives Vite access to 100s of existing plugins, plus it’s generally easier to read and maintain.",[26,7110,7112],{"id":7111},"microfrontends","Microfrontends",[13,7114,7115],{},"Microfrontends allow different teams to ship parts of the UI independently.",[41,7117,7118,7124],{},[44,7119,7120,7123],{},[17,7121,7122],{},"Vite:"," Works via plugin (vite-plugin-federation).",[44,7125,7126,7129],{},[17,7127,7128],{},"Webpack:"," Native support with Module Federation.",[13,7131,7132],{},"Webpack still has the edge for large-scale MFEs. Vite’s ecosystem is catching up.",[26,7134,7136],{"id":7135},"how-to-migrate-from-webpack-to-vite","🔁 How to Migrate from Webpack to Vite",[13,7138,7139],{},"Already using Webpack? Migrating to Vite is often easier than you’d think.",[13,7141,7142],{},"Start simple:",[2954,7144,7145,7151,7158,7166,7172],{},[44,7146,7147,7150],{},[17,7148,7149],{},"Dev-only with Vite",", keep Webpack in production",[44,7152,7153,7154,7157],{},"Migrate ",[17,7155,7156],{},"Storybook"," or internal UI preview first",[44,7159,2936,7160,7165],{},[4036,7161,7164],{"href":7162,"rel":7163},"https:\u002F\u002Fgithub.com\u002FIndexXuan\u002Fvite-plugin-vue-cli",[4040],"vite-plugin-vue-cli"," to reuse Vue CLI config",[44,7167,7168,7169],{},"Try Vite inside a ",[17,7170,7171],{},"monorepo sub-package",[44,7173,7174],{},"Gradually refactor Webpack-specific imports",[13,7176,7177,7178,7181,7182,7185],{},"Once ready, replace ",[17,7179,7180],{},"webpack.config.js"," with a lightweight ",[17,7183,7184],{},"vite.config.ts",", then:",[41,7187,7188,7191],{},[44,7189,7190],{},"Use official Vite plugins (@vitejs\u002Fplugin-vue, vite-plugin-legacy, etc.)",[44,7192,7193],{},"Adapt environment variables:",[13,7195,7196],{},[17,7197,7198],{},"process.env.MY_VAR becomes import.meta.env.VITE_MY_VAR",[41,7200,7201],{},[44,7202,7203],{},"Add vue-tsc or vite-plugin-checker for type safety if needed",[13,7205,7206,7207,7210,7211,7214,7215,7220,7221],{},"If you need ",[17,7208,7209],{},"Vite consulting",", help with ",[17,7212,7213],{},"migrating from Webpack to Vite",", or ",[4036,7216,7219],{"href":7217,"rel":7218},"https:\u002F\u002Fepicmax.co\u002Fvue-js-consulting-services",[4040],"vue js consultants",", book a ",[17,7222,7223],{},[4036,7224,7226],{"href":3117,"rel":7225},[4040],"free consultation with a Vue expert.",[26,7228,7230],{"id":7229},"hybrid-setup-strategy","🔀 Hybrid Setup Strategy",[13,7232,7233],{},"Not ready to go all in on Vite? You don’t have to. Many teams use Vite alongside Webpack during the transition phase.",[13,7235,7236],{},"Here’s how a hybrid setup can work:",[2954,7238,7239,7245,7253,7258,7263],{},[44,7240,7241,7244],{},[17,7242,7243],{},"Use Vite in development only",", keep Webpack for production builds",[44,7246,7247,7248,3011,7250],{},"Migrate isolated pieces like ",[17,7249,7156],{},[17,7251,7252],{},"internal UI previews",[44,7254,2936,7255,7165],{},[4036,7256,7164],{"href":7162,"rel":7257},[4040],[44,7259,7260,7261],{},"Try Vite in a ",[17,7262,7171],{},[44,7264,7174],{},[13,7266,7267],{},"✅ This lets you unlock Vite’s fast dev feedback loop without the risk of breaking production.",[26,7269,7271],{"id":7270},"when-to-stick-with-webpack","When to Stick with Webpack",[13,7273,7274],{},"Vite isn’t perfect for every use case. You might want to keep using Webpack if:",[41,7276,7277,7284,7291,7298],{},[44,7278,7279,7280,7283],{},"You already use ",[17,7281,7282],{},"Module Federation"," and it’s working well",[44,7285,7286,7287,7290],{},"Your team has ",[17,7288,7289],{},"custom Webpack plugins\u002Floaders"," you rely on",[44,7292,7293,7294,7297],{},"You’re working in a ",[17,7295,7296],{},"legacy mono-repo"," with shared Webpack configs",[44,7299,7300],{},"Your infrastructure is heavily optimized around Webpack and migration costs are high",[13,7302,7303],{},"In these cases, sticking with Webpack may be the most pragmatic choice - for now.",[26,7305,7307],{"id":7306},"limitations-of-vite","Limitations of Vite",[13,7309,7310],{},"Vite is not a silver bullet. Watch out for:",[41,7312,7313,7316,7319,7322,7325],{},[44,7314,7315],{},"Lack of full CJS\u002FESM interop (some libraries break)",[44,7317,7318],{},"Missing require.context equivalent",[44,7320,7321],{},"Slower cold starts on very large codebases (>500 files)",[44,7323,7324],{},"Less mature support for custom Webpack-like plugins",[44,7326,7327],{},"Not compatible with Yarn PnP out of the box",[26,7329,7331],{"id":7330},"checklist-should-you-migrate-from-webpack-to-vite","Checklist \"Should You Migrate from Webpack to VIte\"",[179,7333,7334,7344],{},[182,7335,7336],{},[185,7337,7338,7341],{},[188,7339,7340],{},"Question",[188,7342,7343],{},"If “Yes” →",[196,7345,7346,7354,7361,7369,7376,7384,7392],{},[185,7347,7348,7351],{},[201,7349,7350],{},"Using Vue 3 or Nuxt 3?",[201,7352,7353],{},"Try Vite",[185,7355,7356,7359],{},[201,7357,7358],{},"Slow HMR or builds blocking workflow?",[201,7360,7353],{},[185,7362,7363,7366],{},[201,7364,7365],{},"Heavily invested in Module Federation?",[201,7367,7368],{},"Stay Webpack",[185,7370,7371,7374],{},[201,7372,7373],{},"Do you need advanced Webpack plugins?",[201,7375,7368],{},[185,7377,7378,7381],{},[201,7379,7380],{},"New greenfield project?",[201,7382,7383],{},"Use Vite",[185,7385,7386,7389],{},[201,7387,7388],{},"Multiple apps sharing config?",[201,7390,7391],{},"Consider gradual switch",[185,7393,7394,7397],{},[201,7395,7396],{},"Already on Vue CLI?",[201,7398,7399],{},"Explore vite-plugin-vue-cli",[26,7401,3106],{"id":3105},[13,7403,7404],{},"For new Vue 3 or Nuxt 3 services, Vite offers tangible gains in dev feedback speed, CI cycle time, and default bundle size with less configuration overhead. Retain Webpack only when its unique features (federation, bespoke loaders, legacy constraints) clearly outweigh the maintenance cost.",[26,7406,7408],{"id":7407},"next-step","Next Step",[13,7410,7411],{},"Clone a feature branch, swap your webpack scripts for Vite, and benchmark cold start, HMR, and CI durations. Hard numbers from your own repo will confirm whether the switch pays off.",[13,7413,7414,7415,7417],{},"If you need help ",[17,7416,7213],{}," or a consultation on your Vue application, the Epicmax team is always happy to help. For the past 8 years, we’ve been focused exclusively on frontend development within the Vue ecosystem and actively advocate for modern, high-quality frontend tools.",[13,7419,7420],{},"Book a free call if you’d like our input.",[3114,7422],{"label":3116,"to":3117},[3119,7424,7425],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":338,"searchDepth":469,"depth":469,"links":7427},[7428,7429,7430,7431,7432,7433,7434,7435,7436,7437,7438,7439,7440,7441,7442,7443],{"id":4387,"depth":359,"text":4388},{"id":6174,"depth":359,"text":6175},{"id":6235,"depth":359,"text":6236},{"id":6895,"depth":359,"text":6896},{"id":6977,"depth":359,"text":6978},{"id":7007,"depth":359,"text":7008},{"id":7014,"depth":359,"text":4457},{"id":7035,"depth":359,"text":7036},{"id":7111,"depth":359,"text":7112},{"id":7135,"depth":359,"text":7136},{"id":7229,"depth":359,"text":7230},{"id":7270,"depth":359,"text":7271},{"id":7306,"depth":359,"text":7307},{"id":7330,"depth":359,"text":7331},{"id":3105,"depth":359,"text":3106},{"id":7407,"depth":359,"text":7408},"Discover how Vite improves Vue.js development with faster builds, instant HMR and simpler configs. Full Vite vs Webpack comparison plus migration steps.","\u002Fimages\u002Fblog\u002Fvite-vs-webpack\u002Fcard.png",{},"\u002Fblog\u002Fvite-vs-webpack",{"title":6017,"description":7444},"blog\u002Fvite-vs-webpack","r7TRvBScm2TbbmE1ovziutQtmHzwgfb-DSlUd590xXA",{"id":7452,"title":7453,"author":7,"authorSlug":8,"body":7454,"date":8683,"description":8684,"extension":3154,"faq":8,"featured":3155,"image":8685,"meta":8686,"navigation":472,"path":8687,"readTime":8,"seo":8688,"stem":8689,"tags":8,"__hash__":8690},"blog\u002Fblog\u002Fhow-to-fix-cls.md","How to Fix CLS in E-Commerce Galleries and Carousels",{"type":10,"value":7455,"toc":8664},[7456,7459,7470,7477,7490,7506,7510,7513,7518,7521,7524,7527,7530,7533,7536,7540,7576,7580,7584,7589,7641,7646,7713,7718,7780,7785,7848,7854,7858,7953,7956,7960,7965,8045,8050,8083,8087,8091,8126,8130,8199,8203,8208,8248,8252,8309,8313,8375,8379,8404,8408,8414,8427,8431,8434,8460,8463,8467,8470,8505,8509,8512,8534,8539,8553,8558,8561,8566,8580,8586,8590,8593,8598,8601,8604,8607,8610,8613,8616,8626,8629,8642,8645,8659,8661],[13,7457,7458],{},"Image galleries are often the centerpiece of an e-commerce product page — sleek, interactive, and visually engaging.",[13,7460,7461,7462,7465,7466,7469],{},"But they also hide a major performance trap: they’re among the leading causes of ",[17,7463,7464],{},"Cumulative Layout Shift (CLS)"," — a key ",[17,7467,7468],{},"Core Web Vital"," that directly impacts user experience, SEO rankings, and conversion rates.",[13,7471,7472,7473,7476],{},"💡 ",[17,7474,7475],{},"In short:"," CLS measures unexpected layout movements during page load. When images push or shift content around, it not only frustrates users — it can also cost your business trust and sales.",[13,7478,7479,7480,7484,7485,158],{},"This comprehensive guide explains how to fix ",[4036,7481,7464],{"href":7482,"rel":7483},"https:\u002F\u002Fweb.dev\u002Farticles\u002Fcls",[4040]," issues in image galleries and improve your website’s ",[4036,7486,7489],{"href":7487,"rel":7488},"https:\u002F\u002Fweb.dev\u002Farticles\u002Fvitals",[4040],"Core Web Vitals",[13,7491,7492,7493,7498,7499,7505],{},"Created by ",[4036,7494,7497],{"href":7495,"rel":7496},"https:\u002F\u002Fepicmax.co",[4040],"Epicmax"," — a ",[17,7500,7501],{},[4036,7502,7504],{"href":4315,"rel":7503},[4040],"Vue.js development company"," specializing in performance, scalability, and Core Web Vitals optimization.",[26,7507,7509],{"id":7508},"why-galleries-are-cls-hotspots","😬 Why Galleries Are CLS Hotspots",[13,7511,7512],{},"Image galleries are interactive, media-heavy, and frequently rendered via JavaScript — making them prone to layout instability.",[13,7514,7515],{},[17,7516,7517],{},"Common issues include:",[13,7519,7520],{},"📷 Multiple high-res images with inconsistent sizes",[13,7522,7523],{},"🎠 JS-heavy carousels initialized after load",[13,7525,7526],{},"💭 Lazy-loaded media without placeholders",[13,7528,7529],{},"🔍 Hover-based zoom or overlays",[13,7531,7532],{},"⚙️ Late-rendered Vue components in SPAs",[13,7534,7535],{},"❗ Even small layout shifts can push CTAs, prices, or descriptions out of place, damaging trust and usability.",[26,7537,7539],{"id":7538},"️-common-cls-causes-in-image-galleries","⚠️ Common CLS Causes in Image Galleries",[2954,7541,7542,7548,7554,7564,7570],{},[44,7543,7544,7547],{},[17,7545,7546],{},"Missing Image Dimensions"," - Without width, height, or aspect-ratio, no space is reserved.",[44,7549,7550,7553],{},[17,7551,7552],{},"Lazy Loading Without Placeholder"," - No pre-allocated space = sudden pop-ins during scroll.",[44,7555,7556,7563],{},[17,7557,7558],{},[4036,7559,7562],{"href":7560,"rel":7561},"https:\u002F\u002Fweb.dev\u002Fcls\u002F#unsized-content",[4040],"Carousels"," That Inject DOM After Load - JS libraries render content late, shifting layout.",[44,7565,7566,7569],{},[17,7567,7568],{},"Animations That Move Layout"," - Modifying margin, top, or position causes shifts.",[44,7571,7572,7575],{},[17,7573,7574],{},"Dynamic UI Near the Gallery"," - Stock alerts, banners, or popups push content unexpectedly.",[26,7577,7579],{"id":7578},"️-fixes-for-cls-in-image-galleries","🛠️ Fixes for CLS in Image Galleries",[328,7581,7583],{"id":7582},"_1-reserve-space-with-dimensions-or-aspect-ratios","1. ✅ Reserve Space with Dimensions or Aspect Ratios",[13,7585,7586],{},[17,7587,7588],{},"❌ Bad:",[333,7590,7594],{"className":7591,"code":7592,"language":7593,"meta":338,"style":338},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cimg src=\"shoe.jpg\" alt=\"Red shoe\" loading=\"lazy\">\n","html",[63,7595,7596],{"__ignoreMap":338},[342,7597,7598,7600,7603,7606,7608,7610,7613,7615,7618,7620,7622,7625,7627,7630,7632,7634,7637,7639],{"class":344,"line":345},[342,7599,1023],{"class":446},[342,7601,7602],{"class":496},"img",[342,7604,7605],{"class":830}," src",[342,7607,837],{"class":446},[342,7609,566],{"class":446},[342,7611,7612],{"class":352},"shoe.jpg",[342,7614,566],{"class":446},[342,7616,7617],{"class":830}," alt",[342,7619,837],{"class":446},[342,7621,566],{"class":446},[342,7623,7624],{"class":352},"Red shoe",[342,7626,566],{"class":446},[342,7628,7629],{"class":830}," loading",[342,7631,837],{"class":446},[342,7633,566],{"class":446},[342,7635,7636],{"class":352},"lazy",[342,7638,566],{"class":446},[342,7640,1280],{"class":446},[13,7642,7643],{},[17,7644,7645],{},"✅ Good:",[333,7647,7649],{"className":7591,"code":7648,"language":7593,"meta":338,"style":338},"\u003Cimg src=\"shoe.jpg\" width=\"300\" height=\"400\" loading=\"lazy\" alt=\"Red shoe\">\n",[63,7650,7651],{"__ignoreMap":338},[342,7652,7653,7655,7657,7659,7661,7663,7665,7667,7670,7672,7674,7677,7679,7682,7684,7686,7689,7691,7693,7695,7697,7699,7701,7703,7705,7707,7709,7711],{"class":344,"line":345},[342,7654,1023],{"class":446},[342,7656,7602],{"class":496},[342,7658,7605],{"class":830},[342,7660,837],{"class":446},[342,7662,566],{"class":446},[342,7664,7612],{"class":352},[342,7666,566],{"class":446},[342,7668,7669],{"class":830}," width",[342,7671,837],{"class":446},[342,7673,566],{"class":446},[342,7675,7676],{"class":352},"300",[342,7678,566],{"class":446},[342,7680,7681],{"class":830}," height",[342,7683,837],{"class":446},[342,7685,566],{"class":446},[342,7687,7688],{"class":352},"400",[342,7690,566],{"class":446},[342,7692,7629],{"class":830},[342,7694,837],{"class":446},[342,7696,566],{"class":446},[342,7698,7636],{"class":352},[342,7700,566],{"class":446},[342,7702,7617],{"class":830},[342,7704,837],{"class":446},[342,7706,566],{"class":446},[342,7708,7624],{"class":352},[342,7710,566],{"class":446},[342,7712,1280],{"class":446},[13,7714,7715],{},[17,7716,7717],{},"✅ Vue Example:",[333,7719,7721],{"className":7591,"code":7720,"language":7593,"meta":338,"style":338},"\u003Cimg :src=\"img.url\" :width=\"img.width\" :height=\"img.height\" :alt=\"img.alt\" \u002F>\n",[63,7722,7723],{"__ignoreMap":338},[342,7724,7725,7727,7729,7732,7734,7736,7739,7741,7744,7746,7748,7751,7753,7756,7758,7760,7763,7765,7768,7770,7772,7775,7777],{"class":344,"line":345},[342,7726,1023],{"class":446},[342,7728,7602],{"class":496},[342,7730,7731],{"class":830}," :src",[342,7733,837],{"class":446},[342,7735,566],{"class":446},[342,7737,7738],{"class":352},"img.url",[342,7740,566],{"class":446},[342,7742,7743],{"class":830}," :width",[342,7745,837],{"class":446},[342,7747,566],{"class":446},[342,7749,7750],{"class":352},"img.width",[342,7752,566],{"class":446},[342,7754,7755],{"class":830}," :height",[342,7757,837],{"class":446},[342,7759,566],{"class":446},[342,7761,7762],{"class":352},"img.height",[342,7764,566],{"class":446},[342,7766,7767],{"class":830}," :alt",[342,7769,837],{"class":446},[342,7771,566],{"class":446},[342,7773,7774],{"class":352},"img.alt",[342,7776,566],{"class":446},[342,7778,7779],{"class":446}," \u002F>\n",[13,7781,7782],{},[17,7783,7784],{},"✅ CSS with aspect-ratio:",[333,7786,7789],{"className":7787,"code":7788,"language":6641,"meta":338,"style":338},"language-css shiki shiki-themes material-theme-lighter material-theme material-theme-palenight",".product-img {\n  aspect-ratio: 4 \u002F 5;\n  width: 100%;\n  height: auto;\n}\n",[63,7790,7791,7800,7820,7832,7844],{"__ignoreMap":338},[342,7792,7793,7795,7798],{"class":344,"line":345},[342,7794,158],{"class":446},[342,7796,7797],{"class":348},"product-img",[342,7799,503],{"class":446},[342,7801,7802,7806,7808,7811,7814,7817],{"class":344,"line":359},[342,7803,7805],{"class":7804},"sqsOY","  aspect-ratio",[342,7807,500],{"class":446},[342,7809,7810],{"class":1208}," 4",[342,7812,7813],{"class":450}," \u002F ",[342,7815,7816],{"class":1208},"5",[342,7818,7819],{"class":446},";\n",[342,7821,7822,7825,7827,7830],{"class":344,"line":469},[342,7823,7824],{"class":7804},"  width",[342,7826,500],{"class":446},[342,7828,7829],{"class":1208}," 100%",[342,7831,7819],{"class":446},[342,7833,7834,7837,7839,7842],{"class":344,"line":476},[342,7835,7836],{"class":7804},"  height",[342,7838,500],{"class":446},[342,7840,7841],{"class":450}," auto",[342,7843,7819],{"class":446},[342,7845,7846],{"class":344,"line":493},[342,7847,956],{"class":446},[13,7849,7850,7853],{},[17,7851,7852],{},"Quick Win:"," Just adding width and height (or aspect-ratio) to your gallery images is often enough to cut CLS by 50%+.",[328,7855,7857],{"id":7856},"_2-add-placeholders-for-lazy-loaded-images","2. ✅ Add Placeholders for Lazy-Loaded Images",[333,7859,7861],{"className":7591,"code":7860,"language":7593,"meta":338,"style":338},"\u003Cdiv class=\"img-frame\" style=\"width:300px; height:400px;\">\n  \u003Cimg src=\"placeholder.jpg\" data-src=\"shoe.jpg\" class=\"lazy\" alt=\"...\">\n\u003C\u002Fdiv>\n",[63,7862,7863,7894,7945],{"__ignoreMap":338},[342,7864,7865,7867,7869,7871,7873,7875,7878,7880,7883,7885,7887,7890,7892],{"class":344,"line":345},[342,7866,1023],{"class":446},[342,7868,1533],{"class":496},[342,7870,1536],{"class":830},[342,7872,837],{"class":446},[342,7874,566],{"class":446},[342,7876,7877],{"class":352},"img-frame",[342,7879,566],{"class":446},[342,7881,7882],{"class":830}," style",[342,7884,837],{"class":446},[342,7886,566],{"class":446},[342,7888,7889],{"class":352},"width:300px; height:400px;",[342,7891,566],{"class":446},[342,7893,1280],{"class":446},[342,7895,7896,7898,7900,7902,7904,7906,7909,7911,7914,7916,7918,7920,7922,7924,7926,7928,7930,7932,7934,7936,7938,7941,7943],{"class":344,"line":359},[342,7897,1530],{"class":446},[342,7899,7602],{"class":496},[342,7901,7605],{"class":830},[342,7903,837],{"class":446},[342,7905,566],{"class":446},[342,7907,7908],{"class":352},"placeholder.jpg",[342,7910,566],{"class":446},[342,7912,7913],{"class":830}," data-src",[342,7915,837],{"class":446},[342,7917,566],{"class":446},[342,7919,7612],{"class":352},[342,7921,566],{"class":446},[342,7923,1536],{"class":830},[342,7925,837],{"class":446},[342,7927,566],{"class":446},[342,7929,7636],{"class":352},[342,7931,566],{"class":446},[342,7933,7617],{"class":830},[342,7935,837],{"class":446},[342,7937,566],{"class":446},[342,7939,7940],{"class":352},"...",[342,7942,566],{"class":446},[342,7944,1280],{"class":446},[342,7946,7947,7949,7951],{"class":344,"line":469},[342,7948,1505],{"class":446},[342,7950,1533],{"class":496},[342,7952,1280],{"class":446},[13,7954,7955],{},"Even with native loading=\"lazy\", dimensions are still essential.",[328,7957,7959],{"id":7958},"_3-stabilize-carousels-on-first-render","3. ✅ Stabilize Carousels on First Render",[13,7961,7962],{},[17,7963,7964],{},"CSS:",[333,7966,7968],{"className":7787,"code":7967,"language":6641,"meta":338,"style":338},".my-slider > .slide:not(:first-child) {\n  display: none;\n}\n\n.carousel {\n  min-height: 400px;\n}\n",[63,7969,7970,8000,8012,8016,8020,8029,8041],{"__ignoreMap":338},[342,7971,7972,7974,7977,7979,7982,7985,7987,7990,7993,7996,7998],{"class":344,"line":345},[342,7973,158],{"class":446},[342,7975,7976],{"class":348},"my-slider",[342,7978,4934],{"class":446},[342,7980,7981],{"class":446}," .",[342,7983,7984],{"class":348},"slide",[342,7986,500],{"class":446},[342,7988,7989],{"class":830},"not",[342,7991,7992],{"class":446},"(:",[342,7994,7995],{"class":830},"first-child",[342,7997,294],{"class":446},[342,7999,503],{"class":446},[342,8001,8002,8005,8007,8010],{"class":344,"line":359},[342,8003,8004],{"class":7804},"  display",[342,8006,500],{"class":446},[342,8008,8009],{"class":450}," none",[342,8011,7819],{"class":446},[342,8013,8014],{"class":344,"line":469},[342,8015,956],{"class":446},[342,8017,8018],{"class":344,"line":476},[342,8019,473],{"emptyLinePlaceholder":472},[342,8021,8022,8024,8027],{"class":344,"line":493},[342,8023,158],{"class":446},[342,8025,8026],{"class":348},"carousel",[342,8028,503],{"class":446},[342,8030,8031,8034,8036,8039],{"class":344,"line":506},[342,8032,8033],{"class":7804},"  min-height",[342,8035,500],{"class":446},[342,8037,8038],{"class":1208}," 400px",[342,8040,7819],{"class":446},[342,8042,8043],{"class":344,"line":530},[342,8044,956],{"class":446},[13,8046,8047],{},[17,8048,8049],{},"Vue Example:",[333,8051,8053],{"className":7591,"code":8052,"language":7593,"meta":338,"style":338},"\u003Cdiv v-show=\"index === 0\">...\u003C\u002Fdiv>\n",[63,8054,8055],{"__ignoreMap":338},[342,8056,8057,8059,8061,8064,8066,8068,8071,8073,8075,8077,8079,8081],{"class":344,"line":345},[342,8058,1023],{"class":446},[342,8060,1533],{"class":496},[342,8062,8063],{"class":830}," v-show",[342,8065,837],{"class":446},[342,8067,566],{"class":446},[342,8069,8070],{"class":352},"index === 0",[342,8072,566],{"class":446},[342,8074,1035],{"class":446},[342,8076,7940],{"class":450},[342,8078,1505],{"class":446},[342,8080,1533],{"class":496},[342,8082,1280],{"class":446},[328,8084,8086],{"id":8085},"_4-animate-with-transform-not-margin-or-position","4. ✅ Animate with transform, Not margin or position",[13,8088,8089],{},[17,8090,7588],{},[333,8092,8094],{"className":7787,"code":8093,"language":6641,"meta":338,"style":338},".zoom:hover {\n  margin-top: 20px;\n}\n",[63,8095,8096,8110,8122],{"__ignoreMap":338},[342,8097,8098,8100,8103,8105,8108],{"class":344,"line":345},[342,8099,158],{"class":446},[342,8101,8102],{"class":348},"zoom",[342,8104,500],{"class":446},[342,8106,8107],{"class":830},"hover",[342,8109,503],{"class":446},[342,8111,8112,8115,8117,8120],{"class":344,"line":359},[342,8113,8114],{"class":7804},"  margin-top",[342,8116,500],{"class":446},[342,8118,8119],{"class":1208}," 20px",[342,8121,7819],{"class":446},[342,8123,8124],{"class":344,"line":469},[342,8125,956],{"class":446},[13,8127,8128],{},[17,8129,7645],{},[333,8131,8133],{"className":7787,"code":8132,"language":6641,"meta":338,"style":338},".zoom {\n  transition: transform 0.3s ease;\n}\n.zoom:hover {\n  transform: scale(1.1);\n}\n",[63,8134,8135,8143,8161,8165,8177,8195],{"__ignoreMap":338},[342,8136,8137,8139,8141],{"class":344,"line":345},[342,8138,158],{"class":446},[342,8140,8102],{"class":348},[342,8142,503],{"class":446},[342,8144,8145,8148,8150,8153,8156,8159],{"class":344,"line":359},[342,8146,8147],{"class":7804},"  transition",[342,8149,500],{"class":446},[342,8151,8152],{"class":450}," transform ",[342,8154,8155],{"class":1208},"0.3s",[342,8157,8158],{"class":450}," ease",[342,8160,7819],{"class":446},[342,8162,8163],{"class":344,"line":469},[342,8164,956],{"class":446},[342,8166,8167,8169,8171,8173,8175],{"class":344,"line":476},[342,8168,158],{"class":446},[342,8170,8102],{"class":348},[342,8172,500],{"class":446},[342,8174,8107],{"class":830},[342,8176,503],{"class":446},[342,8178,8179,8182,8184,8187,8189,8192],{"class":344,"line":493},[342,8180,8181],{"class":7804},"  transform",[342,8183,500],{"class":446},[342,8185,8186],{"class":362}," scale",[342,8188,487],{"class":446},[342,8190,8191],{"class":1208},"1.1",[342,8193,8194],{"class":446},");\n",[342,8196,8197],{"class":344,"line":506},[342,8198,956],{"class":446},[328,8200,8202],{"id":8201},"_5-pre-allocate-space-for-injected-ui","5. ✅ Pre-allocate Space for Injected UI",[13,8204,8205],{},[17,8206,8207],{},"HTML:",[333,8209,8211],{"className":7591,"code":8210,"language":7593,"meta":338,"style":338},"\u003Cdiv class=\"promo-placeholder\" style=\"min-height: 60px;\">\u003C\u002Fdiv>\n",[63,8212,8213],{"__ignoreMap":338},[342,8214,8215,8217,8219,8221,8223,8225,8228,8230,8232,8234,8236,8239,8241,8244,8246],{"class":344,"line":345},[342,8216,1023],{"class":446},[342,8218,1533],{"class":496},[342,8220,1536],{"class":830},[342,8222,837],{"class":446},[342,8224,566],{"class":446},[342,8226,8227],{"class":352},"promo-placeholder",[342,8229,566],{"class":446},[342,8231,7882],{"class":830},[342,8233,837],{"class":446},[342,8235,566],{"class":446},[342,8237,8238],{"class":352},"min-height: 60px;",[342,8240,566],{"class":446},[342,8242,8243],{"class":446},">\u003C\u002F",[342,8245,1533],{"class":496},[342,8247,1280],{"class":446},[13,8249,8250],{},[17,8251,7964],{},[333,8253,8255],{"className":7787,"code":8254,"language":6641,"meta":338,"style":338},".gallery-wrapper::before {\n  content: \"\";\n  display: block;\n  height: 60px;\n}\n",[63,8256,8257,8272,8283,8294,8305],{"__ignoreMap":338},[342,8258,8259,8261,8264,8267,8270],{"class":344,"line":345},[342,8260,158],{"class":446},[342,8262,8263],{"class":348},"gallery-wrapper",[342,8265,8266],{"class":446},"::",[342,8268,8269],{"class":830},"before",[342,8271,503],{"class":446},[342,8273,8274,8277,8279,8281],{"class":344,"line":359},[342,8275,8276],{"class":7804},"  content",[342,8278,500],{"class":446},[342,8280,2217],{"class":446},[342,8282,7819],{"class":446},[342,8284,8285,8287,8289,8292],{"class":344,"line":469},[342,8286,8004],{"class":7804},[342,8288,500],{"class":446},[342,8290,8291],{"class":450}," block",[342,8293,7819],{"class":446},[342,8295,8296,8298,8300,8303],{"class":344,"line":476},[342,8297,7836],{"class":7804},[342,8299,500],{"class":446},[342,8301,8302],{"class":1208}," 60px",[342,8304,7819],{"class":446},[342,8306,8307],{"class":344,"line":493},[342,8308,956],{"class":446},[328,8310,8312],{"id":8311},"clsfriendly-carousel-libraries","🌟 CLS ‑Friendly Carousel Libraries",[179,8314,8315,8331],{},[182,8316,8317],{},[185,8318,8319,8322,8325,8328],{},[188,8320,8321],{},"Library",[188,8323,8324],{},"Type",[188,8326,8327],{},"CLS Safety",[188,8329,8330],{},"Why It Helps",[196,8332,8333,8347,8361],{},[185,8334,8335,8338,8341,8344],{},[201,8336,8337],{},"vue3-carousel",[201,8339,8340],{},"Vue 3",[201,8342,8343],{},"✅ Good",[201,8345,8346],{},"Lightweight SSR-friendly carousel: slides aren’t injected in the DOM until after hydration. No surprise shifts.",[185,8348,8349,8352,8355,8358],{},[201,8350,8351],{},"vue3-ssr-carousel",[201,8353,8354],{},"Vue 3 (SSR\u002FSSG)",[201,8356,8357],{},"✅ Excellent",[201,8359,8360],{},"No JS layout changes post-hydration—fully SSR-rendered slides ensure zero CLS.",[185,8362,8363,8366,8369,8372],{},[201,8364,8365],{},"Glider.js",[201,8367,8368],{},"Vanilla JS",[201,8370,8371],{},"✅ Better",[201,8373,8374],{},"Lightweight (≈2.8 KB), native scrolling, and uses CSS layout—minimal DOM manipulation helps stabilize CLS.",[26,8376,8378],{"id":8377},"why-these-matter-for-cls","🧹 Why These Matter for CLS",[41,8380,8381,8392],{},[44,8382,8383,8386,8387,20,8389,8391],{},[17,8384,8385],{},"SSR ‑friendliness",": Libraries like ",[5161,8388,8337],{},[5161,8390,8351],{}," render the carousel fully on the server (or pre-render), preventing sudden DOM injections post-load.",[44,8393,8394,8397,8398,20,8401,8403],{},[17,8395,8396],{},"CSS ‑driven layout",": Tools like ",[5161,8399,8400],{},"vue-horizontal",[5161,8402,8365],{}," rely on CSS (e.g., native scroll or transform), which avoids modifying layout and therefore minimizes layout thrashing.",[26,8405,8407],{"id":8406},"business-impact-of-cls-why-it-matters","📈 Business Impact of CLS: Why It Matters",[13,8409,8410,8413],{},[17,8411,8412],{},"CLS isn’t just a developer metric"," — it’s a silent killer of conversions, trust, and SEO. In e-commerce, layout shifts from image galleries directly impact revenue.",[13,8415,8416,8417,8419,8420,8422,8423,8426],{},"A ",[17,8418,4373],{}," like ",[17,8421,7497],{}," helps teams detect and fix CLS issues early — improving stability, Core Web Vitals, and user experience. If you’re looking to ",[17,8424,8425],{},"hire Vue.js developers"," who care about both performance and UX, you’re in the right place.",[26,8428,8430],{"id":8429},"real-world-cls-data","📊 Real-World CLS Data",[13,8432,8433],{},"Understanding how your site compares to the wider web is the first step:",[41,8435,8436,8442,8448,8454],{},[44,8437,8438,8441],{},[17,8439,8440],{},"∼50%"," of page loads already meet Google’s “Good” threshold (CLS \u003C 0.10) — if you’re above this, your competition is likely offering a smoother, more stable experience.",[44,8443,8444,8447],{},[17,8445,8446],{},"0.28"," is the 75th percentile — if you're above this, you're among the worst 25% in layout stability and almost certainly losing trust and revenue.",[44,8449,8450,8453],{},[17,8451,8452],{},"CLS > 0.1"," correlates with lower conversions and higher bounce rates.",[44,8455,8456,8459],{},[17,8457,8458],{},"Google’s target",": CLS ≤ 0.1 in at least 75% of sessions.",[13,8461,8462],{},"💡 If your product gallery causes unexpected shifts, you’re likely losing users and sales to competitors with smoother experiences.",[26,8464,8466],{"id":8465},"business-benefits-of-fixing-cls","💡 Business Benefits of Fixing CLS",[13,8468,8469],{},"Stable galleries deliver measurable results:",[41,8471,8472,8479,8486,8498],{},[44,8473,8474,8475,8478],{},"📉 ",[17,8476,8477],{},"Reduced Bounce:"," Users stay longer when pages don’t jump",[44,8480,8481,8482,8485],{},"⏳ ",[17,8483,8484],{},"Longer Sessions:"," A stable interface encourages exploration",[44,8487,8488,8489,8492,8493,8497],{},"🔍 ",[17,8490,8491],{},"SEO Boost:"," CLS is a ",[4036,8494,7489],{"href":8495,"rel":8496},"https:\u002F\u002Fweb.dev\u002Fvitals\u002F",[4040]," ranking factor",[44,8499,8500,8501,8504],{},"💳 ",[17,8502,8503],{},"More Conversions:"," A smooth gallery builds trust and shortens decision time",[26,8506,8508],{"id":8507},"measuring-roi-ab-and-correlation-testing","📈 Measuring ROI: A\u002FB and Correlation Testing",[13,8510,8511],{},"Fixing CLS isn’t just a tech win — it should pay off in business metrics.",[13,8513,8514,8515,8419,8518,118,8523,7214,8528,8533],{},"Once you’ve optimized your gallery, use ",[17,8516,8517],{},"real-user monitoring (RUM) tools",[4036,8519,8522],{"href":8520,"rel":8521},"https:\u002F\u002Fspeedcurve.com\u002F",[4040],"SpeedCurve",[4036,8524,8527],{"href":8525,"rel":8526},"https:\u002F\u002Fraygun.com\u002F",[4040],"Raygun",[4036,8529,8532],{"href":8530,"rel":8531},"https:\u002F\u002Fnewrelic.com\u002F",[4040],"New Relic"," to connect performance improvements to business impact.",[13,8535,8536],{},[17,8537,8538],{},"These tools let you monitor:",[41,8540,8541,8544,8547,8550],{},[44,8542,8543],{},"Bounce rate",[44,8545,8546],{},"Session duration",[44,8548,8549],{},"Conversion rate",[44,8551,8552],{},"CLS and other layout metrics",[13,8554,8555],{},[17,8556,8557],{},"📊 Real-world stat:",[13,8559,8560],{},"Sessions with CLS \u003C 0.1 convert 2× better than sessions with CLS > 0.25.",[13,8562,8563],{},[17,8564,8565],{},"Make it actionable by:",[41,8567,8568,8571],{},[44,8569,8570],{},"Running A\u002FB tests to compare gallery performance before and after fixes",[44,8572,8573,8574,8579],{},"Using ",[4036,8575,8578],{"href":8576,"rel":8577},"https:\u002F\u002Fsupport.speedcurve.com\u002Fdocs\u002Fcreate-correlation-charts",[4040],"correlation charts"," to visually tie CLS improvements to trust, engagement, and revenue gains",[13,8581,8582,8583,8585],{},"These are graphs that map CLS values against business KPIs to find patterns.",[4375,8584],{},"\nBy treating CLS like a business KPI — not just a dev concern — you’ll unlock stronger results and buy-in from stakeholders.",[26,8587,8589],{"id":8588},"what-to-do-next","🚀 What to Do Next",[13,8591,8592],{},"🛒 Start by fixing one gallery on your top product page — and watch your CLS and conversions improve.",[13,8594,8595],{},[17,8596,8597],{},"📋 Start Here:",[13,8599,8600],{},"✅ Audit CLS in DevTools → Performance tab",[13,8602,8603],{},"✅ Add dimensions or aspect-ratio to gallery images",[13,8605,8606],{},"✅ Set CSS min-heights for carousels",[13,8608,8609],{},"✅ Add skeletons or placeholders for lazy media",[13,8611,8612],{},"✅ Pick a CLS-friendly library (e.g., vue3-carousel)",[26,8614,8615],{"id":3105},"✅ Conclusion",[13,8617,8618,8619,20,8622,8625],{},"Image galleries make your product pages engaging — but they can silently ruin your ",[17,8620,8621],{},"CLS score",[17,8623,8624],{},"conversion rates",". Thankfully, most layout shift issues are preventable with a few targeted optimizations.",[13,8627,8628],{},"By reserving space for images, adding lazy-load placeholders, stabilizing carousels, and using transform instead of layout-moving animations, you can significantly reduce CLS and improve the overall user experience.",[13,8630,8631,8632,118,8635,7214,8638,8641],{},"Don’t stop at implementation — validate your fixes with ",[17,8633,8634],{},"Chrome DevTools",[17,8636,8637],{},"Lighthouse",[17,8639,8640],{},"RUM tools"," to confirm improvements in real-world conditions.",[13,8643,8644],{},"✨ Even small CLS optimizations can lead to measurable gains in engagement, SEO performance, and revenue.",[13,8646,8647,8648,20,8652,8658],{},"Need help refining your gallery performance? Epicmax offers ",[4036,8649,8651],{"href":7217,"rel":8650},[4040],"Vue.js consulting services",[17,8653,8654],{},[4036,8655,8657],{"href":4315,"rel":8656},[4040],"outsourced Vue developers"," who specialize in Core Web Vitals, performance, and UX optimization.",[3114,8660],{"label":3116,"to":3117},[3119,8662,8663],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":338,"searchDepth":469,"depth":469,"links":8665},[8666,8667,8668,8676,8677,8678,8679,8680,8681,8682],{"id":7508,"depth":359,"text":7509},{"id":7538,"depth":359,"text":7539},{"id":7578,"depth":359,"text":7579,"children":8669},[8670,8671,8672,8673,8674,8675],{"id":7582,"depth":469,"text":7583},{"id":7856,"depth":469,"text":7857},{"id":7958,"depth":469,"text":7959},{"id":8085,"depth":469,"text":8086},{"id":8201,"depth":469,"text":8202},{"id":8311,"depth":469,"text":8312},{"id":8377,"depth":359,"text":8378},{"id":8406,"depth":359,"text":8407},{"id":8429,"depth":359,"text":8430},{"id":8465,"depth":359,"text":8466},{"id":8507,"depth":359,"text":8508},{"id":8588,"depth":359,"text":8589},{"id":3105,"depth":359,"text":8615},"2025-10-08","Learn how to prevent layout shifts in e-commerce image galleries and carousels. Epicmax — a Vue.js development and consulting agency — helps you improve Core Web Vitals, fix CLS issues, and boost performance and conversions.","\u002Fimages\u002Fblog\u002Fhow-to-fix-cls\u002Fcard.png",{},"\u002Fblog\u002Fhow-to-fix-cls",{"title":7453,"description":8684},"blog\u002Fhow-to-fix-cls","uW-yvqs9hIxwnnuhqI2kS2sk4aaK0PjTqw_ooE8wbn8",{"id":8692,"title":8693,"author":7,"authorSlug":8,"body":8694,"date":10288,"description":10289,"extension":3154,"faq":8,"featured":3155,"image":10290,"meta":10291,"navigation":472,"path":10292,"readTime":8,"seo":10293,"stem":10294,"tags":10295,"__hash__":10296},"blog\u002Fblog\u002Freact-vue.md","React vs Vue: Common Patterns Between Frameworks",{"type":10,"value":8695,"toc":10277},[8696,8699,8702,8705,8719,8723,8726,8729,8732,8829,8833,8836,8897,8900,8987,8990,8994,8997,9218,9221,9414,9431,9435,9438,9462,9465,9492,9495,9560,9563,9698,9702,9705,9743,9746,9782,9789,9872,9876,9878,9933,9935,9977,9980,9984,9986,10032,10034,10102,10105,10151,10154,10160,10222,10225,10257,10263,10265,10268,10271,10274],[13,8697,8698],{},"After six years of working with React, I transitioned to Vue about a year ago. Since then, I've built everything from UI libraries to full-scale applications using Vue.",[13,8700,8701],{},"This article is meant to help React developers understand key patterns and how they translate into Vue. If you're looking to expand your frontend skills or explore another popular JavaScript framework, this React-to-Vue comparison should make the transition smoother.",[13,8703,8704],{},"If you are coming from a React background and already have basic knowledge of Vue, you might benefit from more detailed articles on advanced techniques:",[41,8706,8707,8713],{},[44,8708,8709],{},[4036,8710,8712],{"href":8711},"\u002Fblog\u002Ffixing-vue-sfc-templates-for-react","Fixing Vue SFC templates for React developers",[44,8714,8715],{},[4036,8716,8718],{"href":8717},"\u002Fblog\u002Fusing-jsx-with-vue","Using JSX with Vue",[26,8720,8722],{"id":8721},"overview-react-vs-vue","Overview: React vs Vue",[13,8724,8725],{},"React and Vue.js are two of the most popular frontend frameworks for building user interfaces. While they share similar goals, they differ in syntax and architectural philosophy.",[13,8727,8728],{},"Teams planning to upgrade or migrate projects may benefit from guidance from a Vue.js migration company to ensure best practices and a seamless transition.",[13,8730,8731],{},"This guide compares common React patterns to their Vue counterparts so that React developers can quickly understand Vue's approach. Here is a comparison table of React features and their equivalents in Vue:",[179,8733,8734,8746],{},[182,8735,8736],{},[185,8737,8738,8740,8743],{},[188,8739,190],{},[188,8741,8742],{},"React",[188,8744,8745],{},"Vue",[196,8747,8748,8759,8770,8781,8792,8803,8814],{},[185,8749,8750,8753,8756],{},[201,8751,8752],{},"Defining markup to render",[201,8754,8755],{},"JSX",[201,8757,8758],{},"Templates",[185,8760,8761,8764,8767],{},[201,8762,8763],{},"Reuse logic between components",[201,8765,8766],{},"Hooks",[201,8768,8769],{},"Composables",[185,8771,8772,8775,8778],{},[201,8773,8774],{},"Passing data between components",[201,8776,8777],{},"Callback props",[201,8779,8780],{},"Events",[185,8782,8783,8786,8789],{},[201,8784,8785],{},"Passing content to render",[201,8787,8788],{},"JSX as a prop",[201,8790,8791],{},"Slots",[185,8793,8794,8797,8800],{},[201,8795,8796],{},"Rendering content based on data",[201,8798,8799],{},"Render props",[201,8801,8802],{},"Scoped slots",[185,8804,8805,8808,8811],{},[201,8806,8807],{},"Sharing state deeply",[201,8809,8810],{},"Context",[201,8812,8813],{},"Provide \u002F inject",[185,8815,8816,8819,8824],{},[201,8817,8818],{},"Polymorphic components",[201,8820,8821,8823],{},[63,8822,1983],{}," prop",[201,8825,8826],{},[63,8827,8828],{},"\u003Ccomponent :is=\"...\" >",[26,8830,8832],{"id":8831},"jsx-vs-templates-in-vue","JSX vs templates in Vue",[13,8834,8835],{},"React uses JSX to define markup directly in JavaScript:",[333,8837,8841],{"className":8838,"code":8839,"language":8840,"meta":338,"style":338},"language-jsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F Component.jsx\nexport default function Component({ greeting }) {\n  return \u003Cp>{greeting}\u003C\u002Fp>;\n}\n","jsx",[63,8842,8843,8848,8870,8893],{"__ignoreMap":338},[342,8844,8845],{"class":344,"line":345},[342,8846,8847],{"class":436},"\u002F\u002F Component.jsx\n",[342,8849,8850,8852,8854,8856,8859,8862,8865,8868],{"class":344,"line":359},[342,8851,479],{"class":442},[342,8853,482],{"class":442},[342,8855,935],{"class":830},[342,8857,8858],{"class":362}," Component",[342,8860,8861],{"class":446},"({",[342,8863,8864],{"class":2819}," greeting",[342,8866,8867],{"class":446}," })",[342,8869,503],{"class":446},[342,8871,8872,8874,8877,8879,8882,8885,8888,8890],{"class":344,"line":469},[342,8873,948],{"class":442},[342,8875,8876],{"class":446}," \u003C",[342,8878,13],{"class":496},[342,8880,8881],{"class":446},">{",[342,8883,8884],{"class":450},"greeting",[342,8886,8887],{"class":446},"}\u003C\u002F",[342,8889,13],{"class":496},[342,8891,8892],{"class":446},">;\n",[342,8894,8895],{"class":344,"line":476},[342,8896,956],{"class":446},[13,8898,8899],{},"Vue uses HTML-based templates in Single-File Components (SFCs):",[333,8901,8903],{"className":1246,"code":8902,"language":1248,"meta":338,"style":338},"\u003C!-- Component.vue -->\n\u003Cscript setup>\nconst props = defineProps(['greeting']);\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cp>{{ greeting }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n",[63,8904,8905,8910,8920,8946,8954,8962,8979],{"__ignoreMap":338},[342,8906,8907],{"class":344,"line":345},[342,8908,8909],{"class":436},"\u003C!-- Component.vue -->\n",[342,8911,8912,8914,8916,8918],{"class":344,"line":359},[342,8913,1023],{"class":446},[342,8915,1262],{"class":496},[342,8917,1265],{"class":830},[342,8919,1280],{"class":446},[342,8921,8922,8924,8927,8929,8932,8935,8937,8939,8941,8944],{"class":344,"line":469},[342,8923,831],{"class":830},[342,8925,8926],{"class":450}," props ",[342,8928,837],{"class":446},[342,8930,8931],{"class":362}," defineProps",[342,8933,8934],{"class":450},"([",[342,8936,3229],{"class":446},[342,8938,8884],{"class":352},[342,8940,3229],{"class":446},[342,8942,8943],{"class":450},"])",[342,8945,7819],{"class":446},[342,8947,8948,8950,8952],{"class":344,"line":476},[342,8949,1505],{"class":446},[342,8951,1262],{"class":496},[342,8953,1280],{"class":446},[342,8955,8956,8958,8960],{"class":344,"line":493},[342,8957,1023],{"class":446},[342,8959,1522],{"class":496},[342,8961,1280],{"class":446},[342,8963,8964,8966,8968,8970,8973,8975,8977],{"class":344,"line":506},[342,8965,1530],{"class":446},[342,8967,13],{"class":496},[342,8969,1035],{"class":446},[342,8971,8972],{"class":450},"{{ greeting }}",[342,8974,1505],{"class":446},[342,8976,13],{"class":496},[342,8978,1280],{"class":446},[342,8980,8981,8983,8985],{"class":344,"line":530},[342,8982,1505],{"class":446},[342,8984,1522],{"class":496},[342,8986,1280],{"class":446},[13,8988,8989],{},"Although Vue supports JSX, templates are preferred in the Vue ecosystem.",[26,8991,8993],{"id":8992},"react-hooks-vs-vue-composables","React hooks vs Vue composables",[13,8995,8996],{},"Hooks and composables are both ways to reuse logic. React example:",[333,8998,9002],{"className":8999,"code":9000,"language":9001,"meta":338,"style":338},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","function useFetch(url) {\n  const [data, setData] = useState(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    fetch(url)\n      .then(res => res.json())\n      .then(json => {\n        setData(json);\n        setLoading(false);\n      });\n  }, [url]);\n\n  return { data, loading };\n}\n","javascript",[63,9003,9004,9020,9049,9078,9082,9095,9106,9132,9146,9159,9173,9182,9195,9199,9214],{"__ignoreMap":338},[342,9005,9006,9008,9011,9013,9016,9018],{"class":344,"line":345},[342,9007,4783],{"class":830},[342,9009,9010],{"class":362}," useFetch",[342,9012,487],{"class":446},[342,9014,9015],{"class":2819},"url",[342,9017,294],{"class":446},[342,9019,503],{"class":446},[342,9021,9022,9024,9026,9029,9031,9034,9036,9038,9040,9042,9045,9047],{"class":344,"line":359},[342,9023,1101],{"class":830},[342,9025,3226],{"class":446},[342,9027,9028],{"class":450},"data",[342,9030,1047],{"class":446},[342,9032,9033],{"class":450}," setData",[342,9035,1212],{"class":446},[342,9037,1107],{"class":446},[342,9039,1020],{"class":362},[342,9041,487],{"class":496},[342,9043,9044],{"class":446},"null",[342,9046,294],{"class":496},[342,9048,7819],{"class":446},[342,9050,9051,9053,9055,9058,9060,9063,9065,9067,9069,9071,9074,9076],{"class":344,"line":469},[342,9052,1101],{"class":830},[342,9054,3226],{"class":446},[342,9056,9057],{"class":450},"loading",[342,9059,1047],{"class":446},[342,9061,9062],{"class":450}," setLoading",[342,9064,1212],{"class":446},[342,9066,1107],{"class":446},[342,9068,1020],{"class":362},[342,9070,487],{"class":496},[342,9072,9073],{"class":3603},"true",[342,9075,294],{"class":496},[342,9077,7819],{"class":446},[342,9079,9080],{"class":344,"line":476},[342,9081,473],{"emptyLinePlaceholder":472},[342,9083,9084,9087,9089,9091,9093],{"class":344,"line":493},[342,9085,9086],{"class":362},"  useEffect",[342,9088,487],{"class":496},[342,9090,941],{"class":446},[342,9092,1011],{"class":830},[342,9094,503],{"class":446},[342,9096,9097,9100,9102,9104],{"class":344,"line":506},[342,9098,9099],{"class":362},"    fetch",[342,9101,487],{"class":496},[342,9103,9015],{"class":450},[342,9105,706],{"class":496},[342,9107,9108,9111,9114,9116,9119,9121,9124,9126,9129],{"class":344,"line":530},[342,9109,9110],{"class":446},"      .",[342,9112,9113],{"class":362},"then",[342,9115,487],{"class":496},[342,9117,9118],{"class":2819},"res",[342,9120,1011],{"class":830},[342,9122,9123],{"class":450}," res",[342,9125,158],{"class":446},[342,9127,9128],{"class":362},"json",[342,9130,9131],{"class":496},"())\n",[342,9133,9134,9136,9138,9140,9142,9144],{"class":344,"line":540},[342,9135,9110],{"class":446},[342,9137,9113],{"class":362},[342,9139,487],{"class":496},[342,9141,9128],{"class":2819},[342,9143,1011],{"class":830},[342,9145,503],{"class":446},[342,9147,9148,9151,9153,9155,9157],{"class":344,"line":571},[342,9149,9150],{"class":362},"        setData",[342,9152,487],{"class":496},[342,9154,9128],{"class":450},[342,9156,294],{"class":496},[342,9158,7819],{"class":446},[342,9160,9161,9164,9166,9169,9171],{"class":344,"line":600},[342,9162,9163],{"class":362},"        setLoading",[342,9165,487],{"class":496},[342,9167,9168],{"class":3603},"false",[342,9170,294],{"class":496},[342,9172,7819],{"class":446},[342,9174,9175,9178,9180],{"class":344,"line":629},[342,9176,9177],{"class":446},"      }",[342,9179,294],{"class":496},[342,9181,7819],{"class":446},[342,9183,9184,9187,9189,9191,9193],{"class":344,"line":659},[342,9185,9186],{"class":446},"  },",[342,9188,3226],{"class":496},[342,9190,9015],{"class":450},[342,9192,8943],{"class":496},[342,9194,7819],{"class":446},[342,9196,9197],{"class":344,"line":688},[342,9198,473],{"emptyLinePlaceholder":472},[342,9200,9201,9203,9205,9207,9209,9211],{"class":344,"line":694},[342,9202,948],{"class":442},[342,9204,447],{"class":446},[342,9206,1432],{"class":450},[342,9208,1047],{"class":446},[342,9210,7629],{"class":450},[342,9212,9213],{"class":446}," };\n",[342,9215,9216],{"class":344,"line":700},[342,9217,956],{"class":446},[13,9219,9220],{},"Vue composable:",[333,9222,9224],{"className":8999,"code":9223,"language":9001,"meta":338,"style":338},"import { ref, onMounted } from 'vue';\n\nfunction useFetch(url) {\n  const data = ref(null);\n  const loading = ref(true);\n\n  onMounted(async () => {\n    const response = await fetch(url);\n    data.value = await response.json();\n    loading.value = false;\n  });\n\n  return { data, loading };\n}\n",[63,9225,9226,9251,9255,9269,9287,9305,9309,9324,9346,9369,9384,9392,9396,9410],{"__ignoreMap":338},[342,9227,9228,9230,9232,9234,9236,9239,9241,9243,9245,9247,9249],{"class":344,"line":345},[342,9229,443],{"class":442},[342,9231,447],{"class":446},[342,9233,2132],{"class":450},[342,9235,1047],{"class":446},[342,9237,9238],{"class":450}," onMounted",[342,9240,454],{"class":446},[342,9242,457],{"class":442},[342,9244,4017],{"class":446},[342,9246,1248],{"class":352},[342,9248,3229],{"class":446},[342,9250,7819],{"class":446},[342,9252,9253],{"class":344,"line":359},[342,9254,473],{"emptyLinePlaceholder":472},[342,9256,9257,9259,9261,9263,9265,9267],{"class":344,"line":469},[342,9258,4783],{"class":830},[342,9260,9010],{"class":362},[342,9262,487],{"class":446},[342,9264,9015],{"class":2819},[342,9266,294],{"class":446},[342,9268,503],{"class":446},[342,9270,9271,9273,9275,9277,9279,9281,9283,9285],{"class":344,"line":476},[342,9272,1101],{"class":830},[342,9274,1432],{"class":450},[342,9276,1107],{"class":446},[342,9278,2132],{"class":362},[342,9280,487],{"class":496},[342,9282,9044],{"class":446},[342,9284,294],{"class":496},[342,9286,7819],{"class":446},[342,9288,9289,9291,9293,9295,9297,9299,9301,9303],{"class":344,"line":493},[342,9290,1101],{"class":830},[342,9292,7629],{"class":450},[342,9294,1107],{"class":446},[342,9296,2132],{"class":362},[342,9298,487],{"class":496},[342,9300,9073],{"class":3603},[342,9302,294],{"class":496},[342,9304,7819],{"class":446},[342,9306,9307],{"class":344,"line":506},[342,9308,473],{"emptyLinePlaceholder":472},[342,9310,9311,9314,9316,9318,9320,9322],{"class":344,"line":530},[342,9312,9313],{"class":362},"  onMounted",[342,9315,487],{"class":496},[342,9317,1090],{"class":830},[342,9319,1008],{"class":446},[342,9321,1011],{"class":830},[342,9323,503],{"class":446},[342,9325,9326,9328,9331,9333,9335,9338,9340,9342,9344],{"class":344,"line":540},[342,9327,1158],{"class":830},[342,9329,9330],{"class":450}," response",[342,9332,1107],{"class":446},[342,9334,1170],{"class":442},[342,9336,9337],{"class":362}," fetch",[342,9339,487],{"class":496},[342,9341,9015],{"class":450},[342,9343,294],{"class":496},[342,9345,7819],{"class":446},[342,9347,9348,9351,9353,9355,9357,9359,9361,9363,9365,9367],{"class":344,"line":571},[342,9349,9350],{"class":450},"    data",[342,9352,158],{"class":446},[342,9354,1148],{"class":450},[342,9356,1107],{"class":446},[342,9358,1170],{"class":442},[342,9360,9330],{"class":450},[342,9362,158],{"class":446},[342,9364,9128],{"class":362},[342,9366,941],{"class":496},[342,9368,7819],{"class":446},[342,9370,9371,9374,9376,9378,9380,9382],{"class":344,"line":600},[342,9372,9373],{"class":450},"    loading",[342,9375,158],{"class":446},[342,9377,1148],{"class":450},[342,9379,1107],{"class":446},[342,9381,3604],{"class":3603},[342,9383,7819],{"class":446},[342,9385,9386,9388,9390],{"class":344,"line":629},[342,9387,1401],{"class":446},[342,9389,294],{"class":496},[342,9391,7819],{"class":446},[342,9393,9394],{"class":344,"line":659},[342,9395,473],{"emptyLinePlaceholder":472},[342,9397,9398,9400,9402,9404,9406,9408],{"class":344,"line":688},[342,9399,948],{"class":442},[342,9401,447],{"class":446},[342,9403,1432],{"class":450},[342,9405,1047],{"class":446},[342,9407,7629],{"class":450},[342,9409,9213],{"class":446},[342,9411,9412],{"class":344,"line":694},[342,9413,956],{"class":446},[13,9415,9416,9417,3011,9420,9423,9424,20,9427,9430],{},"Unlike React, which requires explicit hooks like ",[63,9418,9419],{},"useState",[63,9421,9422],{},"useEffect",", Vue's reactivity system is built-in. You use ",[63,9425,9426],{},"ref",[63,9428,9429],{},"reactive"," to declare reactive state, and Vue automatically tracks dependencies and updates.",[26,9432,9434],{"id":9433},"callback-props-vs-events","Callback props vs events",[13,9436,9437],{},"React pattern:",[333,9439,9441],{"className":8838,"code":9440,"language":8840,"meta":338,"style":338},"\u003CChildComponent onDataChange={handleDataChange} \u002F>\n",[63,9442,9443],{"__ignoreMap":338},[342,9444,9445,9447,9450,9453,9456,9459],{"class":344,"line":345},[342,9446,1023],{"class":446},[342,9448,9449],{"class":348},"ChildComponent",[342,9451,9452],{"class":830}," onDataChange",[342,9454,9455],{"class":446},"={",[342,9457,9458],{"class":450},"handleDataChange",[342,9460,9461],{"class":446},"} \u002F>\n",[13,9463,9464],{},"Vue pattern:",[333,9466,9468],{"className":1246,"code":9467,"language":1248,"meta":338,"style":338},"\u003CChildComponent @data-change=\"handleDataChange\" \u002F>\n",[63,9469,9470],{"__ignoreMap":338},[342,9471,9472,9474,9476,9479,9482,9484,9486,9488,9490],{"class":344,"line":345},[342,9473,1023],{"class":446},[342,9475,9449],{"class":496},[342,9477,9478],{"class":446}," @",[342,9480,9481],{"class":830},"data-change",[342,9483,837],{"class":446},[342,9485,566],{"class":446},[342,9487,9458],{"class":450},[342,9489,566],{"class":446},[342,9491,7779],{"class":446},[13,9493,9494],{},"React child:",[333,9496,9498],{"className":8999,"code":9497,"language":9001,"meta":338,"style":338},"function ChildComponent({ onDataChange }) {\n  return \u003Cbutton onClick={() => onDataChange('New Data')}>Click\u003C\u002Fbutton>;\n}\n",[63,9499,9500,9515,9556],{"__ignoreMap":338},[342,9501,9502,9504,9507,9509,9511,9513],{"class":344,"line":345},[342,9503,4783],{"class":830},[342,9505,9506],{"class":362}," ChildComponent",[342,9508,8861],{"class":446},[342,9510,9452],{"class":2819},[342,9512,8867],{"class":446},[342,9514,503],{"class":446},[342,9516,9517,9519,9521,9523,9526,9529,9531,9533,9535,9537,9540,9542,9544,9547,9550,9552,9554],{"class":344,"line":359},[342,9518,948],{"class":442},[342,9520,8876],{"class":446},[342,9522,2498],{"class":496},[342,9524,9525],{"class":830}," onClick",[342,9527,9528],{"class":446},"={()",[342,9530,1011],{"class":830},[342,9532,9452],{"class":362},[342,9534,487],{"class":450},[342,9536,3229],{"class":446},[342,9538,9539],{"class":352},"New Data",[342,9541,3229],{"class":446},[342,9543,294],{"class":450},[342,9545,9546],{"class":446},"}>",[342,9548,9549],{"class":450},"Click",[342,9551,1505],{"class":446},[342,9553,2498],{"class":496},[342,9555,8892],{"class":446},[342,9557,9558],{"class":344,"line":469},[342,9559,956],{"class":446},[13,9561,9562],{},"Vue child:",[333,9564,9566],{"className":1246,"code":9565,"language":1248,"meta":338,"style":338},"\u003Cscript setup>\nconst emit = defineEmits(['data-change']);\n\nfunction changeData() {\n  emit('data-change', 'New Data');\n}\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cbutton @click=\"changeData\">Click\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[63,9567,9568,9578,9602,9606,9617,9642,9646,9654,9662,9690],{"__ignoreMap":338},[342,9569,9570,9572,9574,9576],{"class":344,"line":345},[342,9571,1023],{"class":446},[342,9573,1262],{"class":496},[342,9575,1265],{"class":830},[342,9577,1280],{"class":446},[342,9579,9580,9582,9585,9587,9590,9592,9594,9596,9598,9600],{"class":344,"line":359},[342,9581,831],{"class":830},[342,9583,9584],{"class":450}," emit ",[342,9586,837],{"class":446},[342,9588,9589],{"class":362}," defineEmits",[342,9591,8934],{"class":450},[342,9593,3229],{"class":446},[342,9595,9481],{"class":352},[342,9597,3229],{"class":446},[342,9599,8943],{"class":450},[342,9601,7819],{"class":446},[342,9603,9604],{"class":344,"line":469},[342,9605,473],{"emptyLinePlaceholder":472},[342,9607,9608,9610,9613,9615],{"class":344,"line":476},[342,9609,4783],{"class":830},[342,9611,9612],{"class":362}," changeData",[342,9614,941],{"class":446},[342,9616,503],{"class":446},[342,9618,9619,9622,9624,9626,9628,9630,9632,9634,9636,9638,9640],{"class":344,"line":493},[342,9620,9621],{"class":362},"  emit",[342,9623,487],{"class":496},[342,9625,3229],{"class":446},[342,9627,9481],{"class":352},[342,9629,3229],{"class":446},[342,9631,1047],{"class":446},[342,9633,4017],{"class":446},[342,9635,9539],{"class":352},[342,9637,3229],{"class":446},[342,9639,294],{"class":496},[342,9641,7819],{"class":446},[342,9643,9644],{"class":344,"line":506},[342,9645,956],{"class":446},[342,9647,9648,9650,9652],{"class":344,"line":530},[342,9649,1505],{"class":446},[342,9651,1262],{"class":496},[342,9653,1280],{"class":446},[342,9655,9656,9658,9660],{"class":344,"line":540},[342,9657,1023],{"class":446},[342,9659,1522],{"class":496},[342,9661,1280],{"class":446},[342,9663,9664,9666,9668,9671,9673,9675,9678,9680,9682,9684,9686,9688],{"class":344,"line":571},[342,9665,1530],{"class":446},[342,9667,2498],{"class":496},[342,9669,9670],{"class":830}," @click",[342,9672,837],{"class":446},[342,9674,566],{"class":446},[342,9676,9677],{"class":352},"changeData",[342,9679,566],{"class":446},[342,9681,1035],{"class":446},[342,9683,9549],{"class":450},[342,9685,1505],{"class":446},[342,9687,2498],{"class":496},[342,9689,1280],{"class":446},[342,9691,9692,9694,9696],{"class":344,"line":600},[342,9693,1505],{"class":446},[342,9695,1522],{"class":496},[342,9697,1280],{"class":446},[26,9699,9701],{"id":9700},"jsx-as-prop-vs-vue-slots","JSX as prop vs Vue slots",[13,9703,9704],{},"React:",[333,9706,9708],{"className":8838,"code":9707,"language":8840,"meta":338,"style":338},"\u003CChildComponent>\n  \u003Ch1>Hello\u003C\u002Fh1>\n\u003C\u002FChildComponent>\n",[63,9709,9710,9718,9735],{"__ignoreMap":338},[342,9711,9712,9714,9716],{"class":344,"line":345},[342,9713,1023],{"class":446},[342,9715,9449],{"class":348},[342,9717,1280],{"class":446},[342,9719,9720,9722,9724,9726,9729,9731,9733],{"class":344,"line":359},[342,9721,1530],{"class":446},[342,9723,1556],{"class":496},[342,9725,1035],{"class":446},[342,9727,9728],{"class":450},"Hello",[342,9730,1505],{"class":446},[342,9732,1556],{"class":496},[342,9734,1280],{"class":446},[342,9736,9737,9739,9741],{"class":344,"line":469},[342,9738,1505],{"class":446},[342,9740,9449],{"class":348},[342,9742,1280],{"class":446},[13,9744,9745],{},"Vue:",[333,9747,9749],{"className":1246,"code":9748,"language":1248,"meta":338,"style":338},"\u003CChildComponent>\n  \u003Ctemplate #default>\n    \u003Ch1>Hello\u003C\u002Fh1>\n  \u003C\u002Ftemplate>\n\u003C\u002FChildComponent>\n",[63,9750,9751,9759,9764,9769,9774],{"__ignoreMap":338},[342,9752,9753,9755,9757],{"class":344,"line":345},[342,9754,1023],{"class":446},[342,9756,9449],{"class":496},[342,9758,1280],{"class":446},[342,9760,9761],{"class":344,"line":359},[342,9762,9763],{"class":450},"  \u003Ctemplate #default>\n",[342,9765,9766],{"class":344,"line":469},[342,9767,9768],{"class":450},"    \u003Ch1>Hello\u003C\u002Fh1>\n",[342,9770,9771],{"class":344,"line":476},[342,9772,9773],{"class":450},"  \u003C\u002Ftemplate>\n",[342,9775,9776,9778,9780],{"class":344,"line":493},[342,9777,1505],{"class":446},[342,9779,9449],{"class":496},[342,9781,1280],{"class":446},[13,9783,9784,9785,9788],{},"Vue slots are used in templates with ",[63,9786,9787],{},"\u003Cslot>\u003C\u002Fslot>",". Named slots allow multiple insertion points:",[333,9790,9792],{"className":1246,"code":9791,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cslot name=\"header\">\u003C\u002Fslot>\n  \u003Cslot>\u003C\u002Fslot> \u003C!-- default -->\n  \u003Cslot name=\"footer\">\u003C\u002Fslot>\n\u003C\u002Ftemplate>\n",[63,9793,9794,9802,9826,9841,9864],{"__ignoreMap":338},[342,9795,9796,9798,9800],{"class":344,"line":345},[342,9797,1023],{"class":446},[342,9799,1522],{"class":496},[342,9801,1280],{"class":446},[342,9803,9804,9806,9809,9811,9813,9815,9818,9820,9822,9824],{"class":344,"line":359},[342,9805,1530],{"class":446},[342,9807,9808],{"class":496},"slot",[342,9810,4012],{"class":830},[342,9812,837],{"class":446},[342,9814,566],{"class":446},[342,9816,9817],{"class":352},"header",[342,9819,566],{"class":446},[342,9821,8243],{"class":446},[342,9823,9808],{"class":496},[342,9825,1280],{"class":446},[342,9827,9828,9830,9832,9834,9836,9838],{"class":344,"line":469},[342,9829,1530],{"class":446},[342,9831,9808],{"class":496},[342,9833,8243],{"class":446},[342,9835,9808],{"class":496},[342,9837,1035],{"class":446},[342,9839,9840],{"class":436}," \u003C!-- default -->\n",[342,9842,9843,9845,9847,9849,9851,9853,9856,9858,9860,9862],{"class":344,"line":476},[342,9844,1530],{"class":446},[342,9846,9808],{"class":496},[342,9848,4012],{"class":830},[342,9850,837],{"class":446},[342,9852,566],{"class":446},[342,9854,9855],{"class":352},"footer",[342,9857,566],{"class":446},[342,9859,8243],{"class":446},[342,9861,9808],{"class":496},[342,9863,1280],{"class":446},[342,9865,9866,9868,9870],{"class":344,"line":493},[342,9867,1505],{"class":446},[342,9869,1522],{"class":496},[342,9871,1280],{"class":446},[26,9873,9875],{"id":9874},"render-props-vs-scoped-slots","Render props vs scoped slots",[13,9877,9704],{},[333,9879,9881],{"className":8838,"code":9880,"language":8840,"meta":338,"style":338},"\u003CMouseTracker render={({ x, y }) => \u003Cp>{x}, {y}\u003C\u002Fp>} \u002F>\n",[63,9882,9883],{"__ignoreMap":338},[342,9884,9885,9887,9890,9893,9896,9899,9901,9904,9906,9908,9910,9912,9914,9917,9919,9921,9923,9926,9928,9930],{"class":344,"line":345},[342,9886,1023],{"class":446},[342,9888,9889],{"class":348},"MouseTracker",[342,9891,9892],{"class":830}," render",[342,9894,9895],{"class":446},"={({",[342,9897,9898],{"class":2819}," x",[342,9900,1047],{"class":446},[342,9902,9903],{"class":2819}," y",[342,9905,8867],{"class":446},[342,9907,1011],{"class":830},[342,9909,8876],{"class":446},[342,9911,13],{"class":496},[342,9913,8881],{"class":446},[342,9915,9916],{"class":450},"x",[342,9918,703],{"class":446},[342,9920,118],{"class":450},[342,9922,2086],{"class":446},[342,9924,9925],{"class":450},"y",[342,9927,8887],{"class":446},[342,9929,13],{"class":496},[342,9931,9932],{"class":446},">} \u002F>\n",[13,9934,9745],{},[333,9936,9938],{"className":1246,"code":9937,"language":1248,"meta":338,"style":338},"\u003CMouseTracker v-slot=\"{ position }\">\n  \u003Cp>{{ position.x }}, {{ position.y }}\u003C\u002Fp>\n\u003C\u002FMouseTracker>\n",[63,9939,9940,9964,9969],{"__ignoreMap":338},[342,9941,9942,9944,9946,9949,9951,9953,9955,9958,9960,9962],{"class":344,"line":345},[342,9943,1023],{"class":446},[342,9945,9889],{"class":496},[342,9947,9948],{"class":830}," v-slot",[342,9950,837],{"class":446},[342,9952,566],{"class":446},[342,9954,2086],{"class":446},[342,9956,9957],{"class":450}," position ",[342,9959,703],{"class":446},[342,9961,566],{"class":446},[342,9963,1280],{"class":446},[342,9965,9966],{"class":344,"line":359},[342,9967,9968],{"class":450},"  \u003Cp>{{ position.x }}, {{ position.y }}\u003C\u002Fp>\n",[342,9970,9971,9973,9975],{"class":344,"line":469},[342,9972,1505],{"class":446},[342,9974,9889],{"class":496},[342,9976,1280],{"class":446},[13,9978,9979],{},"Scoped slots let you expose internal data to the parent for full control over rendering.",[26,9981,9983],{"id":9982},"context-api-vs-provideinject","Context API vs provide\u002Finject",[13,9985,9704],{},[333,9987,9989],{"className":8838,"code":9988,"language":8840,"meta":338,"style":338},"\u003CThemeContext.Provider value={{ theme, setTheme }}>\n  \u003CApp \u002F>\n\u003C\u002FThemeContext.Provider>\n",[63,9990,9991,10015,10024],{"__ignoreMap":338},[342,9992,9993,9995,9998,10001,10004,10007,10009,10012],{"class":344,"line":345},[342,9994,1023],{"class":446},[342,9996,9997],{"class":348},"ThemeContext.Provider",[342,9999,10000],{"class":830}," value",[342,10002,10003],{"class":446},"={{",[342,10005,10006],{"class":450}," theme",[342,10008,1047],{"class":446},[342,10010,10011],{"class":450}," setTheme ",[342,10013,10014],{"class":446},"}}>\n",[342,10016,10017,10019,10022],{"class":344,"line":359},[342,10018,1530],{"class":446},[342,10020,10021],{"class":348},"App",[342,10023,7779],{"class":446},[342,10025,10026,10028,10030],{"class":344,"line":469},[342,10027,1505],{"class":446},[342,10029,9997],{"class":348},[342,10031,1280],{"class":446},[13,10033,9745],{},[333,10035,10037],{"className":1246,"code":10036,"language":1248,"meta":338,"style":338},"\u003Cscript setup>\nconst theme = ref('light');\nprovide('theme', theme);\n\u003C\u002Fscript>\n",[63,10038,10039,10049,10073,10094],{"__ignoreMap":338},[342,10040,10041,10043,10045,10047],{"class":344,"line":345},[342,10042,1023],{"class":446},[342,10044,1262],{"class":496},[342,10046,1265],{"class":830},[342,10048,1280],{"class":446},[342,10050,10051,10053,10056,10058,10060,10062,10064,10067,10069,10071],{"class":344,"line":359},[342,10052,831],{"class":830},[342,10054,10055],{"class":450}," theme ",[342,10057,837],{"class":446},[342,10059,2132],{"class":362},[342,10061,487],{"class":450},[342,10063,3229],{"class":446},[342,10065,10066],{"class":352},"light",[342,10068,3229],{"class":446},[342,10070,294],{"class":450},[342,10072,7819],{"class":446},[342,10074,10075,10078,10080,10082,10085,10087,10089,10092],{"class":344,"line":469},[342,10076,10077],{"class":362},"provide",[342,10079,487],{"class":450},[342,10081,3229],{"class":446},[342,10083,10084],{"class":352},"theme",[342,10086,3229],{"class":446},[342,10088,1047],{"class":446},[342,10090,10091],{"class":450}," theme)",[342,10093,7819],{"class":446},[342,10095,10096,10098,10100],{"class":344,"line":476},[342,10097,1505],{"class":446},[342,10099,1262],{"class":496},[342,10101,1280],{"class":446},[13,10103,10104],{},"To consume:",[333,10106,10108],{"className":1246,"code":10107,"language":1248,"meta":338,"style":338},"\u003Cscript setup>\nconst theme = inject('theme');\n\u003C\u002Fscript>\n",[63,10109,10110,10120,10143],{"__ignoreMap":338},[342,10111,10112,10114,10116,10118],{"class":344,"line":345},[342,10113,1023],{"class":446},[342,10115,1262],{"class":496},[342,10117,1265],{"class":830},[342,10119,1280],{"class":446},[342,10121,10122,10124,10126,10128,10131,10133,10135,10137,10139,10141],{"class":344,"line":359},[342,10123,831],{"class":830},[342,10125,10055],{"class":450},[342,10127,837],{"class":446},[342,10129,10130],{"class":362}," inject",[342,10132,487],{"class":450},[342,10134,3229],{"class":446},[342,10136,10084],{"class":352},[342,10138,3229],{"class":446},[342,10140,294],{"class":450},[342,10142,7819],{"class":446},[342,10144,10145,10147,10149],{"class":344,"line":469},[342,10146,1505],{"class":446},[342,10148,1262],{"class":496},[342,10150,1280],{"class":446},[26,10152,8818],{"id":10153},"polymorphic-components",[13,10155,10156,10157,10159],{},"React pattern using the ",[63,10158,1983],{}," prop:",[333,10161,10163],{"className":8999,"code":10162,"language":9001,"meta":338,"style":338},"function Box({ as: Component = 'div', ...props }) {\n  return \u003CComponent {...props} \u002F>;\n}\n",[63,10164,10165,10201,10218],{"__ignoreMap":338},[342,10166,10167,10169,10172,10174,10177,10179,10181,10183,10185,10187,10189,10191,10194,10197,10199],{"class":344,"line":345},[342,10168,4783],{"class":830},[342,10170,10171],{"class":362}," Box",[342,10173,8861],{"class":446},[342,10175,10176],{"class":496}," as",[342,10178,500],{"class":446},[342,10180,8858],{"class":2819},[342,10182,1107],{"class":446},[342,10184,4017],{"class":446},[342,10186,1533],{"class":352},[342,10188,3229],{"class":446},[342,10190,1047],{"class":446},[342,10192,10193],{"class":446}," ...",[342,10195,10196],{"class":2819},"props",[342,10198,8867],{"class":446},[342,10200,503],{"class":446},[342,10202,10203,10205,10207,10210,10213,10215],{"class":344,"line":359},[342,10204,948],{"class":442},[342,10206,8876],{"class":446},[342,10208,10209],{"class":348},"Component",[342,10211,10212],{"class":446}," {...",[342,10214,10196],{"class":450},[342,10216,10217],{"class":446},"} \u002F>;\n",[342,10219,10220],{"class":344,"line":469},[342,10221,956],{"class":446},[13,10223,10224],{},"Vue dynamic components:",[333,10226,10228],{"className":1246,"code":10227,"language":1248,"meta":338,"style":338},"\u003Ccomponent :is=\"as\">\u003C\u002Fcomponent>\n",[63,10229,10230],{"__ignoreMap":338},[342,10231,10232,10234,10237,10240,10243,10245,10247,10249,10251,10253,10255],{"class":344,"line":345},[342,10233,1023],{"class":446},[342,10235,10236],{"class":496},"component",[342,10238,10239],{"class":446}," :",[342,10241,10242],{"class":830},"is",[342,10244,837],{"class":446},[342,10246,566],{"class":446},[342,10248,1983],{"class":450},[342,10250,566],{"class":446},[342,10252,8243],{"class":446},[342,10254,10236],{"class":496},[342,10256,1280],{"class":446},[13,10258,10259,10260,10262],{},"You can pass either an HTML tag name or a component to the ",[63,10261,10242],{}," attribute.",[26,10264,3106],{"id":3105},[13,10266,10267],{},"Migrating from React to Vue is easier when you map familiar patterns. Vue's Composition API and template syntax offer similar flexibility with a different philosophy. This comparison shows that while the syntax differs, most React patterns have Vue equivalents.",[13,10269,10270],{},"Whether you're building your next frontend app or just exploring another JavaScript framework, Vue is a powerful alternative for modern UI development.",[3114,10272],{"label":10273,"to":3117},"Talk to a Vue expert",[3119,10275,10276],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":338,"searchDepth":469,"depth":469,"links":10278},[10279,10280,10281,10282,10283,10284,10285,10286,10287],{"id":8721,"depth":359,"text":8722},{"id":8831,"depth":359,"text":8832},{"id":8992,"depth":359,"text":8993},{"id":9433,"depth":359,"text":9434},{"id":9700,"depth":359,"text":9701},{"id":9874,"depth":359,"text":9875},{"id":9982,"depth":359,"text":9983},{"id":10153,"depth":359,"text":8818},{"id":3105,"depth":359,"text":3106},"2025-06-05","A React-to-Vue comparison for developers switching frameworks: JSX vs templates, hooks vs composables, callback props vs events, context vs provide\u002Finject.","\u002Fimages\u002Fblog\u002Freact-vue\u002Fcard.png",{},"\u002Fblog\u002Freact-vue",{"title":8693,"description":10289},"blog\u002Freact-vue",[3164,8742],"QlQI2tMPjhg1DO0hh4W2BcX5lAOLhBOhnv7KSpKC18c",{"id":10298,"title":10299,"author":7,"authorSlug":8,"body":10300,"date":11412,"description":11413,"extension":3154,"faq":11414,"featured":472,"image":11425,"meta":11426,"navigation":472,"path":11427,"readTime":11428,"seo":11429,"stem":11430,"tags":8,"__hash__":11431},"blog\u002Fblog\u002Ftop-vue-js-companies-to-work-with.md","Top Vue.js Development Companies to Work With",{"type":10,"value":10301,"toc":11363},[10302,10305,10308,10311,10315,10318,10446,10450,10453,10456,10467,10470,10474,10723,10726,10730,10736,10749,10753,10756,10760,10763,10767,10770,10790,10797,10801,10804,10806,10810,10816,10819,10823,10826,10830,10839,10842,10845,10847,10851,10857,10860,10864,10867,10871,10874,10877,10880,10882,10886,10892,10895,10899,10902,10905,10913,10916,10919,10921,10925,10931,10934,10938,10941,10945,10948,10951,10954,10956,10960,10966,10969,10973,10976,10979,10987,10989,10993,10999,11002,11006,11009,11013,11016,11019,11022,11024,11028,11034,11037,11041,11044,11047,11055,11058,11061,11063,11067,11073,11076,11080,11083,11087,11090,11093,11096,11098,11102,11108,11111,11115,11118,11121,11129,11132,11135,11137,11141,11144,11268,11272,11275,11281,11287,11293,11299,11305,11309,11314,11317,11320,11325,11328,11333,11336,11341,11344,11349,11352,11355,11357,11360],[13,10303,10304],{},"Not all Vue.js development companies are solving the same problem. Some are good at shipping components quickly. A smaller number understand how to design Vue.js systems that stay maintainable at scale — where the architecture decisions made in week two do not become the performance bottlenecks and refactoring crises of month fourteen.",[13,10306,10307],{},"This guide is about the latter category. The ten companies profiled here were selected against a deliberately high bar: Vue 3 Composition API depth, TypeScript integration as standard, demonstrated experience with production SaaS products, and — where it exists — open-source ecosystem contribution that provides objective evidence of framework mastery.",[13,10309,10310],{},"Each profile is written to be genuinely useful for a technical decision-maker evaluating a Vue.js partner, not to fill a listicle quota.",[26,10312,10314],{"id":10313},"quick-pick-find-your-match-in-30-seconds","Quick Pick: Find Your Match in 30 Seconds",[13,10316,10317],{},"Skip the full profiles if you already know what you need. The table below maps the most common Vue.js project scenarios to the best-fit company from this list — with a one-line rationale for each recommendation.",[179,10319,10320,10333],{},[182,10321,10322],{},[185,10323,10324,10327,10330],{},[188,10325,10326],{},"You need…",[188,10328,10329],{},"Best fit",[188,10331,10332],{},"Why",[196,10334,10335,10347,10358,10369,10380,10391,10402,10413,10424,10435],{},[185,10336,10337,10340,10344],{},[201,10338,10339],{},"Deep Vue.js architecture + OSS expertise",[201,10341,10342],{},[17,10343,7497],{},[201,10345,10346],{},"Only team that built a major Vue.js library",[185,10348,10349,10352,10355],{},[201,10350,10351],{},"Full-cycle delivery with process discipline",[201,10353,10354],{},"Monterail",[201,10356,10357],{},"15 years of cross-functional Vue.js delivery",[185,10359,10360,10363,10366],{},[201,10361,10362],{},"Enterprise scale, parallel teams",[201,10364,10365],{},"Netguru",[201,10367,10368],{},"700+ engineers, Vue guild, proven at volume",[185,10370,10371,10374,10377],{},[201,10372,10373],{},"Vue + Node.js full-stack SaaS",[201,10375,10376],{},"Brocoders",[201,10378,10379],{},"JS-native stack, shared TypeScript contracts",[185,10381,10382,10385,10388],{},[201,10383,10384],{},"Product-led engineering, Vue + Rails",[201,10386,10387],{},"Railsware",[201,10389,10390],{},"2-decade track record, discovery-first model",[185,10392,10393,10396,10399],{},[201,10394,10395],{},"Complex forms and dynamic UI components",[201,10397,10398],{},"Vueform",[201,10400,10401],{},"Creators of the leading Vue.js form framework",[185,10403,10404,10407,10410],{},[201,10405,10406],{},"Methodical enterprise audit before delivery",[201,10408,10409],{},"Merixstudio",[201,10411,10412],{},"25-year history, audit-first planning model",[185,10414,10415,10418,10421],{},[201,10416,10417],{},"Composable-first migration, budget-conscious",[201,10419,10420],{},"Apiko",[201,10422,10423],{},"Specialised composable libs, $25–$49\u002Fhr",[185,10425,10426,10429,10432],{},[201,10427,10428],{},"Parallel migration at mid-to-large scale",[201,10430,10431],{},"10Clouds",[201,10433,10434],{},"Migration architect role, quality gate model",[185,10436,10437,10440,10443],{},[201,10438,10439],{},"Vue + Python, data-heavy SaaS dashboards",[201,10441,10442],{},"Sunscrapers",[201,10444,10445],{},"Best-in-class Vue + Django\u002FFastAPI combination",[26,10447,10449],{"id":10448},"what-separates-a-good-vuejs-partner-from-a-great-one","What Separates a Good Vue.js Partner from a Great One",[13,10451,10452],{},"Framework proficiency is the entry ticket, not the differentiator. The meaningful gap between Vue.js development companies shows up at the architecture layer — and it only becomes visible months into a project.",[13,10454,10455],{},"Three questions that reveal genuine Vue.js depth:",[2954,10457,10458,10461,10464],{},[44,10459,10460],{},"How do you structure state in a multi-module Vue 3 application approaching 100k lines of code?",[44,10462,10463],{},"Walk me through migrating a Vue 2 Options API codebase to Composition API without a feature freeze.",[44,10465,10466],{},"How do you design a component API for a shared library that 15 developers will extend over 3 years?",[13,10468,10469],{},"Teams that answer these with operational specificity — actual patterns, real trade-offs, specific examples from production code — are in a different league from teams reciting documentation. Every company in this guide can answer all three.",[26,10471,10473],{"id":10472},"company-comparison-at-a-glance","Company Comparison at a Glance",[179,10475,10476,10503],{},[182,10477,10478],{},[185,10479,10480,10483,10486,10489,10492,10495,10497,10500],{},[188,10481,10482],{},"Company",[188,10484,10485],{},"HQ",[188,10487,10488],{},"Founded",[188,10490,10491],{},"Rate ($\u002Fhr)",[188,10493,10494],{},"Vue Version",[188,10496,264],{},[188,10498,10499],{},"OSS Contrib.",[188,10501,10502],{},"Best For",[196,10504,10505,10532,10554,10575,10597,10618,10640,10660,10682,10703],{},[185,10506,10507,10511,10514,10517,10520,10523,10526,10529],{},[201,10508,10509],{},[17,10510,7497],{},[201,10512,10513],{},"Vilnius, LT",[201,10515,10516],{},"2017",[201,10518,10519],{},"$50–$99",[201,10521,10522],{},"Vue 2 & 3",[201,10524,10525],{},"Full",[201,10527,10528],{},"Vuestic UI (9k★)",[201,10530,10531],{},"Vue consulting, OSS, design systems",[185,10533,10534,10536,10539,10542,10544,10546,10548,10551],{},[201,10535,10354],{},[201,10537,10538],{},"Wrocław, PL",[201,10540,10541],{},"2010",[201,10543,10519],{},[201,10545,10522],{},[201,10547,10525],{},[201,10549,10550],{},"No",[201,10552,10553],{},"Full-cycle Vue product delivery",[185,10555,10556,10558,10561,10564,10566,10568,10570,10572],{},[201,10557,10365],{},[201,10559,10560],{},"Poznań, PL",[201,10562,10563],{},"2008",[201,10565,10519],{},[201,10567,10522],{},[201,10569,10525],{},[201,10571,10550],{},[201,10573,10574],{},"Enterprise Vue, large-scale teams",[185,10576,10577,10579,10582,10585,10587,10590,10592,10594],{},[201,10578,10376],{},[201,10580,10581],{},"Tallinn, EE",[201,10583,10584],{},"2015",[201,10586,10519],{},[201,10588,10589],{},"Vue 3",[201,10591,10525],{},[201,10593,10550],{},[201,10595,10596],{},"Vue + Node.js SaaS full-stack",[185,10598,10599,10601,10603,10606,10609,10611,10613,10615],{},[201,10600,10387],{},[201,10602,10513],{},[201,10604,10605],{},"2005",[201,10607,10608],{},"$100–$149",[201,10610,10522],{},[201,10612,10525],{},[201,10614,10550],{},[201,10616,10617],{},"Product-led Vue + Rails",[185,10619,10620,10622,10625,10628,10630,10632,10634,10637],{},[201,10621,10398],{},[201,10623,10624],{},"Remote (EU)",[201,10626,10627],{},"2020",[201,10629,10608],{},[201,10631,10589],{},[201,10633,10525],{},[201,10635,10636],{},"Vueform lib",[201,10638,10639],{},"Form & component architecture",[185,10641,10642,10644,10646,10649,10651,10653,10655,10657],{},[201,10643,10409],{},[201,10645,10560],{},[201,10647,10648],{},"1999",[201,10650,10519],{},[201,10652,10522],{},[201,10654,10525],{},[201,10656,10550],{},[201,10658,10659],{},"Enterprise audit + Vue delivery",[185,10661,10662,10664,10667,10670,10673,10675,10677,10679],{},[201,10663,10420],{},[201,10665,10666],{},"Ivano-Frank., UA",[201,10668,10669],{},"2014",[201,10671,10672],{},"$25–$49",[201,10674,10522],{},[201,10676,10525],{},[201,10678,10550],{},[201,10680,10681],{},"Composable-first Vue migrations",[185,10683,10684,10686,10689,10692,10694,10696,10698,10700],{},[201,10685,10431],{},[201,10687,10688],{},"Warsaw, PL",[201,10690,10691],{},"2009",[201,10693,10519],{},[201,10695,10522],{},[201,10697,10525],{},[201,10699,10550],{},[201,10701,10702],{},"Parallel Vue delivery at scale",[185,10704,10705,10707,10709,10712,10714,10716,10718,10720],{},[201,10706,10442],{},[201,10708,10688],{},[201,10710,10711],{},"2011",[201,10713,10519],{},[201,10715,10522],{},[201,10717,10525],{},[201,10719,10550],{},[201,10721,10722],{},"Vue + Python data SaaS",[10724,10725],"hr",{},[328,10727,10729],{"id":10728},"_1-epicmax-vilnius-lithuania","1. Epicmax — Vilnius, Lithuania",[13,10731,10732],{},[7602,10733],{"alt":10734,"src":10735},"Epicmax logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fepicmax.png",[13,10737,10738,10739,10742,10743,10748],{},"Epicmax holds a position in the Vue.js market that no other company on this list — or any other list — can replicate: ",[17,10740,10741],{},"they built the ecosystem"," . As the creators and maintainers of ",[4036,10744,10747],{"href":10745,"rel":10746},"https:\u002F\u002Fui.vuestic.dev\u002F",[4040],"Vuestic UI",", a production Vue 3 component library with over 9,000 GitHub stars, 80+ enterprise-grade components, and active deployment across more than 170 countries, Epicmax engineers have had to make Vue.js work for tens of thousands of developers in production environments they will never directly see. That experience is structurally different from client project experience, and it shows.",[328,10750,10752],{"id":10751},"why-epicmax-leads-this-list","Why Epicmax leads this list",[13,10754,10755],{},"The practical consequence of Epicmax's open-source work is a depth of Vue.js architectural judgment that is impossible to develop any other way. When their engineers review a component architecture, they are pattern-matching against a dataset of thousands of real Vue.js codebases from developers who used Vuestic UI in production — not just the handful of client projects a typical agency accumulates. Epicmax catches architectural problems that senior engineers at other firms miss, because they have seen the failure modes play out at library scale.",[328,10757,10759],{"id":10758},"case-study-snapshot","Case study snapshot",[13,10761,10762],{},"A Series B SaaS company came to Epicmax with a Vue 2 codebase approaching 80,000 lines — two years of product development built on the Options API, with a custom component library that had accumulated significant inconsistency as the team grew from 3 to 14 engineers. The primary constraint: the product could not stop shipping. Epicmax designed an incremental migration strategy using Vue's official migration build, produced automated codemods for the mechanical transformations, rebuilt the component library on Vuestic UI V3 with a documented API system, and delivered the complete Vue 3 migration over 18 weeks — with zero feature freeze and no production regressions in migrated modules. The resulting codebase had 40% fewer lines of frontend code, full TypeScript coverage, and a component library that new engineers could learn in days rather than weeks.",[328,10764,10766],{"id":10765},"featured-case-study-quility-insurance","Featured case study: Quility Insurance",[13,10768,10769],{},"Quility is an award-winning US insurtech company with a mission to make life insurance accessible, affordable, and customised for everyone. Their digital platform allows customers to apply for life insurance online through a 10-minute application or with the support of a licensed agent. The Epicmax team was brought in to accelerate the development of Quility HQ — a central hub for agents and employees to access sales tools, view statistics, and conduct agent training — and to build a mobile version of the intranet application.",[41,10771,10772,10778,10784],{},[44,10773,10774,10777],{},[17,10775,10776],{},"Stack:"," Vue 2\u002F3, Vuetify, TypeScript, Chart.js, SCSS",[44,10779,10780,10783],{},[17,10781,10782],{},"Duration:"," Ongoing, November 2021 — present",[44,10785,10786,10789],{},[17,10787,10788],{},"Results:"," mobile version shipped, full feature set delivered, legacy code refactored, theming support added",[13,10791,10792],{},[4036,10793,10796],{"href":10794,"rel":10795},"https:\u002F\u002Fepicmax.co\u002Fquility-insurance",[4040],"Read the full case study",[328,10798,10800],{"id":10799},"ratings-and-reviews","Ratings and Reviews",[13,10802,10803],{},"Strong record on Clutch with consistent feedback on Vue.js architectural depth and migration delivery.",[10724,10805],{},[328,10807,10809],{"id":10808},"_2-monterail-wrocław-poland","2. Monterail — Wrocław, Poland",[13,10811,10812],{},[7602,10813],{"alt":10814,"src":10815},"Monterail logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fmonterail.png",[13,10817,10818],{},"Monterail is the Vue.js development agency that European product companies return to across multiple projects — a reliable signal that the relationship is delivering something beyond a single successful delivery. Founded in Wrocław in 2010, the agency's cross-functional sprint model integrates Vue.js engineers, UX designers, and QA specialists into shared cycles that produce coherent products rather than technically correct code that does not serve the actual user.",[328,10820,10822],{"id":10821},"where-monterail-excels","Where Monterail excels",[13,10824,10825],{},"Their test-first approach — behavioural tests written before implementation — reduces regression rates significantly in products with rapid release cadences. For scale-ups where engineering velocity and quality must coexist, Monterail's fifteen years of Vue.js delivery discipline produces both.",[328,10827,10829],{"id":10828},"what-clients-say","What clients say",[10831,10832,10833,10836],"blockquote",{},[13,10834,10835],{},"\"Monterail's code review process caught architectural issues our internal team had missed entirely. Three sprints in, we had higher test coverage than we'd achieved in two years of in-house development.\"",[13,10837,10838],{},"— CTO, Series B HR tech company",[328,10840,10800],{"id":10841},"ratings-and-reviews-1",[13,10843,10844],{},"Multi-year Clutch presence with repeat-client testimonials emphasising delivery discipline and team integration.",[10724,10846],{},[328,10848,10850],{"id":10849},"_3-netguru-poznań-poland","3. Netguru — Poznań, Poland",[13,10852,10853],{},[7602,10854],{"alt":10855,"src":10856},"Netguru logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fnetguru.png",[13,10858,10859],{},"Netguru fields nearly 700 engineers, including one of the largest pools of senior Vue.js developers in Central Europe — organised into an internal Vue.js guild that propagates architectural best practices and collective learnings across all projects simultaneously. Founded in 2008, the agency's scale enables a delivery model for large Vue.js programmes that smaller firms cannot match: multiple senior engineers on parallel workstreams, programme-level architectural oversight, and QA infrastructure for large component systems.",[328,10861,10863],{"id":10862},"where-netguru-excels","Where Netguru excels",[13,10865,10866],{},"Their product discovery process — structured commercial and technical exploration before any implementation — ensures Vue.js architecture decisions are made with full understanding of five-year product trajectory, not just current requirements. For enterprise Vue.js programmes where late-stage architectural correction is commercially prohibitive, this front-loaded investment in clarity consistently reduces total delivery cost.",[328,10868,10870],{"id":10869},"when-not-to-choose-netguru","When NOT to choose Netguru",[13,10872,10873],{},"If your primary need is strategic Vue.js consulting or deep architecture work on a small, complex codebase rather than large-scale delivery capacity, Netguru's strengths are greater than the engagement requires. Epicmax or Vueform will deliver more value at lower cost in that scenario.",[328,10875,10800],{"id":10876},"ratings-and-reviews-2",[13,10878,10879],{},"Enterprise-tier Clutch profile with documented track record across large product programmes.",[10724,10881],{},[328,10883,10885],{"id":10884},"_4-brocoders-tallinn-estonia","4. Brocoders — Tallinn, Estonia",[13,10887,10888],{},[7602,10889],{"alt":10890,"src":10891},"Brocoders logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fbrocoders.png",[13,10893,10894],{},"Among Vue.js app development firms that cover the full stack, Brocoders is the strongest JavaScript-native option: Vue 3 frontends paired with NestJS backends, sharing TypeScript interface definitions across the API boundary. Founded in Tallinn in 2015, their full-stack coherence eliminates a category of integration bugs — frontend components consuming poorly-shaped API responses — that organisations using separate frontend and backend vendors consistently encounter.",[328,10896,10898],{"id":10897},"where-brocoders-excels","Where Brocoders excels",[13,10900,10901],{},"Weekly production deployments with short feedback cycles give SaaS product teams continuous visibility into progress. For companies that have experienced long development phases with divergence from product intent, Brocoders' high-frequency delivery culture is a process improvement that compounds into better commercial outcomes.",[328,10903,10829],{"id":10904},"what-clients-say-1",[10831,10906,10907,10910],{},[13,10908,10909],{},"\"We'd worked with agencies that handed over a big release every six weeks. With Brocoders we were reviewing working features in production every Friday. The product ended up meaningfully different — and better — than what we originally specified.\"",[13,10911,10912],{},"— VP Product, B2B SaaS company",[328,10914,10800],{"id":10915},"ratings-and-reviews-3",[13,10917,10918],{},"Strong Clutch presence with consistent feedback on delivery cadence and full-stack TypeScript coherence.",[10724,10920],{},[328,10922,10924],{"id":10923},"_5-railsware-vilnius-lithuania","5. Railsware — Vilnius, Lithuania",[13,10926,10927],{},[7602,10928],{"alt":10929,"src":10930},"Railsware logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Frailsware.png",[13,10932,10933],{},"Among Vue software development companies that treat engineering as a product discipline rather than a technical service, Railsware is the strongest available option. Founded in Vilnius in 2005, their twenty-year history spans every major evolution in modern frontend development — and their Vue.js practice benefits from two decades of watching architectural decisions succeed and fail in production, producing strong evidence-based opinions about what actually works.",[328,10935,10937],{"id":10936},"where-railsware-excels","Where Railsware excels",[13,10939,10940],{},"Their discovery sprint model — one week of architectural risk identification before implementation — consistently surfaces the decisions that will be most expensive to reverse later. For product companies where engineering quality and commercial alignment must both be high, Railsware's combined Vue.js depth and product thinking culture consistently delivers both.",[328,10942,10944],{"id":10943},"when-not-to-choose-railsware","When NOT to choose Railsware",[13,10946,10947],{},"Their $100–$149\u002Fhr rate is justified by the depth they bring, but it makes them less cost-efficient for straightforward Vue.js implementation work that does not require their level of strategic engineering judgment. For delivery-focused engagements where the architecture is already defined, Monterail or Brocoders provide equivalent execution quality at lower cost.",[328,10949,10800],{"id":10950},"ratings-and-reviews-4",[13,10952,10953],{},"Premium Clutch tier with feedback emphasising product-engineering discipline rather than pure delivery throughput.",[10724,10955],{},[328,10957,10959],{"id":10958},"_6-vueform-remote-eu-based","6. Vueform — Remote (EU-based)",[13,10961,10962],{},[7602,10963],{"alt":10964,"src":10965},"Vueform logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fvueform.png",[13,10967,10968],{},"Vueform is staffed by the kind of Vue.js experts who have spent years solving one specific category of Vue.js problem at depth — rather than accumulating surface-level proficiency across many. As creators of the Vueform framework, the most feature-complete open-source Vue.js form library available, their consulting practice applies this hard-won expertise directly to client products with complex data-entry requirements, dynamic schema systems, and multi-step workflow components.",[328,10970,10972],{"id":10971},"where-vueform-excels","Where Vueform excels",[13,10974,10975],{},"For projects where the primary Vue.js complexity lives in the component interaction layer — validation logic trees, conditional rendering, schema-driven UI, multi-step state — Vueform's specialisation delivers in timelines that general-purpose teams simply cannot achieve. Their framework's enterprise adoption is the most credible possible evidence of their component engineering quality: you can read the code and review how they handle edge cases before signing anything.",[328,10977,10829],{"id":10978},"what-clients-say-2",[10831,10980,10981,10984],{},[13,10982,10983],{},"\"We'd spent three months trying to build a dynamic form system with our Vue.js team. Vueform rebuilt it in three weeks and it handled every edge case we threw at it. The codebase went from 4,000 lines of custom form logic to 800 lines using their framework.\"",[13,10985,10986],{},"— Lead Engineer, insurance SaaS",[10724,10988],{},[328,10990,10992],{"id":10991},"_7-merixstudio-poznań-poland","7. Merixstudio — Poznań, Poland",[13,10994,10995],{},[7602,10996],{"alt":10997,"src":10998},"Merixstudio logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fmerixstudio.png",[13,11000,11001],{},"Merixstudio is one of the oldest custom Vue.js development companies in Poland — founded in 1999, making them one of the few Vue.js agencies with a software engineering track record longer than Vue.js itself. Their audit-first methodology: two weeks of architecture assessment before any delivery commitment, producing migration or build plans based on what the codebase actually contains rather than what the specification describes.",[328,11003,11005],{"id":11004},"where-merixstudio-excels","Where Merixstudio excels",[13,11007,11008],{},"Their pre-engagement audit — covering component complexity, third-party compatibility, TypeScript adoption, and build infrastructure — gives enterprise clients a factual investment decision basis before committing to a programme. For organisations that have experienced Vue.js project overruns from underestimated complexity, Merixstudio's methodical upfront assessment is one of the highest-ROI services available before a large engineering commitment.",[328,11010,11012],{"id":11011},"when-not-to-choose-merixstudio","When NOT to choose Merixstudio",[13,11014,11015],{},"Their scale and process rigour make them less agile for early-stage companies that need to move quickly and iterate. For seed to Series A SaaS companies, Brocoders or Apiko will deliver faster initial velocity at lower cost.",[328,11017,10800],{"id":11018},"ratings-and-reviews-5",[13,11020,11021],{},"Long-standing Clutch record with feedback emphasising the value of their pre-engagement audit phase.",[10724,11023],{},[328,11025,11027],{"id":11026},"_8-apiko-ivano-frankivsk-ukraine","8. Apiko — Ivano-Frankivsk, Ukraine",[13,11029,11030],{},[7602,11031],{"alt":11032,"src":11033},"Apiko logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fapiko.png",[13,11035,11036],{},"Among Vue software development firms in Eastern Europe at the accessible price tier, Apiko delivers the most architecturally sophisticated Vue.js migrations available. Founded in Ivano-Frankivsk in 2014, their composable-first approach to Vue 2 to Vue 3 migration produces not just framework-current code but genuinely better code — composable libraries that encapsulate reactive logic clearly, migrated components with fewer lines and more predictable behaviour, and an architectural foundation that accelerates future development rather than merely inheriting the structure of the Vue 2 original.",[328,11038,11040],{"id":11039},"where-apiko-excels","Where Apiko excels",[13,11042,11043],{},"The secondary value of Apiko's composable-first migration is an architectural dividend: the composable library produced during migration becomes a reusable asset for future development. New features built after migration are consistently faster to implement because the reactive logic patterns are established, documented, and reusable — rather than requiring engineers to rediscover the same patterns independently across components.",[328,11045,10829],{"id":11046},"what-clients-say-3",[10831,11048,11049,11052],{},[13,11050,11051],{},"\"Our Vue 2 codebase was technically migrated to Vue 3 syntax by our previous agency, but the architecture hadn't changed — it was still Options API thinking in Composition API clothing. Apiko rewrote the core modules correctly in four weeks. The difference in readability was immediate.\"",[13,11053,11054],{},"— Engineering Manager, B2B SaaS",[328,11056,10800],{"id":11057},"ratings-and-reviews-6",[13,11059,11060],{},"Solid Clutch presence at the accessible price tier with feedback emphasising architectural quality despite lower rates.",[10724,11062],{},[328,11064,11066],{"id":11065},"_9-10clouds-warsaw-poland","9. 10Clouds — Warsaw, Poland",[13,11068,11069],{},[7602,11070],{"alt":11071,"src":11072},"10Clouds logo","\u002Fimages\u002Fblog\u002Fcompanies\u002F10clouds.png",[13,11074,11075],{},"10Clouds is the right choice when the Vue.js programme requires parallel delivery velocity without sacrificing architectural consistency. Founded in Warsaw in 2009, their migration architect role — a senior Vue.js engineer responsible for the architecture standard across all parallel workstreams — is the governance mechanism that prevents large parallel Vue.js programmes from fragmenting into modules with incompatible patterns and inconsistent implementations.",[328,11077,11079],{"id":11078},"where-10clouds-excels","Where 10Clouds excels",[13,11081,11082],{},"Their quality gate process — every module reviewed against the migration architecture standard before merge — prevents pattern drift in programmes where multiple engineers work simultaneously. For Vue.js programmes where calendar velocity is constrained by external factors (investment timelines, product launch dates, competitive pressure), 10Clouds' parallel delivery capability compresses timelines without the consistency cost that ungoverned parallel work consistently produces.",[328,11084,11086],{"id":11085},"when-not-to-choose-10clouds","When NOT to choose 10Clouds",[13,11088,11089],{},"For small Vue.js projects where a two-engineer team working sequentially is the appropriate model, 10Clouds' governance infrastructure is overhead that does not pay for itself. Their sweet spot is programmes where parallel workstreams are genuinely necessary — typically five or more engineers and 20,000+ lines of Vue.js code to migrate or build.",[328,11091,10800],{"id":11092},"ratings-and-reviews-7",[13,11094,11095],{},"Established Clutch profile with feedback emphasising parallel-delivery governance and consistency at scale.",[10724,11097],{},[328,11099,11101],{"id":11100},"_10-sunscrapers-warsaw-poland","10. Sunscrapers — Warsaw, Poland",[13,11103,11104],{},[7602,11105],{"alt":11106,"src":11107},"Sunscrapers logo","\u002Fimages\u002Fblog\u002Fcompanies\u002Fsunscrapers.png",[13,11109,11110],{},"Sunscrapers is the strongest option on this list for Vue.js applications where the primary challenge is making large datasets fast, navigable, and coherent in a Vue.js interface. Founded in Warsaw in 2011, the firm's dual Vue.js and Python expertise allows them to address data-heavy Vue.js performance not just at the frontend rendering layer but at the API design layer — shaping Django and FastAPI endpoints around what Vue.js components actually need, eliminating the frontend transformation overhead that generic REST responses impose.",[328,11112,11114],{"id":11113},"where-sunscrapers-excels","Where Sunscrapers excels",[13,11116,11117],{},"Their performance measurement discipline — baseline Vue.js rendering metrics captured before any work begins, validated against after delivery — means clients know whether Sunscrapers' work actually improved performance rather than relying on subjective perception. For SaaS products where rendering performance is a user experience metric that directly affects retention, this measurement rigour is commercially meaningful.",[328,11119,10829],{"id":11120},"what-clients-say-4",[10831,11122,11123,11126],{},[13,11124,11125],{},"\"Our dashboard was loading 8 seconds for a 10,000-row dataset. Sunscrapers diagnosed the issue at the API layer — we were fetching 40 fields per row and using 4 in the view. After their API redesign and Vue.js virtual scrolling implementation, the same dataset loaded in 900ms.\"",[13,11127,11128],{},"— CTO, analytics SaaS",[328,11130,10800],{"id":11131},"ratings-and-reviews-8",[13,11133,11134],{},"Niche Clutch presence with strong feedback in data-heavy SaaS engagements.",[10724,11136],{},[26,11138,11140],{"id":11139},"typical-vuejs-project-timelines-and-budgets","Typical Vue.js Project Timelines and Budgets",[13,11142,11143],{},"Use the table below as a planning reference. Ranges are based on observed market rates for the companies in this guide and typical project scope — individual projects vary based on codebase complexity, team experience, and requirements clarity.",[179,11145,11146,11165],{},[182,11147,11148],{},[185,11149,11150,11153,11156,11159,11162],{},[188,11151,11152],{},"Project Type",[188,11154,11155],{},"Team Size",[188,11157,11158],{},"Timeline",[188,11160,11161],{},"Typical Budget",[188,11163,11164],{},"Recommended Partner Type",[196,11166,11167,11184,11201,11218,11234,11251],{},[185,11168,11169,11172,11175,11178,11181],{},[201,11170,11171],{},"Vue 3 architecture audit",[201,11173,11174],{},"1–2 consultants",[201,11176,11177],{},"2–4 weeks",[201,11179,11180],{},"$5k–$20k",[201,11182,11183],{},"Specialist (Epicmax, Vueform)",[185,11185,11186,11189,11192,11195,11198],{},[201,11187,11188],{},"MVP SaaS frontend (Vue 3)",[201,11190,11191],{},"2–3 engineers",[201,11193,11194],{},"8–14 weeks",[201,11196,11197],{},"$25k–$80k",[201,11199,11200],{},"Full-stack agency (Brocoders, Railsware)",[185,11202,11203,11206,11209,11212,11215],{},[201,11204,11205],{},"Design system build",[201,11207,11208],{},"2–4 engineers",[201,11210,11211],{},"10–16 weeks",[201,11213,11214],{},"$30k–$100k",[201,11216,11217],{},"OSS specialist (Epicmax)",[185,11219,11220,11223,11225,11228,11231],{},[201,11221,11222],{},"Vue 2 → Vue 3 migration",[201,11224,11208],{},[201,11226,11227],{},"8–24 weeks",[201,11229,11230],{},"$20k–$150k",[201,11232,11233],{},"Migration specialist (Epicmax, Apiko)",[185,11235,11236,11239,11242,11245,11248],{},[201,11237,11238],{},"Enterprise Vue.js product",[201,11240,11241],{},"5–10 engineers",[201,11243,11244],{},"6–18 months",[201,11246,11247],{},"$200k–$1M+",[201,11249,11250],{},"Scale agency (Netguru, Monterail)",[185,11252,11253,11256,11259,11262,11265],{},[201,11254,11255],{},"Full-stack Vue + backend",[201,11257,11258],{},"3–6 engineers",[201,11260,11261],{},"12–20 weeks",[201,11263,11264],{},"$60k–$250k",[201,11266,11267],{},"Full-stack firm (Brocoders, Sunscrapers)",[26,11269,11271],{"id":11270},"how-to-choose-the-right-vuejs-development-company","How to Choose the Right Vue.js Development Company",[13,11273,11274],{},"The Quick Pick table at the top of this guide covers the most common scenarios. For more complex decisions, the following framework helps:",[13,11276,11277,11280],{},[17,11278,11279],{},"Define the primary technical challenge first."," Architecture design, delivery execution, domain-specific expertise, scale, and cost-efficiency are all legitimate primary needs — but they point to different companies. Conflating them leads to selecting a company that does everything adequately but nothing exceptionally.",[13,11282,11283,11286],{},[17,11284,11285],{},"Evaluate Vue 3 depth, not Vue.js brand recognition."," A company with ten years of Vue.js experience but primarily Vue 2 work is less equipped for a modern Vue 3 project than a newer agency with a focused Vue 3 practice. Check portfolio dates and specifically ask for Composition API code samples.",[13,11288,11289,11292],{},[17,11290,11291],{},"Ask for architectural references, not outcome references."," Client testimonials about delivery speed and communication are common. References that address architectural quality — \"their component API design aged well as our team grew\" — are rarer and more revealing. Ask specifically for a reference from an engineer who worked with or reviewed the code the agency delivered.",[13,11294,11295,11298],{},[17,11296,11297],{},"Run the three-question test from earlier in this guide."," State management architecture, Options API migration, and component API design for shared libraries. The quality of the answer tells you more about Vue.js depth than any portfolio.",[13,11300,11301,11304],{},[17,11302,11303],{},"Consider what happens after the engagement ends."," The agency leaves; your engineering team inherits the codebase. The most commercially valuable Vue.js partners are those that produce codebases your internal team can confidently maintain, extend, and understand — and that invest in knowledge transfer so that capability stays with you rather than the agency.",[26,11306,11308],{"id":11307},"frequently-asked-questions","Frequently Asked Questions",[13,11310,11311],{},[17,11312,11313],{},"Vue.js vs React: which is better for SaaS products?",[13,11315,11316],{},"Neither framework is categorically better for SaaS — both have powered successful products at every scale. The practical considerations that favour Vue.js for SaaS: its more opinionated structure reduces architectural decision overhead for teams without a dedicated frontend architect; Pinia's state management API is cleaner and less boilerplate-heavy than the React ecosystem's fragmented state management options; and the Vue.js learning curve is consistently faster for engineers coming from traditional web backgrounds.",[13,11318,11319],{},"React's advantages: a larger talent pool in North American markets, broader enterprise adoption, and a larger ecosystem of third-party component libraries. For European SaaS companies, Vue.js is the more natural choice given the regional engineering talent distribution; for US-headquartered companies expecting primarily US engineering hires, React's talent pool advantage is real.",[13,11321,11322],{},[17,11323,11324],{},"How do I evaluate a Vue.js agency's code quality before hiring?",[13,11326,11327],{},"Three practical approaches. First, ask for a GitHub repository or code sample from a completed project — not a curated portfolio piece, but a real feature branch or component library they can share. Evaluate: Composition API usage patterns, TypeScript coverage, test coverage, and whether composables are used for logic reuse rather than mixins. Second, conduct a technical interview with the specific engineer(s) who will work on your project, not the sales team — use the three architecture questions in this guide. Third, ask for a paid, time-boxed architecture review of your current codebase or a specific technical problem: the quality of their analysis and recommendations is the most direct evidence of their Vue.js depth you can get before commitment.",[13,11329,11330],{},[17,11331,11332],{},"What is the difference between Vue 2 and Vue 3 that matters for choosing a development company?",[13,11334,11335],{},"The Composition API is the most significant difference for evaluating a Vue.js company. Vue 3's Composition API enables genuinely better code organisation — logic can be co-located, extracted into reusable composables, and tested independently in ways that Vue 2's Options API made structurally difficult. A company that still builds primarily with Options API patterns in Vue 3 is delivering technically current code with architecturally legacy structure. When evaluating agencies, ask whether they write new Vue 3 components with Composition API or Options API — the answer immediately reveals whether they have genuinely adopted Vue 3 or merely migrated their Vue 2 mindset to new syntax.",[13,11337,11338],{},[17,11339,11340],{},"How much does a Vue.js development company typically cost?",[13,11342,11343],{},"Eastern European agencies — representing the majority of this guide — charge $25–$99\u002Fhr for senior engineers. The $50–$99 range covers strong senior developers at well-established firms. Below $25\u002Fhr, quality consistency becomes unpredictable. Above $150\u002Fhr typically reflects North American or Western European agencies where the premium buys timezone alignment and enterprise contract infrastructure rather than proportionally better Vue.js technical depth. For most SaaS product companies, the $50–$99\u002Fhr range from established Eastern European Vue.js specialists offers the best combination of technical quality and commercial efficiency.",[13,11345,11346],{},[17,11347,11348],{},"Is it better to hire a Vue.js specialist agency or a general full-stack agency?",[13,11350,11351],{},"It depends entirely on where the primary complexity lives. If your product's hardest engineering problems are in the Vue.js frontend — architecture, component systems, performance — a specialist like Epicmax or Vueform will outperform a generalist at that specific challenge. If the complexity is evenly distributed across frontend and backend, a full-stack agency that handles both layers with a unified engineering culture (Brocoders, Railsware) typically produces more coherent outcomes than coordinating two separate specialists.",[13,11353,11354],{},"The most common mistake is hiring a full-stack generalist for a project where the Vue.js layer is the primary technical challenge — and getting backend-quality thinking applied to a frontend-architecture problem.",[10724,11356],{},[13,11358,11359],{},"The ten companies in this guide cover the full range of Vue.js development requirements — from ecosystem-level specialists who bring framework insight that client project experience cannot produce, to large process-mature agencies for enterprise scale, to domain specialists whose narrow focus delivers results generalists cannot match in their specific area.",[13,11361,11362],{},"The selection criteria that matter most in 2026: Vue 3 Composition API depth rather than Vue.js brand recognition; TypeScript as standard rather than option; a track record with production SaaS products at comparable complexity; and — most importantly — the ability to explain their architectural approach to your specific problem with operational specificity. The company that can do that convincingly is the one worth hiring.",{"title":338,"searchDepth":469,"depth":469,"links":11364},[11365,11366,11367,11409,11410,11411],{"id":10313,"depth":359,"text":10314},{"id":10448,"depth":359,"text":10449},{"id":10472,"depth":359,"text":10473,"children":11368},[11369,11370,11371,11372,11373,11374,11375,11376,11377,11378,11379,11380,11381,11382,11383,11384,11385,11386,11387,11388,11389,11390,11391,11392,11393,11394,11395,11396,11397,11398,11399,11400,11401,11402,11403,11404,11405,11406,11407,11408],{"id":10728,"depth":469,"text":10729},{"id":10751,"depth":469,"text":10752},{"id":10758,"depth":469,"text":10759},{"id":10765,"depth":469,"text":10766},{"id":10799,"depth":469,"text":10800},{"id":10808,"depth":469,"text":10809},{"id":10821,"depth":469,"text":10822},{"id":10828,"depth":469,"text":10829},{"id":10841,"depth":469,"text":10800},{"id":10849,"depth":469,"text":10850},{"id":10862,"depth":469,"text":10863},{"id":10869,"depth":469,"text":10870},{"id":10876,"depth":469,"text":10800},{"id":10884,"depth":469,"text":10885},{"id":10897,"depth":469,"text":10898},{"id":10904,"depth":469,"text":10829},{"id":10915,"depth":469,"text":10800},{"id":10923,"depth":469,"text":10924},{"id":10936,"depth":469,"text":10937},{"id":10943,"depth":469,"text":10944},{"id":10950,"depth":469,"text":10800},{"id":10958,"depth":469,"text":10959},{"id":10971,"depth":469,"text":10972},{"id":10978,"depth":469,"text":10829},{"id":10991,"depth":469,"text":10992},{"id":11004,"depth":469,"text":11005},{"id":11011,"depth":469,"text":11012},{"id":11018,"depth":469,"text":10800},{"id":11026,"depth":469,"text":11027},{"id":11039,"depth":469,"text":11040},{"id":11046,"depth":469,"text":10829},{"id":11057,"depth":469,"text":10800},{"id":11065,"depth":469,"text":11066},{"id":11078,"depth":469,"text":11079},{"id":11085,"depth":469,"text":11086},{"id":11092,"depth":469,"text":10800},{"id":11100,"depth":469,"text":11101},{"id":11113,"depth":469,"text":11114},{"id":11120,"depth":469,"text":10829},{"id":11131,"depth":469,"text":10800},{"id":11139,"depth":359,"text":11140},{"id":11270,"depth":359,"text":11271},{"id":11307,"depth":359,"text":11308},"2025-05-13","Compare the best Vue.js development firms — from specialized studios to global agencies. Find the right partner for your project.",[11415,11417,11419,11421,11423],{"question":11313,"answer":11416},"Neither framework is categorically better for SaaS — both have powered successful products at every scale. Vue.js favours SaaS teams without a dedicated frontend architect thanks to its more opinionated structure, Pinia's cleaner state management, and a faster learning curve. React's advantages are a larger North American talent pool, broader enterprise adoption, and a bigger third-party ecosystem. For European SaaS companies Vue.js is the more natural choice; for US-headquartered companies hiring primarily US engineers, React's talent-pool advantage is real.",{"question":11324,"answer":11418},"Three practical approaches. First, ask for a real GitHub repository or code sample — not a curated portfolio piece — and evaluate Composition API patterns, TypeScript coverage, test coverage, and composable usage. Second, conduct a technical interview with the engineers who will actually work on your project, not the sales team. Third, ask for a paid, time-boxed architecture review of your current codebase — the quality of their analysis is the most direct evidence of their Vue.js depth before commitment.",{"question":11332,"answer":11420},"The Composition API is the most significant difference. It enables genuinely better code organisation — logic can be co-located, extracted into reusable composables, and tested independently in ways Vue 2's Options API made difficult. A company that still builds primarily with Options API patterns in Vue 3 is delivering technically current code with architecturally legacy structure. Ask whether they write new Vue 3 components with the Composition API or Options API — the answer reveals whether they have genuinely adopted Vue 3.",{"question":11340,"answer":11422},"Eastern European agencies charge $25–$99\u002Fhr for senior engineers, with $50–$99 covering strong seniors at well-established firms. Below $25\u002Fhr, quality consistency becomes unpredictable. Above $150\u002Fhr typically reflects North American or Western European agencies where the premium buys timezone alignment and enterprise contract infrastructure rather than proportionally better technical depth. For most SaaS product companies, $50–$99\u002Fhr from established Eastern European specialists offers the best combination of quality and commercial efficiency.",{"question":11348,"answer":11424},"It depends on where the primary complexity lives. If your product's hardest engineering problems are in the Vue.js frontend — architecture, component systems, performance — a specialist will outperform a generalist. If complexity is evenly distributed across frontend and backend, a full-stack agency with a unified engineering culture typically produces more coherent outcomes than coordinating two specialists. The most common mistake is hiring a full-stack generalist for a project where the Vue.js layer is the primary technical challenge.","\u002Fimages\u002Fblog\u002Ftop-vue-js-companies-to-work-with\u002Fcard.png",{},"\u002Fblog\u002Ftop-vue-js-companies-to-work-with","12 min read",{"title":10299,"description":11413},"blog\u002Ftop-vue-js-companies-to-work-with","vxnxOfX9dWcBnryuHiDI1C3so_a3iqN6b4jzSacO-qI",{"id":11433,"title":11434,"author":7,"authorSlug":8,"body":11435,"date":11530,"description":11531,"extension":3154,"faq":8,"featured":3155,"image":11457,"meta":11532,"navigation":472,"path":11533,"readTime":8,"seo":11534,"stem":11535,"tags":11536,"__hash__":11537},"blog\u002Fblog\u002Fepicmax-madvue-2025.md","Epicmax Sponsors MadVue 2025 – Let's Meet in Madrid!",{"type":10,"value":11436,"toc":11525},[11437,11452,11458,11462,11471,11475,11482,11485,11499,11503,11506,11522],[13,11438,11439,11440,11443,11444,11447,11448,11451],{},"We're thrilled to announce that ",[17,11441,11442],{},"Epicmax is an official sponsor of MadVue 2025",", the Vue.js event happening on ",[17,11445,11446],{},"May 29th, 2025"," in ",[17,11449,11450],{},"Madrid, Spain","! MadVue brings together the most passionate Vue developers, community contributors, and forward-thinking companies — and we're proud to support this amazing event dedicated to innovation in the Vue ecosystem.",[13,11453,11454],{},[7602,11455],{"alt":11456,"src":11457},"MadVue 2025 in Madrid","\u002Fimages\u002Fblog\u002Fepicmax-madvue-2025\u002Fcard.jpeg",[26,11459,11461],{"id":11460},"why-madvue","Why MadVue?",[13,11463,11464,11465,20,11467,11470],{},"As long-time contributors to the Vue.js community and creators of open-source projects like ",[17,11466,10747],{},[17,11468,11469],{},"Vuestic Admin",", we deeply believe in the power of community-driven development. Events like MadVue are essential for sharing knowledge, sparking collaboration, and driving the future of front-end development.",[26,11472,11474],{"id":11473},"what-we-bring-to-the-table","What we bring to the table",[13,11476,11477,11478,11481],{},"With ",[17,11479,11480],{},"60+ Vue.js projects"," delivered for clients around the world, we've seen and solved nearly every front-end challenge out there. But we don't just ship code — we partner with our clients to embed frontend expertise directly into their product teams, helping them grow smarter and faster.",[13,11483,11484],{},"At MadVue, you'll have a chance to:",[41,11486,11487,11490,11493,11496],{},[44,11488,11489],{},"Meet our team in person",[44,11491,11492],{},"Ask questions about Vue.js architecture, component libraries, performance, and scalability",[44,11494,11495],{},"Get a behind-the-scenes look at Vuestic UI and what's next",[44,11497,11498],{},"Explore collaboration opportunities and career paths",[26,11500,11502],{"id":11501},"see-you-in-madrid","See you in Madrid!",[13,11504,11505],{},"Whether you're a developer, a company looking for Vue expertise, or someone who just loves frontend — we'd love to meet you at MadVue!",[41,11507,11508,11511,11514],{},[44,11509,11510],{},"🗓️ May 29, 2025",[44,11512,11513],{},"📍 Madrid, Spain",[44,11515,11516,11517],{},"👉 Get your ticket: ",[4036,11518,11521],{"href":11519,"rel":11520},"https:\u002F\u002Fmadvue.es\u002F",[4040],"madvue.es",[13,11523,11524],{},"Let's build the future of Vue — together.",{"title":338,"searchDepth":469,"depth":469,"links":11526},[11527,11528,11529],{"id":11460,"depth":359,"text":11461},{"id":11473,"depth":359,"text":11474},{"id":11501,"depth":359,"text":11502},"2025-05-12","Epicmax is an official sponsor of MadVue 2025 in Madrid on May 29th. Meet our team, talk Vue.js architecture, and get a look at what's next for Vuestic UI.",{},"\u002Fblog\u002Fepicmax-madvue-2025",{"title":11434,"description":11531},"blog\u002Fepicmax-madvue-2025",[8780,3164],"Td-ey1cbGF3WchTul266jJi82IW-KndK93f3mtDNg5w",{"id":11539,"title":11540,"author":7,"authorSlug":8,"body":11541,"date":11627,"description":11628,"extension":3154,"faq":8,"featured":3155,"image":11629,"meta":11630,"navigation":472,"path":11631,"readTime":8,"seo":11632,"stem":11633,"tags":11634,"__hash__":11637},"blog\u002Fblog\u002Fepicmax-official-nuxt-partner.md","Epicmax Becomes Official Nuxt Partner",{"type":10,"value":11542,"toc":11623},[11543,11549,11552,11558,11562,11565,11585,11588,11591,11599,11603,11617,11620],[13,11544,11545,11546],{},"We're happy to share some great news: ",[17,11547,11548],{},"Epicmax is now an official partner of Nuxt!",[13,11550,11551],{},"Nuxt is one of the most powerful and user-friendly frameworks for building modern web apps. It's built on top of Vue.js and gives you everything you need to create fast, secure, and scalable websites — with server-side rendering, static site generation, and great developer experience out of the box.",[13,11553,11554,11555,158],{},"But what really makes Nuxt special is the ",[17,11556,11557],{},"team behind it",[26,11559,11561],{"id":11560},"the-team-behind-nuxt","The team behind Nuxt",[13,11563,11564],{},"The Nuxt team is building a full suite of tools for developers:",[41,11566,11567,11573,11579],{},[44,11568,11569,11572],{},[17,11570,11571],{},"Nuxt UI Pro"," — a set of premium UI components to build beautiful interfaces faster",[44,11574,11575,11578],{},[17,11576,11577],{},"Nuxt Studio"," — a visual editor to manage content on Nuxt websites without touching code",[44,11580,11581,11584],{},[17,11582,11583],{},"NuxtHub"," — a full platform to deploy and manage Nuxt apps at scale",[13,11586,11587],{},"This shows one thing: the Nuxt team really cares about making development faster, smoother, and more fun — not just for indie developers, but for professional teams and businesses too.",[13,11589,11590],{},"At Epicmax, we've been using Nuxt in real-world projects for years: from landing pages to admin panels to full SaaS platforms. We love Nuxt because it enables us to move quickly and build reliable, high-quality apps that scale.",[13,11592,11593,11594,11598],{},"For companies with existing Vue or Nuxt 2 applications, our ",[4036,11595,11597],{"href":11596},"\u002Fservices\u002Fnuxt-migration","Nuxt migration services"," help upgrade smoothly to Nuxt 3 while ensuring better performance, maintainability, and long-term support.",[26,11600,11602],{"id":11601},"what-becoming-a-nuxt-partner-gives-us","What becoming a Nuxt partner gives us",[41,11604,11605,11608,11611,11614],{},[44,11606,11607],{},"✅ Early access to new features and tools",[44,11609,11610],{},"✅ Direct communication with the Nuxt core team",[44,11612,11613],{},"✅ Even more trust from our clients",[44,11615,11616],{},"✅ A chance to contribute back to the Vue\u002FNuxt community",[13,11618,11619],{},"We're proud to be part of this growing ecosystem and excited for what's next. If you're looking to build something with Nuxt — let's talk!",[3114,11621],{"label":11622,"to":3117},"Start a Nuxt project",{"title":338,"searchDepth":469,"depth":469,"links":11624},[11625,11626],{"id":11560,"depth":359,"text":11561},{"id":11601,"depth":359,"text":11602},"2025-04-02","Epicmax is now an official Nuxt partner — with early access to new features, direct communication with the Nuxt core team, and years of real-world Nuxt experience.","\u002Fimages\u002Fblog\u002Fepicmax-official-nuxt-partner\u002Fcard.png",{},"\u002Fblog\u002Fepicmax-official-nuxt-partner",{"title":11540,"description":11628},"blog\u002Fepicmax-official-nuxt-partner",[11635,11636],"Nuxt","Partnerships","b3pxQlMGWZMVsGbIVS07p0dhqZANQRbFGIiSLi8Cvg0",{"id":11639,"title":11640,"author":7,"authorSlug":8,"body":11641,"date":15633,"description":15634,"extension":3154,"faq":8,"featured":3155,"image":15635,"meta":15636,"navigation":472,"path":15637,"readTime":8,"seo":15638,"stem":15639,"tags":15640,"__hash__":15642},"blog\u002Fblog\u002Fvueform.md","Vueform + Vuestic UI: Full Integration for Scalable Vue Forms",{"type":10,"value":11642,"toc":15616},[11643,11646,11650,11653,11673,11677,11684,12003,12012,12036,12047,12051,12058,12218,12222,12326,12331,12335,12346,12716,12731,12735,12738,13054,13064,13068,13074,13141,13144,13148,13173,13279,13285,13349,13353,13356,13786,13790,13901,13905,13911,14552,14556,14772,14782,14925,14929,15118,15161,15165,15170,15206,15211,15379,15384,15576,15582,15585,15588,15608,15611,15613],[13,11644,11645],{},"Vueform is a powerful form framework for Vue, and Vuestic UI is a full-featured component library. This guide walks through building a custom wrapper around Vuestic UI components for use inside Vueform, covering everything you need for full compatibility.",[26,11647,11649],{"id":11648},"integration-methods","Integration methods",[13,11651,11652],{},"Vueform offers several integration strategies:",[41,11654,11655,11661,11667],{},[44,11656,11657,11660],{},[17,11658,11659],{},"Generic Elements"," — maximum flexibility, full control over props, slots, and events. In this article, we will consider this approach.",[44,11662,11663,11666],{},[17,11664,11665],{},"Plugins"," — more suitable for extending existing functionality, adding new props and features.",[44,11668,11669,11672],{},[17,11670,11671],{},"Alternative Views"," — easier if you don't want to develop a new element.",[26,11674,11676],{"id":11675},"step-1-create-a-custom-wrapper-for-a-vuestic-ui-component","Step 1: Create a custom wrapper for a Vuestic UI component",[13,11678,11679,11680,11683],{},"Let's create our first wrapper. For this, we will use ",[63,11681,11682],{},"VaInput"," from Vuestic UI:",[333,11685,11687],{"className":1246,"code":11686,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CElementLayout>\n    \u003Ctemplate #element>\n      \u003CVaInput \u002F>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate v-for=\"(component, slot) in elementSlots\" #[slot]>\n      \u003Cslot :name=\"slot\" :el$=\"el$\">\n        \u003Ccomponent :is=\"component\" :el$=\"el$\" \u002F>\n      \u003C\u002Fslot>\n    \u003C\u002Ftemplate>\n  \u003C\u002FElementLayout>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nimport { defineElement } from '@vueform\u002Fvueform'\nimport { VaInput } from 'vuestic-ui'\n\nexport default defineElement({\n  name: 'VaInputElement',\n  components: [VaInput],\n  setup(props, { element }) {\n    \u002F\u002F Initial setup\n  },\n})\n\u003C\u002Fscript>\n",[63,11688,11689,11697,11706,11720,11728,11736,11773,11804,11833,11841,11849,11857,11865,11869,11877,11897,11917,11921,11933,11949,11961,11980,11985,11989,11995],{"__ignoreMap":338},[342,11690,11691,11693,11695],{"class":344,"line":345},[342,11692,1023],{"class":446},[342,11694,1522],{"class":496},[342,11696,1280],{"class":446},[342,11698,11699,11701,11704],{"class":344,"line":359},[342,11700,1530],{"class":446},[342,11702,11703],{"class":496},"ElementLayout",[342,11705,1280],{"class":446},[342,11707,11708,11710,11712,11715,11718],{"class":344,"line":469},[342,11709,1553],{"class":446},[342,11711,1522],{"class":496},[342,11713,11714],{"class":446}," #",[342,11716,11717],{"class":830},"element",[342,11719,1280],{"class":446},[342,11721,11722,11724,11726],{"class":344,"line":476},[342,11723,1604],{"class":446},[342,11725,11682],{"class":496},[342,11727,7779],{"class":446},[342,11729,11730,11732,11734],{"class":344,"line":493},[342,11731,1793],{"class":446},[342,11733,1522],{"class":496},[342,11735,1280],{"class":446},[342,11737,11738,11740,11742,11745,11747,11749,11752,11754,11757,11760,11763,11765,11768,11770],{"class":344,"line":506},[342,11739,1553],{"class":446},[342,11741,1522],{"class":496},[342,11743,11744],{"class":442}," v-for",[342,11746,837],{"class":446},[342,11748,566],{"class":446},[342,11750,11751],{"class":450},"(component",[342,11753,1047],{"class":446},[342,11755,11756],{"class":450}," slot) ",[342,11758,11759],{"class":446},"in",[342,11761,11762],{"class":450}," elementSlots",[342,11764,566],{"class":446},[342,11766,11767],{"class":446}," #[",[342,11769,9808],{"class":450},[342,11771,11772],{"class":446},"]>\n",[342,11774,11775,11777,11779,11782,11784,11786,11788,11790,11793,11795,11797,11800,11802],{"class":344,"line":530},[342,11776,1604],{"class":446},[342,11778,9808],{"class":496},[342,11780,11781],{"class":830}," :name",[342,11783,837],{"class":446},[342,11785,566],{"class":446},[342,11787,9808],{"class":352},[342,11789,566],{"class":446},[342,11791,11792],{"class":830}," :el$",[342,11794,837],{"class":446},[342,11796,566],{"class":446},[342,11798,11799],{"class":352},"el$",[342,11801,566],{"class":446},[342,11803,1280],{"class":446},[342,11805,11806,11808,11810,11813,11815,11817,11819,11821,11823,11825,11827,11829,11831],{"class":344,"line":540},[342,11807,1679],{"class":446},[342,11809,10236],{"class":496},[342,11811,11812],{"class":830}," :is",[342,11814,837],{"class":446},[342,11816,566],{"class":446},[342,11818,10236],{"class":352},[342,11820,566],{"class":446},[342,11822,11792],{"class":830},[342,11824,837],{"class":446},[342,11826,566],{"class":446},[342,11828,11799],{"class":352},[342,11830,566],{"class":446},[342,11832,7779],{"class":446},[342,11834,11835,11837,11839],{"class":344,"line":571},[342,11836,1782],{"class":446},[342,11838,9808],{"class":496},[342,11840,1280],{"class":446},[342,11842,11843,11845,11847],{"class":344,"line":600},[342,11844,1793],{"class":446},[342,11846,1522],{"class":496},[342,11848,1280],{"class":446},[342,11850,11851,11853,11855],{"class":344,"line":629},[342,11852,1803],{"class":446},[342,11854,11703],{"class":496},[342,11856,1280],{"class":446},[342,11858,11859,11861,11863],{"class":344,"line":659},[342,11860,1505],{"class":446},[342,11862,1522],{"class":496},[342,11864,1280],{"class":446},[342,11866,11867],{"class":344,"line":688},[342,11868,473],{"emptyLinePlaceholder":472},[342,11870,11871,11873,11875],{"class":344,"line":694},[342,11872,1023],{"class":446},[342,11874,1262],{"class":496},[342,11876,1280],{"class":446},[342,11878,11879,11881,11883,11886,11888,11890,11892,11895],{"class":344,"line":700},[342,11880,443],{"class":442},[342,11882,447],{"class":446},[342,11884,11885],{"class":450}," defineElement",[342,11887,454],{"class":446},[342,11889,457],{"class":442},[342,11891,4017],{"class":446},[342,11893,11894],{"class":352},"@vueform\u002Fvueform",[342,11896,5273],{"class":446},[342,11898,11899,11901,11903,11906,11908,11910,11912,11915],{"class":344,"line":1461},[342,11900,443],{"class":442},[342,11902,447],{"class":446},[342,11904,11905],{"class":450}," VaInput",[342,11907,454],{"class":446},[342,11909,457],{"class":442},[342,11911,4017],{"class":446},[342,11913,11914],{"class":352},"vuestic-ui",[342,11916,5273],{"class":446},[342,11918,11919],{"class":344,"line":1478},[342,11920,473],{"emptyLinePlaceholder":472},[342,11922,11923,11925,11927,11929,11931],{"class":344,"line":1495},[342,11924,479],{"class":442},[342,11926,482],{"class":442},[342,11928,11885],{"class":362},[342,11930,487],{"class":450},[342,11932,490],{"class":446},[342,11934,11935,11938,11940,11942,11945,11947],{"class":344,"line":1502},[342,11936,11937],{"class":496},"  name",[342,11939,500],{"class":446},[342,11941,4017],{"class":446},[342,11943,11944],{"class":352},"VaInputElement",[342,11946,3229],{"class":446},[342,11948,527],{"class":446},[342,11950,11951,11954,11956,11959],{"class":344,"line":1512},[342,11952,11953],{"class":496},"  components",[342,11955,500],{"class":446},[342,11957,11958],{"class":450}," [VaInput]",[342,11960,527],{"class":446},[342,11962,11963,11965,11967,11969,11971,11973,11976,11978],{"class":344,"line":1517},[342,11964,5642],{"class":496},[342,11966,487],{"class":446},[342,11968,10196],{"class":2819},[342,11970,1047],{"class":446},[342,11972,447],{"class":446},[342,11974,11975],{"class":2819}," element",[342,11977,8867],{"class":446},[342,11979,503],{"class":446},[342,11981,11982],{"class":344,"line":1527},[342,11983,11984],{"class":436},"    \u002F\u002F Initial setup\n",[342,11986,11987],{"class":344,"line":1550},[342,11988,697],{"class":446},[342,11990,11991,11993],{"class":344,"line":1581},[342,11992,703],{"class":446},[342,11994,706],{"class":450},[342,11996,11997,11999,12001],{"class":344,"line":1601},[342,11998,1505],{"class":446},[342,12000,1262],{"class":496},[342,12002,1280],{"class":446},[13,12004,12005,12008,12009,12011],{},[63,12006,12007],{},"defineElement"," is imported directly from ",[63,12010,11894],{},". This is our main helper function for creating a custom component.",[13,12013,784,12014,12017,12018,118,12021,118,12024,118,12027,118,12029,118,12032,12035],{},[63,12015,12016],{},"v-for"," block passes generic slots from Vueform (",[63,12019,12020],{},"label",[63,12022,12023],{},"info",[63,12025,12026],{},"description",[63,12028,8269],{},[63,12030,12031],{},"between",[63,12033,12034],{},"after","). There aren't many of them, but they can conflict with slots from Vuestic UI, so they can be removed.",[13,12037,12038,12039,12042,12043,12046],{},"The name ",[63,12040,12041],{},"'VaInputElement'"," is special — every name should end with ",[63,12044,12045],{},"'Element'",". This allows Vueform to render the form through the schema (we'll touch on this later).",[26,12048,12050],{"id":12049},"step-2-register-your-component","Step 2: Register your component",[13,12052,12053,12054,12057],{},"Create ",[63,12055,12056],{},"vueform.config.ts"," in the root of the project and import your wrapper:",[333,12059,12062],{"className":12060,"code":12061,"language":1275,"meta":338,"style":338},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import en from '@vueform\u002Fvueform\u002Flocales\u002Fen'\nimport vueform from '@vueform\u002Fvueform\u002Fdist\u002Fvueform'\nimport { defineConfig } from '@vueform\u002Fvueform'\nimport '@vueform\u002Fvueform\u002Fdist\u002Fvueform.css';\nimport VaInputElement from '.\u002Fsrc\u002FVaInputElement.vue';\n\nexport default defineConfig({\n  theme: vueform,\n  locales: { en },\n  locale: 'en',\n  elements: [VaInputElement]\n})\n",[63,12063,12064,12080,12096,12114,12127,12145,12149,12161,12173,12186,12202,12212],{"__ignoreMap":338},[342,12065,12066,12068,12071,12073,12075,12078],{"class":344,"line":345},[342,12067,443],{"class":442},[342,12069,12070],{"class":450}," en ",[342,12072,815],{"class":442},[342,12074,4017],{"class":446},[342,12076,12077],{"class":352},"@vueform\u002Fvueform\u002Flocales\u002Fen",[342,12079,5273],{"class":446},[342,12081,12082,12084,12087,12089,12091,12094],{"class":344,"line":359},[342,12083,443],{"class":442},[342,12085,12086],{"class":450}," vueform ",[342,12088,815],{"class":442},[342,12090,4017],{"class":446},[342,12092,12093],{"class":352},"@vueform\u002Fvueform\u002Fdist\u002Fvueform",[342,12095,5273],{"class":446},[342,12097,12098,12100,12102,12104,12106,12108,12110,12112],{"class":344,"line":469},[342,12099,443],{"class":442},[342,12101,447],{"class":446},[342,12103,451],{"class":450},[342,12105,454],{"class":446},[342,12107,457],{"class":442},[342,12109,4017],{"class":446},[342,12111,11894],{"class":352},[342,12113,5273],{"class":446},[342,12115,12116,12118,12120,12123,12125],{"class":344,"line":476},[342,12117,443],{"class":442},[342,12119,4017],{"class":446},[342,12121,12122],{"class":352},"@vueform\u002Fvueform\u002Fdist\u002Fvueform.css",[342,12124,3229],{"class":446},[342,12126,7819],{"class":446},[342,12128,12129,12131,12134,12136,12138,12141,12143],{"class":344,"line":493},[342,12130,443],{"class":442},[342,12132,12133],{"class":450}," VaInputElement ",[342,12135,815],{"class":442},[342,12137,4017],{"class":446},[342,12139,12140],{"class":352},".\u002Fsrc\u002FVaInputElement.vue",[342,12142,3229],{"class":446},[342,12144,7819],{"class":446},[342,12146,12147],{"class":344,"line":506},[342,12148,473],{"emptyLinePlaceholder":472},[342,12150,12151,12153,12155,12157,12159],{"class":344,"line":530},[342,12152,479],{"class":442},[342,12154,482],{"class":442},[342,12156,451],{"class":362},[342,12158,487],{"class":450},[342,12160,490],{"class":446},[342,12162,12163,12166,12168,12171],{"class":344,"line":540},[342,12164,12165],{"class":496},"  theme",[342,12167,500],{"class":446},[342,12169,12170],{"class":450}," vueform",[342,12172,527],{"class":446},[342,12174,12175,12178,12180,12182,12184],{"class":344,"line":571},[342,12176,12177],{"class":496},"  locales",[342,12179,500],{"class":446},[342,12181,447],{"class":446},[342,12183,12070],{"class":450},[342,12185,6685],{"class":446},[342,12187,12188,12191,12193,12195,12198,12200],{"class":344,"line":600},[342,12189,12190],{"class":496},"  locale",[342,12192,500],{"class":446},[342,12194,4017],{"class":446},[342,12196,12197],{"class":352},"en",[342,12199,3229],{"class":446},[342,12201,527],{"class":446},[342,12203,12204,12207,12209],{"class":344,"line":629},[342,12205,12206],{"class":496},"  elements",[342,12208,500],{"class":446},[342,12210,12211],{"class":450}," [VaInputElement]\n",[342,12213,12214,12216],{"class":344,"line":659},[342,12215,703],{"class":446},[342,12217,706],{"class":450},[26,12219,12221],{"id":12220},"step-3-use-the-custom-element","Step 3: Use the custom element",[333,12223,12225],{"className":1246,"code":12224,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CVueform>\n    \u003CVaInputElement name=\"MyCustomInput\"\u002F>\n  \u003C\u002FVueform>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport VaInputElement from '.\u002FVaInputElement.vue'\n\u003C\u002Fscript>\n",[63,12226,12227,12235,12243,12263,12271,12279,12283,12303,12318],{"__ignoreMap":338},[342,12228,12229,12231,12233],{"class":344,"line":345},[342,12230,1023],{"class":446},[342,12232,1522],{"class":496},[342,12234,1280],{"class":446},[342,12236,12237,12239,12241],{"class":344,"line":359},[342,12238,1530],{"class":446},[342,12240,10398],{"class":496},[342,12242,1280],{"class":446},[342,12244,12245,12247,12249,12251,12253,12255,12258,12260],{"class":344,"line":469},[342,12246,1553],{"class":446},[342,12248,11944],{"class":496},[342,12250,4012],{"class":830},[342,12252,837],{"class":446},[342,12254,566],{"class":446},[342,12256,12257],{"class":352},"MyCustomInput",[342,12259,566],{"class":446},[342,12261,12262],{"class":446},"\u002F>\n",[342,12264,12265,12267,12269],{"class":344,"line":476},[342,12266,1803],{"class":446},[342,12268,10398],{"class":496},[342,12270,1280],{"class":446},[342,12272,12273,12275,12277],{"class":344,"line":493},[342,12274,1505],{"class":446},[342,12276,1522],{"class":496},[342,12278,1280],{"class":446},[342,12280,12281],{"class":344,"line":506},[342,12282,473],{"emptyLinePlaceholder":472},[342,12284,12285,12287,12289,12291,12293,12295,12297,12299,12301],{"class":344,"line":530},[342,12286,1023],{"class":446},[342,12288,1262],{"class":496},[342,12290,1265],{"class":830},[342,12292,1268],{"class":830},[342,12294,837],{"class":446},[342,12296,566],{"class":446},[342,12298,1275],{"class":352},[342,12300,566],{"class":446},[342,12302,1280],{"class":446},[342,12304,12305,12307,12309,12311,12313,12316],{"class":344,"line":540},[342,12306,443],{"class":442},[342,12308,12133],{"class":450},[342,12310,815],{"class":442},[342,12312,4017],{"class":446},[342,12314,12315],{"class":352},".\u002FVaInputElement.vue",[342,12317,5273],{"class":446},[342,12319,12320,12322,12324],{"class":344,"line":571},[342,12321,1505],{"class":446},[342,12323,1262],{"class":496},[342,12325,1280],{"class":446},[13,12327,784,12328,12330],{},[63,12329,4582],{}," attribute is mandatory for each element. So far, we can't pass props, slots, or event listeners. Also, Vueform doesn't know anything about our component's model, so validation won't work. Let's fix that next.",[26,12332,12334],{"id":12333},"step-4-add-v-model-and-an-input-handler","Step 4: Add v-model and an input handler",[13,12336,12337,12338,12341,12342,12345],{},"We need to bind the component to Vueform's model using ",[63,12339,12340],{},"model-value"," and update it with ",[63,12343,12344],{},"@update:model-value",". This gives us reactivity and enables validation:",[333,12347,12349],{"className":1246,"code":12348,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CElementLayout>\n    \u003Ctemplate #element>\n      \u003CVaInput :model-value=\"value\" @update:model-value=\"handleInput\" \u002F>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate v-for=\"(component, slot) in elementSlots\" #[slot]>\n      \u003Cslot :name=\"slot\" :el$=\"el$\">\n        \u003Ccomponent :is=\"component\" :el$=\"el$\" \u002F>\n      \u003C\u002Fslot>\n    \u003C\u002Ftemplate>\n  \u003C\u002FElementLayout>\n\u003C\u002Ftemplate>\n\u003Cscript>\nimport { defineElement } from '@vueform\u002Fvueform'\nimport { VaInput } from 'vuestic-ui'\n\nexport default defineElement({\n  name: 'VaInputElement',\n  components: [VaInput],\n  setup(props, { element }) {\n    const { value, update } = element\n    const handleInput = (val) => update(val)\n    return { value, handleInput }\n  },\n})\n\u003C\u002Fscript>\n",[63,12350,12351,12359,12367,12379,12410,12418,12448,12476,12504,12512,12520,12528,12536,12544,12562,12580,12584,12596,12610,12620,12638,12658,12684,12698,12702,12708],{"__ignoreMap":338},[342,12352,12353,12355,12357],{"class":344,"line":345},[342,12354,1023],{"class":446},[342,12356,1522],{"class":496},[342,12358,1280],{"class":446},[342,12360,12361,12363,12365],{"class":344,"line":359},[342,12362,1530],{"class":446},[342,12364,11703],{"class":496},[342,12366,1280],{"class":446},[342,12368,12369,12371,12373,12375,12377],{"class":344,"line":469},[342,12370,1553],{"class":446},[342,12372,1522],{"class":496},[342,12374,11714],{"class":446},[342,12376,11717],{"class":830},[342,12378,1280],{"class":446},[342,12380,12381,12383,12385,12388,12390,12392,12394,12396,12399,12401,12403,12406,12408],{"class":344,"line":476},[342,12382,1604],{"class":446},[342,12384,11682],{"class":496},[342,12386,12387],{"class":830}," :model-value",[342,12389,837],{"class":446},[342,12391,566],{"class":446},[342,12393,1148],{"class":352},[342,12395,566],{"class":446},[342,12397,12398],{"class":830}," @update:model-value",[342,12400,837],{"class":446},[342,12402,566],{"class":446},[342,12404,12405],{"class":352},"handleInput",[342,12407,566],{"class":446},[342,12409,7779],{"class":446},[342,12411,12412,12414,12416],{"class":344,"line":493},[342,12413,1793],{"class":446},[342,12415,1522],{"class":496},[342,12417,1280],{"class":446},[342,12419,12420,12422,12424,12426,12428,12430,12432,12434,12436,12438,12440,12442,12444,12446],{"class":344,"line":506},[342,12421,1553],{"class":446},[342,12423,1522],{"class":496},[342,12425,11744],{"class":442},[342,12427,837],{"class":446},[342,12429,566],{"class":446},[342,12431,11751],{"class":450},[342,12433,1047],{"class":446},[342,12435,11756],{"class":450},[342,12437,11759],{"class":446},[342,12439,11762],{"class":450},[342,12441,566],{"class":446},[342,12443,11767],{"class":446},[342,12445,9808],{"class":450},[342,12447,11772],{"class":446},[342,12449,12450,12452,12454,12456,12458,12460,12462,12464,12466,12468,12470,12472,12474],{"class":344,"line":530},[342,12451,1604],{"class":446},[342,12453,9808],{"class":496},[342,12455,11781],{"class":830},[342,12457,837],{"class":446},[342,12459,566],{"class":446},[342,12461,9808],{"class":352},[342,12463,566],{"class":446},[342,12465,11792],{"class":830},[342,12467,837],{"class":446},[342,12469,566],{"class":446},[342,12471,11799],{"class":352},[342,12473,566],{"class":446},[342,12475,1280],{"class":446},[342,12477,12478,12480,12482,12484,12486,12488,12490,12492,12494,12496,12498,12500,12502],{"class":344,"line":540},[342,12479,1679],{"class":446},[342,12481,10236],{"class":496},[342,12483,11812],{"class":830},[342,12485,837],{"class":446},[342,12487,566],{"class":446},[342,12489,10236],{"class":352},[342,12491,566],{"class":446},[342,12493,11792],{"class":830},[342,12495,837],{"class":446},[342,12497,566],{"class":446},[342,12499,11799],{"class":352},[342,12501,566],{"class":446},[342,12503,7779],{"class":446},[342,12505,12506,12508,12510],{"class":344,"line":571},[342,12507,1782],{"class":446},[342,12509,9808],{"class":496},[342,12511,1280],{"class":446},[342,12513,12514,12516,12518],{"class":344,"line":600},[342,12515,1793],{"class":446},[342,12517,1522],{"class":496},[342,12519,1280],{"class":446},[342,12521,12522,12524,12526],{"class":344,"line":629},[342,12523,1803],{"class":446},[342,12525,11703],{"class":496},[342,12527,1280],{"class":446},[342,12529,12530,12532,12534],{"class":344,"line":659},[342,12531,1505],{"class":446},[342,12533,1522],{"class":496},[342,12535,1280],{"class":446},[342,12537,12538,12540,12542],{"class":344,"line":688},[342,12539,1023],{"class":446},[342,12541,1262],{"class":496},[342,12543,1280],{"class":446},[342,12545,12546,12548,12550,12552,12554,12556,12558,12560],{"class":344,"line":694},[342,12547,443],{"class":442},[342,12549,447],{"class":446},[342,12551,11885],{"class":450},[342,12553,454],{"class":446},[342,12555,457],{"class":442},[342,12557,4017],{"class":446},[342,12559,11894],{"class":352},[342,12561,5273],{"class":446},[342,12563,12564,12566,12568,12570,12572,12574,12576,12578],{"class":344,"line":700},[342,12565,443],{"class":442},[342,12567,447],{"class":446},[342,12569,11905],{"class":450},[342,12571,454],{"class":446},[342,12573,457],{"class":442},[342,12575,4017],{"class":446},[342,12577,11914],{"class":352},[342,12579,5273],{"class":446},[342,12581,12582],{"class":344,"line":1461},[342,12583,473],{"emptyLinePlaceholder":472},[342,12585,12586,12588,12590,12592,12594],{"class":344,"line":1478},[342,12587,479],{"class":442},[342,12589,482],{"class":442},[342,12591,11885],{"class":362},[342,12593,487],{"class":450},[342,12595,490],{"class":446},[342,12597,12598,12600,12602,12604,12606,12608],{"class":344,"line":1495},[342,12599,11937],{"class":496},[342,12601,500],{"class":446},[342,12603,4017],{"class":446},[342,12605,11944],{"class":352},[342,12607,3229],{"class":446},[342,12609,527],{"class":446},[342,12611,12612,12614,12616,12618],{"class":344,"line":1502},[342,12613,11953],{"class":496},[342,12615,500],{"class":446},[342,12617,11958],{"class":450},[342,12619,527],{"class":446},[342,12621,12622,12624,12626,12628,12630,12632,12634,12636],{"class":344,"line":1512},[342,12623,5642],{"class":496},[342,12625,487],{"class":446},[342,12627,10196],{"class":2819},[342,12629,1047],{"class":446},[342,12631,447],{"class":446},[342,12633,11975],{"class":2819},[342,12635,8867],{"class":446},[342,12637,503],{"class":446},[342,12639,12640,12642,12644,12646,12648,12651,12653,12655],{"class":344,"line":1517},[342,12641,1158],{"class":830},[342,12643,447],{"class":446},[342,12645,10000],{"class":450},[342,12647,1047],{"class":446},[342,12649,12650],{"class":450}," update",[342,12652,454],{"class":446},[342,12654,1107],{"class":446},[342,12656,12657],{"class":450}," element\n",[342,12659,12660,12662,12665,12667,12669,12672,12674,12676,12678,12680,12682],{"class":344,"line":1527},[342,12661,1158],{"class":830},[342,12663,12664],{"class":450}," handleInput",[342,12666,1107],{"class":446},[342,12668,1138],{"class":446},[342,12670,12671],{"class":2819},"val",[342,12673,294],{"class":446},[342,12675,1011],{"class":830},[342,12677,12650],{"class":362},[342,12679,487],{"class":496},[342,12681,12671],{"class":450},[342,12683,706],{"class":496},[342,12685,12686,12688,12690,12692,12694,12696],{"class":344,"line":1550},[342,12687,2791],{"class":442},[342,12689,447],{"class":446},[342,12691,10000],{"class":450},[342,12693,1047],{"class":446},[342,12695,12664],{"class":450},[342,12697,2929],{"class":446},[342,12699,12700],{"class":344,"line":1581},[342,12701,697],{"class":446},[342,12703,12704,12706],{"class":344,"line":1601},[342,12705,703],{"class":446},[342,12707,706],{"class":450},[342,12709,12710,12712,12714],{"class":344,"line":1610},[342,12711,1505],{"class":446},[342,12713,1262],{"class":496},[342,12715,1280],{"class":446},[13,12717,784,12718,12720,12721,12724,12725,20,12727,12730],{},[63,12719,11717],{}," variable gives access to Vueform's ",[63,12722,12723],{},"GenericElement"," API. From it, we destructure ",[63,12726,1148],{},[63,12728,12729],{},"update",", and bind them to our Vuestic component.",[26,12732,12734],{"id":12733},"step-5-pass-props-from-vueform-to-vuestic-ui","Step 5: Pass props from Vueform to Vuestic UI",[13,12736,12737],{},"The next step is to pass props dynamically and filter out the ones that might conflict with Vueform internals:",[333,12739,12741],{"className":1246,"code":12740,"language":1248,"meta":338,"style":338},"\u003Cscript>\nimport { defineElement } from '@vueform\u002Fvueform'\nimport { VaInput } from 'vuestic-ui'\nimport { omit } from '.\u002Fomit'\nimport { computed } from 'vue'\n\nconst propsToOmit = ['rules']\n\nexport default defineElement({\n  name: 'VaInputElement',\n  components: [VaInput],\n  props: {\n    ...omit(VaInput.props, propsToOmit),\n  },\n  setup(props, { element }) {\n    const { value, update } = element\n    const handleInput = (val) => update(val)\n    const omittedProps = computed(() => omit(props, propsToOmit))\n    return { value, props: omittedProps, handleInput }\n  },\n})\n\u003C\u002Fscript>\n",[63,12742,12743,12751,12769,12787,12807,12825,12829,12849,12853,12865,12879,12889,12897,12919,12923,12941,12959,12983,13013,13036,13040,13046],{"__ignoreMap":338},[342,12744,12745,12747,12749],{"class":344,"line":345},[342,12746,1023],{"class":446},[342,12748,1262],{"class":496},[342,12750,1280],{"class":446},[342,12752,12753,12755,12757,12759,12761,12763,12765,12767],{"class":344,"line":359},[342,12754,443],{"class":442},[342,12756,447],{"class":446},[342,12758,11885],{"class":450},[342,12760,454],{"class":446},[342,12762,457],{"class":442},[342,12764,4017],{"class":446},[342,12766,11894],{"class":352},[342,12768,5273],{"class":446},[342,12770,12771,12773,12775,12777,12779,12781,12783,12785],{"class":344,"line":469},[342,12772,443],{"class":442},[342,12774,447],{"class":446},[342,12776,11905],{"class":450},[342,12778,454],{"class":446},[342,12780,457],{"class":442},[342,12782,4017],{"class":446},[342,12784,11914],{"class":352},[342,12786,5273],{"class":446},[342,12788,12789,12791,12793,12796,12798,12800,12802,12805],{"class":344,"line":476},[342,12790,443],{"class":442},[342,12792,447],{"class":446},[342,12794,12795],{"class":450}," omit",[342,12797,454],{"class":446},[342,12799,457],{"class":442},[342,12801,4017],{"class":446},[342,12803,12804],{"class":352},".\u002Fomit",[342,12806,5273],{"class":446},[342,12808,12809,12811,12813,12815,12817,12819,12821,12823],{"class":344,"line":493},[342,12810,443],{"class":442},[342,12812,447],{"class":446},[342,12814,1423],{"class":450},[342,12816,454],{"class":446},[342,12818,457],{"class":442},[342,12820,4017],{"class":446},[342,12822,1248],{"class":352},[342,12824,5273],{"class":446},[342,12826,12827],{"class":344,"line":506},[342,12828,473],{"emptyLinePlaceholder":472},[342,12830,12831,12833,12836,12838,12840,12842,12845,12847],{"class":344,"line":530},[342,12832,831],{"class":830},[342,12834,12835],{"class":450}," propsToOmit ",[342,12837,837],{"class":446},[342,12839,3226],{"class":450},[342,12841,3229],{"class":446},[342,12843,12844],{"class":352},"rules",[342,12846,3229],{"class":446},[342,12848,3699],{"class":450},[342,12850,12851],{"class":344,"line":540},[342,12852,473],{"emptyLinePlaceholder":472},[342,12854,12855,12857,12859,12861,12863],{"class":344,"line":571},[342,12856,479],{"class":442},[342,12858,482],{"class":442},[342,12860,11885],{"class":362},[342,12862,487],{"class":450},[342,12864,490],{"class":446},[342,12866,12867,12869,12871,12873,12875,12877],{"class":344,"line":600},[342,12868,11937],{"class":496},[342,12870,500],{"class":446},[342,12872,4017],{"class":446},[342,12874,11944],{"class":352},[342,12876,3229],{"class":446},[342,12878,527],{"class":446},[342,12880,12881,12883,12885,12887],{"class":344,"line":629},[342,12882,11953],{"class":496},[342,12884,500],{"class":446},[342,12886,11958],{"class":450},[342,12888,527],{"class":446},[342,12890,12891,12893,12895],{"class":344,"line":659},[342,12892,5291],{"class":496},[342,12894,500],{"class":446},[342,12896,503],{"class":446},[342,12898,12899,12902,12905,12908,12910,12912,12914,12917],{"class":344,"line":688},[342,12900,12901],{"class":446},"    ...",[342,12903,12904],{"class":362},"omit",[342,12906,12907],{"class":450},"(VaInput",[342,12909,158],{"class":446},[342,12911,10196],{"class":450},[342,12913,1047],{"class":446},[342,12915,12916],{"class":450}," propsToOmit)",[342,12918,527],{"class":446},[342,12920,12921],{"class":344,"line":694},[342,12922,697],{"class":446},[342,12924,12925,12927,12929,12931,12933,12935,12937,12939],{"class":344,"line":700},[342,12926,5642],{"class":496},[342,12928,487],{"class":446},[342,12930,10196],{"class":2819},[342,12932,1047],{"class":446},[342,12934,447],{"class":446},[342,12936,11975],{"class":2819},[342,12938,8867],{"class":446},[342,12940,503],{"class":446},[342,12942,12943,12945,12947,12949,12951,12953,12955,12957],{"class":344,"line":1461},[342,12944,1158],{"class":830},[342,12946,447],{"class":446},[342,12948,10000],{"class":450},[342,12950,1047],{"class":446},[342,12952,12650],{"class":450},[342,12954,454],{"class":446},[342,12956,1107],{"class":446},[342,12958,12657],{"class":450},[342,12960,12961,12963,12965,12967,12969,12971,12973,12975,12977,12979,12981],{"class":344,"line":1478},[342,12962,1158],{"class":830},[342,12964,12664],{"class":450},[342,12966,1107],{"class":446},[342,12968,1138],{"class":446},[342,12970,12671],{"class":2819},[342,12972,294],{"class":446},[342,12974,1011],{"class":830},[342,12976,12650],{"class":362},[342,12978,487],{"class":496},[342,12980,12671],{"class":450},[342,12982,706],{"class":496},[342,12984,12985,12987,12990,12992,12994,12996,12998,13000,13002,13004,13006,13008,13011],{"class":344,"line":1495},[342,12986,1158],{"class":830},[342,12988,12989],{"class":450}," omittedProps",[342,12991,1107],{"class":446},[342,12993,1423],{"class":362},[342,12995,487],{"class":496},[342,12997,941],{"class":446},[342,12999,1011],{"class":830},[342,13001,12795],{"class":362},[342,13003,487],{"class":496},[342,13005,10196],{"class":450},[342,13007,1047],{"class":446},[342,13009,13010],{"class":450}," propsToOmit",[342,13012,4778],{"class":496},[342,13014,13015,13017,13019,13021,13023,13026,13028,13030,13032,13034],{"class":344,"line":1502},[342,13016,2791],{"class":442},[342,13018,447],{"class":446},[342,13020,10000],{"class":450},[342,13022,1047],{"class":446},[342,13024,13025],{"class":496}," props",[342,13027,500],{"class":446},[342,13029,12989],{"class":450},[342,13031,1047],{"class":446},[342,13033,12664],{"class":450},[342,13035,2929],{"class":446},[342,13037,13038],{"class":344,"line":1512},[342,13039,697],{"class":446},[342,13041,13042,13044],{"class":344,"line":1517},[342,13043,703],{"class":446},[342,13045,706],{"class":450},[342,13047,13048,13050,13052],{"class":344,"line":1527},[342,13049,1505],{"class":446},[342,13051,1262],{"class":496},[342,13053,1280],{"class":446},[13,13055,13056,13057,13060,13061,13063],{},"Wrapping with ",[63,13058,13059],{},"computed()"," ensures reactivity. We omit props like ",[63,13062,12844],{}," to avoid conflicts.",[26,13065,13067],{"id":13066},"step-6-add-validation-with-the-rules-prop","Step 6: Add validation with the rules prop",[13,13069,13070,13071,13073],{},"Now we can pass the ",[63,13072,12844],{}," prop and see how it works:",[333,13075,13077],{"className":1246,"code":13076,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CVueform>\n    \u003CVaInputElement name=\"MyCustomInput\" rules=\"required|email|min:5\" \u002F>\n  \u003C\u002FVueform>\n\u003C\u002Ftemplate>\n",[63,13078,13079,13087,13095,13125,13133],{"__ignoreMap":338},[342,13080,13081,13083,13085],{"class":344,"line":345},[342,13082,1023],{"class":446},[342,13084,1522],{"class":496},[342,13086,1280],{"class":446},[342,13088,13089,13091,13093],{"class":344,"line":359},[342,13090,1530],{"class":446},[342,13092,10398],{"class":496},[342,13094,1280],{"class":446},[342,13096,13097,13099,13101,13103,13105,13107,13109,13111,13114,13116,13118,13121,13123],{"class":344,"line":469},[342,13098,1553],{"class":446},[342,13100,11944],{"class":496},[342,13102,4012],{"class":830},[342,13104,837],{"class":446},[342,13106,566],{"class":446},[342,13108,12257],{"class":352},[342,13110,566],{"class":446},[342,13112,13113],{"class":830}," rules",[342,13115,837],{"class":446},[342,13117,566],{"class":446},[342,13119,13120],{"class":352},"required|email|min:5",[342,13122,566],{"class":446},[342,13124,7779],{"class":446},[342,13126,13127,13129,13131],{"class":344,"line":476},[342,13128,1803],{"class":446},[342,13130,10398],{"class":496},[342,13132,1280],{"class":446},[342,13134,13135,13137,13139],{"class":344,"line":493},[342,13136,1505],{"class":446},[342,13138,1522],{"class":496},[342,13140,1280],{"class":446},[13,13142,13143],{},"Vueform will now validate the field using the provided rules, giving you full form validation support just like with native Vueform inputs.",[26,13145,13147],{"id":13146},"step-7-add-event-listeners-using-fire","Step 7: Add event listeners using fire()",[13,13149,13150,13151,13153,13154,13157,13158,13160,13161,13164,13165,13168,13169,13172],{},"To handle events, we first explicitly specify them in ",[63,13152,12007],{}," and use the ",[63,13155,13156],{},"fire"," function from ",[63,13159,11717],{},". We use ",[63,13162,13163],{},"fire()"," instead of native ",[63,13166,13167],{},"context.emit()"," because this way we can listen for events when our element is defined in the schema. To avoid binding each listener manually, we use ",[63,13170,13171],{},"reduce()"," to build a listeners object dynamically:",[333,13174,13176],{"className":4501,"code":13175,"language":4503,"meta":338,"style":338},"const listeners = this.emits.reduce((acc, curr) => {\n  acc[curr] = (...args) => {\n    fire(curr, ...args)\n  }\n  return acc\n}, {})\n",[63,13177,13178,13215,13241,13258,13262,13269],{"__ignoreMap":338},[342,13179,13180,13182,13185,13187,13189,13192,13194,13197,13199,13201,13204,13206,13209,13211,13213],{"class":344,"line":345},[342,13181,831],{"class":830},[342,13183,13184],{"class":450}," listeners ",[342,13186,837],{"class":446},[342,13188,4579],{"class":446},[342,13190,13191],{"class":450},"emits",[342,13193,158],{"class":446},[342,13195,13196],{"class":362},"reduce",[342,13198,487],{"class":450},[342,13200,487],{"class":446},[342,13202,13203],{"class":2819},"acc",[342,13205,1047],{"class":446},[342,13207,13208],{"class":2819}," curr",[342,13210,294],{"class":446},[342,13212,1011],{"class":830},[342,13214,503],{"class":446},[342,13216,13217,13220,13222,13225,13227,13229,13232,13235,13237,13239],{"class":344,"line":359},[342,13218,13219],{"class":450},"  acc",[342,13221,1205],{"class":496},[342,13223,13224],{"class":450},"curr",[342,13226,6682],{"class":496},[342,13228,837],{"class":446},[342,13230,13231],{"class":446}," (...",[342,13233,13234],{"class":2819},"args",[342,13236,294],{"class":446},[342,13238,1011],{"class":830},[342,13240,503],{"class":446},[342,13242,13243,13246,13248,13250,13252,13254,13256],{"class":344,"line":469},[342,13244,13245],{"class":362},"    fire",[342,13247,487],{"class":496},[342,13249,13224],{"class":450},[342,13251,1047],{"class":446},[342,13253,10193],{"class":446},[342,13255,13234],{"class":450},[342,13257,706],{"class":496},[342,13259,13260],{"class":344,"line":476},[342,13261,1229],{"class":446},[342,13263,13264,13266],{"class":344,"line":493},[342,13265,948],{"class":442},[342,13267,13268],{"class":450}," acc\n",[342,13270,13271,13274,13277],{"class":344,"line":506},[342,13272,13273],{"class":446},"},",[342,13275,13276],{"class":446}," {}",[342,13278,706],{"class":450},[13,13280,13281,13282,500],{},"Then we bind all emitted events via ",[63,13283,13284],{},"v-on=\"listeners\"",[333,13286,13288],{"className":1246,"code":13287,"language":1248,"meta":338,"style":338},"\u003CVaInput v-bind=\"props\" :model-value=\"value\" @update:model-value=\"handleInput\" v-on=\"listeners\" \u002F>\n",[63,13289,13290],{"__ignoreMap":338},[342,13291,13292,13294,13296,13299,13301,13303,13305,13307,13309,13311,13313,13315,13317,13319,13321,13323,13325,13327,13329,13331,13333,13335,13338,13340,13342,13345,13347],{"class":344,"line":345},[342,13293,1023],{"class":446},[342,13295,11682],{"class":496},[342,13297,13298],{"class":830}," v-bind",[342,13300,837],{"class":446},[342,13302,566],{"class":446},[342,13304,10196],{"class":450},[342,13306,566],{"class":446},[342,13308,10239],{"class":446},[342,13310,12340],{"class":830},[342,13312,837],{"class":446},[342,13314,566],{"class":446},[342,13316,1148],{"class":450},[342,13318,566],{"class":446},[342,13320,9478],{"class":446},[342,13322,12729],{"class":830},[342,13324,500],{"class":446},[342,13326,12340],{"class":830},[342,13328,837],{"class":446},[342,13330,566],{"class":446},[342,13332,12405],{"class":450},[342,13334,566],{"class":446},[342,13336,13337],{"class":830}," v-on",[342,13339,837],{"class":446},[342,13341,566],{"class":446},[342,13343,13344],{"class":450},"listeners",[342,13346,566],{"class":446},[342,13348,7779],{"class":446},[26,13350,13352],{"id":13351},"step-8-handle-vuestic-and-schema-slots","Step 8: Handle Vuestic and schema slots",[13,13354,13355],{},"Although the generic slot block can be removed, here's how to resolve slot conflicts between Vuestic-specific slots and schema-defined slots:",[333,13357,13359],{"className":1246,"code":13358,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CElementLayout>\n    \u003Ctemplate #element>\n      \u003CVaInput v-bind=\"props\" :model-value=\"value\" @update:model-value=\"handleInput\" v-on=\"listeners\">\n\n        \u003Ctemplate v-for=\"slotKey in vuesticSlotKeys\" #[slotKey]=\"slotProps\">\n          \u003Cslot :name=\"slotKey\" v-bind=\"slotProps\" \u002F>\n        \u003C\u002Ftemplate>\n\n        \u003Ctemplate v-for=\"(component, slot) in schemaSlots\" #[slot]=\"slotProps\">\n          \u003Cslot :name=\"slot\" :el$=\"el$\" v-bind=\"slotProps\">\n            \u003Ccomponent :is=\"component\" :el$=\"el$\" \u002F>\n          \u003C\u002Fslot>\n        \u003C\u002Ftemplate>\n\n      \u003C\u002FVaInput>\n    \u003C\u002Ftemplate>\n\n    \u003Ctemplate v-for=\"(component, slot) in elementSlots\" #[slot]>\n      \u003Cslot :name=\"slot\" :el$=\"el$\">\n        \u003Ccomponent :is=\"component\" :el$=\"el$\" \u002F>\n      \u003C\u002Fslot>\n    \u003C\u002Ftemplate>\n  \u003C\u002FElementLayout>\n\u003C\u002Ftemplate>\n",[63,13360,13361,13369,13377,13389,13437,13441,13480,13509,13517,13521,13560,13598,13627,13636,13644,13648,13656,13664,13668,13698,13726,13754,13762,13770,13778],{"__ignoreMap":338},[342,13362,13363,13365,13367],{"class":344,"line":345},[342,13364,1023],{"class":446},[342,13366,1522],{"class":496},[342,13368,1280],{"class":446},[342,13370,13371,13373,13375],{"class":344,"line":359},[342,13372,1530],{"class":446},[342,13374,11703],{"class":496},[342,13376,1280],{"class":446},[342,13378,13379,13381,13383,13385,13387],{"class":344,"line":469},[342,13380,1553],{"class":446},[342,13382,1522],{"class":496},[342,13384,11714],{"class":446},[342,13386,11717],{"class":830},[342,13388,1280],{"class":446},[342,13390,13391,13393,13395,13397,13399,13401,13403,13405,13407,13409,13411,13413,13415,13417,13419,13421,13423,13425,13427,13429,13431,13433,13435],{"class":344,"line":476},[342,13392,1604],{"class":446},[342,13394,11682],{"class":496},[342,13396,13298],{"class":830},[342,13398,837],{"class":446},[342,13400,566],{"class":446},[342,13402,10196],{"class":352},[342,13404,566],{"class":446},[342,13406,12387],{"class":830},[342,13408,837],{"class":446},[342,13410,566],{"class":446},[342,13412,1148],{"class":352},[342,13414,566],{"class":446},[342,13416,12398],{"class":830},[342,13418,837],{"class":446},[342,13420,566],{"class":446},[342,13422,12405],{"class":352},[342,13424,566],{"class":446},[342,13426,13337],{"class":830},[342,13428,837],{"class":446},[342,13430,566],{"class":446},[342,13432,13344],{"class":352},[342,13434,566],{"class":446},[342,13436,1280],{"class":446},[342,13438,13439],{"class":344,"line":493},[342,13440,473],{"emptyLinePlaceholder":472},[342,13442,13443,13445,13447,13449,13451,13453,13456,13458,13461,13463,13465,13468,13471,13473,13476,13478],{"class":344,"line":506},[342,13444,1679],{"class":446},[342,13446,1522],{"class":496},[342,13448,11744],{"class":442},[342,13450,837],{"class":446},[342,13452,566],{"class":446},[342,13454,13455],{"class":450},"slotKey ",[342,13457,11759],{"class":446},[342,13459,13460],{"class":450}," vuesticSlotKeys",[342,13462,566],{"class":446},[342,13464,11767],{"class":446},[342,13466,13467],{"class":450},"slotKey",[342,13469,13470],{"class":446},"]=",[342,13472,566],{"class":446},[342,13474,13475],{"class":450},"slotProps",[342,13477,566],{"class":446},[342,13479,1280],{"class":446},[342,13481,13482,13485,13487,13489,13491,13493,13495,13497,13499,13501,13503,13505,13507],{"class":344,"line":530},[342,13483,13484],{"class":446},"          \u003C",[342,13486,9808],{"class":496},[342,13488,11781],{"class":830},[342,13490,837],{"class":446},[342,13492,566],{"class":446},[342,13494,13467],{"class":352},[342,13496,566],{"class":446},[342,13498,13298],{"class":830},[342,13500,837],{"class":446},[342,13502,566],{"class":446},[342,13504,13475],{"class":352},[342,13506,566],{"class":446},[342,13508,7779],{"class":446},[342,13510,13511,13513,13515],{"class":344,"line":540},[342,13512,2495],{"class":446},[342,13514,1522],{"class":496},[342,13516,1280],{"class":446},[342,13518,13519],{"class":344,"line":571},[342,13520,473],{"emptyLinePlaceholder":472},[342,13522,13523,13525,13527,13529,13531,13533,13535,13537,13539,13541,13544,13546,13548,13550,13552,13554,13556,13558],{"class":344,"line":600},[342,13524,1679],{"class":446},[342,13526,1522],{"class":496},[342,13528,11744],{"class":442},[342,13530,837],{"class":446},[342,13532,566],{"class":446},[342,13534,11751],{"class":450},[342,13536,1047],{"class":446},[342,13538,11756],{"class":450},[342,13540,11759],{"class":446},[342,13542,13543],{"class":450}," schemaSlots",[342,13545,566],{"class":446},[342,13547,11767],{"class":446},[342,13549,9808],{"class":450},[342,13551,13470],{"class":446},[342,13553,566],{"class":446},[342,13555,13475],{"class":450},[342,13557,566],{"class":446},[342,13559,1280],{"class":446},[342,13561,13562,13564,13566,13568,13570,13572,13574,13576,13578,13580,13582,13584,13586,13588,13590,13592,13594,13596],{"class":344,"line":629},[342,13563,13484],{"class":446},[342,13565,9808],{"class":496},[342,13567,11781],{"class":830},[342,13569,837],{"class":446},[342,13571,566],{"class":446},[342,13573,9808],{"class":352},[342,13575,566],{"class":446},[342,13577,11792],{"class":830},[342,13579,837],{"class":446},[342,13581,566],{"class":446},[342,13583,11799],{"class":352},[342,13585,566],{"class":446},[342,13587,13298],{"class":830},[342,13589,837],{"class":446},[342,13591,566],{"class":446},[342,13593,13475],{"class":352},[342,13595,566],{"class":446},[342,13597,1280],{"class":446},[342,13599,13600,13603,13605,13607,13609,13611,13613,13615,13617,13619,13621,13623,13625],{"class":344,"line":659},[342,13601,13602],{"class":446},"            \u003C",[342,13604,10236],{"class":496},[342,13606,11812],{"class":830},[342,13608,837],{"class":446},[342,13610,566],{"class":446},[342,13612,10236],{"class":352},[342,13614,566],{"class":446},[342,13616,11792],{"class":830},[342,13618,837],{"class":446},[342,13620,566],{"class":446},[342,13622,11799],{"class":352},[342,13624,566],{"class":446},[342,13626,7779],{"class":446},[342,13628,13629,13632,13634],{"class":344,"line":688},[342,13630,13631],{"class":446},"          \u003C\u002F",[342,13633,9808],{"class":496},[342,13635,1280],{"class":446},[342,13637,13638,13640,13642],{"class":344,"line":694},[342,13639,2495],{"class":446},[342,13641,1522],{"class":496},[342,13643,1280],{"class":446},[342,13645,13646],{"class":344,"line":700},[342,13647,473],{"emptyLinePlaceholder":472},[342,13649,13650,13652,13654],{"class":344,"line":1461},[342,13651,1782],{"class":446},[342,13653,11682],{"class":496},[342,13655,1280],{"class":446},[342,13657,13658,13660,13662],{"class":344,"line":1478},[342,13659,1793],{"class":446},[342,13661,1522],{"class":496},[342,13663,1280],{"class":446},[342,13665,13666],{"class":344,"line":1495},[342,13667,473],{"emptyLinePlaceholder":472},[342,13669,13670,13672,13674,13676,13678,13680,13682,13684,13686,13688,13690,13692,13694,13696],{"class":344,"line":1502},[342,13671,1553],{"class":446},[342,13673,1522],{"class":496},[342,13675,11744],{"class":442},[342,13677,837],{"class":446},[342,13679,566],{"class":446},[342,13681,11751],{"class":450},[342,13683,1047],{"class":446},[342,13685,11756],{"class":450},[342,13687,11759],{"class":446},[342,13689,11762],{"class":450},[342,13691,566],{"class":446},[342,13693,11767],{"class":446},[342,13695,9808],{"class":450},[342,13697,11772],{"class":446},[342,13699,13700,13702,13704,13706,13708,13710,13712,13714,13716,13718,13720,13722,13724],{"class":344,"line":1512},[342,13701,1604],{"class":446},[342,13703,9808],{"class":496},[342,13705,11781],{"class":830},[342,13707,837],{"class":446},[342,13709,566],{"class":446},[342,13711,9808],{"class":352},[342,13713,566],{"class":446},[342,13715,11792],{"class":830},[342,13717,837],{"class":446},[342,13719,566],{"class":446},[342,13721,11799],{"class":352},[342,13723,566],{"class":446},[342,13725,1280],{"class":446},[342,13727,13728,13730,13732,13734,13736,13738,13740,13742,13744,13746,13748,13750,13752],{"class":344,"line":1517},[342,13729,1679],{"class":446},[342,13731,10236],{"class":496},[342,13733,11812],{"class":830},[342,13735,837],{"class":446},[342,13737,566],{"class":446},[342,13739,10236],{"class":352},[342,13741,566],{"class":446},[342,13743,11792],{"class":830},[342,13745,837],{"class":446},[342,13747,566],{"class":446},[342,13749,11799],{"class":352},[342,13751,566],{"class":446},[342,13753,7779],{"class":446},[342,13755,13756,13758,13760],{"class":344,"line":1527},[342,13757,1782],{"class":446},[342,13759,9808],{"class":496},[342,13761,1280],{"class":446},[342,13763,13764,13766,13768],{"class":344,"line":1550},[342,13765,1793],{"class":446},[342,13767,1522],{"class":496},[342,13769,1280],{"class":446},[342,13771,13772,13774,13776],{"class":344,"line":1581},[342,13773,1803],{"class":446},[342,13775,11703],{"class":496},[342,13777,1280],{"class":446},[342,13779,13780,13782,13784],{"class":344,"line":1601},[342,13781,1505],{"class":446},[342,13783,1522],{"class":496},[342,13785,1280],{"class":446},[26,13787,13789],{"id":13788},"step-9-use-slots-in-the-schema","Step 9: Use slots in the schema",[333,13791,13793],{"className":1246,"code":13792,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CVueform>\n    \u003CVaInputElement name=\"MyCustomInput\" rules=\"required|email|min:5\" @update:modelValue=\"console.log($event)\">\n      \u003Ctemplate #appendInner>\n        appendInner\n      \u003C\u002Ftemplate>\n    \u003C\u002FVaInputElement>\n  \u003C\u002FVueform>\n\u003C\u002Ftemplate>\n",[63,13794,13795,13803,13811,13851,13864,13869,13877,13885,13893],{"__ignoreMap":338},[342,13796,13797,13799,13801],{"class":344,"line":345},[342,13798,1023],{"class":446},[342,13800,1522],{"class":496},[342,13802,1280],{"class":446},[342,13804,13805,13807,13809],{"class":344,"line":359},[342,13806,1530],{"class":446},[342,13808,10398],{"class":496},[342,13810,1280],{"class":446},[342,13812,13813,13815,13817,13819,13821,13823,13825,13827,13829,13831,13833,13835,13837,13840,13842,13844,13847,13849],{"class":344,"line":469},[342,13814,1553],{"class":446},[342,13816,11944],{"class":496},[342,13818,4012],{"class":830},[342,13820,837],{"class":446},[342,13822,566],{"class":446},[342,13824,12257],{"class":352},[342,13826,566],{"class":446},[342,13828,13113],{"class":830},[342,13830,837],{"class":446},[342,13832,566],{"class":446},[342,13834,13120],{"class":352},[342,13836,566],{"class":446},[342,13838,13839],{"class":830}," @update:modelValue",[342,13841,837],{"class":446},[342,13843,566],{"class":446},[342,13845,13846],{"class":352},"console.log($event)",[342,13848,566],{"class":446},[342,13850,1280],{"class":446},[342,13852,13853,13855,13857,13859,13862],{"class":344,"line":476},[342,13854,1604],{"class":446},[342,13856,1522],{"class":496},[342,13858,11714],{"class":446},[342,13860,13861],{"class":830},"appendInner",[342,13863,1280],{"class":446},[342,13865,13866],{"class":344,"line":493},[342,13867,13868],{"class":450},"        appendInner\n",[342,13870,13871,13873,13875],{"class":344,"line":506},[342,13872,1782],{"class":446},[342,13874,1522],{"class":496},[342,13876,1280],{"class":446},[342,13878,13879,13881,13883],{"class":344,"line":530},[342,13880,1793],{"class":446},[342,13882,11944],{"class":496},[342,13884,1280],{"class":446},[342,13886,13887,13889,13891],{"class":344,"line":540},[342,13888,1803],{"class":446},[342,13890,10398],{"class":496},[342,13892,1280],{"class":446},[342,13894,13895,13897,13899],{"class":344,"line":571},[342,13896,1505],{"class":446},[342,13898,1522],{"class":496},[342,13900,1280],{"class":446},[26,13902,13904],{"id":13903},"step-10-extract-defineelement-logic-for-reusability","Step 10: Extract defineElement logic for reusability",[13,13906,13907,13908,13910],{},"If you're going to integrate a large number of components, it makes sense to put the ",[63,13909,12007],{}," logic in a separate function so you don't repeat yourself:",[333,13912,13914],{"className":4501,"code":13913,"language":4503,"meta":338,"style":338},"import { defineElement } from '@vueform\u002Fvueform'\nimport { computed } from 'vue'\nimport { omit } from '.\u002Fomit'\n\nexport function defineVuesticElement({ name, components, props, emits, propsToOmit }) {\n  return defineElement({\n    name,\n    components,\n    props,\n    emits,\n    setup(props, { element, slots }) {\n      const { value, update, fire, elementSlots } = element\n      const handleInput = (val) => update(val)\n      const omittedProps = computed(() => omit(props, propsToOmit))\n\n      const listeners = this.emits.reduce((acc, curr) => {\n        acc[curr] = (...args) => { fire(curr, ...args) }\n        return acc\n      }, {})\n\n      const vueFormSlotNames = Object.keys(elementSlots.value)\n      const allSlotNames = Object.keys(slots)\n      const vuesticSlotKeys = allSlotNames.filter(\n        name => !vueFormSlotNames.includes(name)\n      )\n\n      const schemaSlots = computed(() => {\n        const result = {}\n        for (const key in props.slots) {\n          if (!vueFormSlotNames.includes(key)) {\n            result[key] = props.slots[key]\n          }\n        }\n        return result\n      })\n\n      return {\n        value,\n        props: omittedProps,\n        listeners,\n        vuesticSlotKeys,\n        schemaSlots,\n        handleInput,\n      }\n    },\n  })\n}\n",[63,13915,13916,13934,13952,13970,13974,14009,14019,14026,14033,14040,14047,14071,14099,14123,14151,14155,14188,14227,14234,14243,14247,14275,14297,14315,14338,14343,14347,14365,14378,14403,14428,14453,14458,14463,14470,14476,14480,14487,14494,14505,14512,14519,14526,14533,14538,14542,14548],{"__ignoreMap":338},[342,13917,13918,13920,13922,13924,13926,13928,13930,13932],{"class":344,"line":345},[342,13919,443],{"class":442},[342,13921,447],{"class":446},[342,13923,11885],{"class":450},[342,13925,454],{"class":446},[342,13927,457],{"class":442},[342,13929,4017],{"class":446},[342,13931,11894],{"class":352},[342,13933,5273],{"class":446},[342,13935,13936,13938,13940,13942,13944,13946,13948,13950],{"class":344,"line":359},[342,13937,443],{"class":442},[342,13939,447],{"class":446},[342,13941,1423],{"class":450},[342,13943,454],{"class":446},[342,13945,457],{"class":442},[342,13947,4017],{"class":446},[342,13949,1248],{"class":352},[342,13951,5273],{"class":446},[342,13953,13954,13956,13958,13960,13962,13964,13966,13968],{"class":344,"line":469},[342,13955,443],{"class":442},[342,13957,447],{"class":446},[342,13959,12795],{"class":450},[342,13961,454],{"class":446},[342,13963,457],{"class":442},[342,13965,4017],{"class":446},[342,13967,12804],{"class":352},[342,13969,5273],{"class":446},[342,13971,13972],{"class":344,"line":476},[342,13973,473],{"emptyLinePlaceholder":472},[342,13975,13976,13978,13980,13983,13985,13987,13989,13992,13994,13996,13998,14001,14003,14005,14007],{"class":344,"line":493},[342,13977,479],{"class":442},[342,13979,935],{"class":830},[342,13981,13982],{"class":362}," defineVuesticElement",[342,13984,8861],{"class":446},[342,13986,4012],{"class":2819},[342,13988,1047],{"class":446},[342,13990,13991],{"class":2819}," components",[342,13993,1047],{"class":446},[342,13995,13025],{"class":2819},[342,13997,1047],{"class":446},[342,13999,14000],{"class":2819}," emits",[342,14002,1047],{"class":446},[342,14004,13010],{"class":2819},[342,14006,8867],{"class":446},[342,14008,503],{"class":446},[342,14010,14011,14013,14015,14017],{"class":344,"line":506},[342,14012,948],{"class":442},[342,14014,11885],{"class":362},[342,14016,487],{"class":496},[342,14018,490],{"class":446},[342,14020,14021,14024],{"class":344,"line":530},[342,14022,14023],{"class":450},"    name",[342,14025,527],{"class":446},[342,14027,14028,14031],{"class":344,"line":540},[342,14029,14030],{"class":450},"    components",[342,14032,527],{"class":446},[342,14034,14035,14038],{"class":344,"line":571},[342,14036,14037],{"class":450},"    props",[342,14039,527],{"class":446},[342,14041,14042,14045],{"class":344,"line":600},[342,14043,14044],{"class":450},"    emits",[342,14046,527],{"class":446},[342,14048,14049,14052,14054,14056,14058,14060,14062,14064,14067,14069],{"class":344,"line":629},[342,14050,14051],{"class":496},"    setup",[342,14053,487],{"class":446},[342,14055,10196],{"class":2819},[342,14057,1047],{"class":446},[342,14059,447],{"class":446},[342,14061,11975],{"class":2819},[342,14063,1047],{"class":446},[342,14065,14066],{"class":2819}," slots",[342,14068,8867],{"class":446},[342,14070,503],{"class":446},[342,14072,14073,14076,14078,14080,14082,14084,14086,14089,14091,14093,14095,14097],{"class":344,"line":659},[342,14074,14075],{"class":830},"      const",[342,14077,447],{"class":446},[342,14079,10000],{"class":450},[342,14081,1047],{"class":446},[342,14083,12650],{"class":450},[342,14085,1047],{"class":446},[342,14087,14088],{"class":450}," fire",[342,14090,1047],{"class":446},[342,14092,11762],{"class":450},[342,14094,454],{"class":446},[342,14096,1107],{"class":446},[342,14098,12657],{"class":450},[342,14100,14101,14103,14105,14107,14109,14111,14113,14115,14117,14119,14121],{"class":344,"line":688},[342,14102,14075],{"class":830},[342,14104,12664],{"class":450},[342,14106,1107],{"class":446},[342,14108,1138],{"class":446},[342,14110,12671],{"class":2819},[342,14112,294],{"class":446},[342,14114,1011],{"class":830},[342,14116,12650],{"class":362},[342,14118,487],{"class":496},[342,14120,12671],{"class":450},[342,14122,706],{"class":496},[342,14124,14125,14127,14129,14131,14133,14135,14137,14139,14141,14143,14145,14147,14149],{"class":344,"line":694},[342,14126,14075],{"class":830},[342,14128,12989],{"class":450},[342,14130,1107],{"class":446},[342,14132,1423],{"class":362},[342,14134,487],{"class":496},[342,14136,941],{"class":446},[342,14138,1011],{"class":830},[342,14140,12795],{"class":362},[342,14142,487],{"class":496},[342,14144,10196],{"class":450},[342,14146,1047],{"class":446},[342,14148,13010],{"class":450},[342,14150,4778],{"class":496},[342,14152,14153],{"class":344,"line":700},[342,14154,473],{"emptyLinePlaceholder":472},[342,14156,14157,14159,14162,14164,14166,14168,14170,14172,14174,14176,14178,14180,14182,14184,14186],{"class":344,"line":1461},[342,14158,14075],{"class":830},[342,14160,14161],{"class":450}," listeners",[342,14163,1107],{"class":446},[342,14165,4579],{"class":446},[342,14167,13191],{"class":450},[342,14169,158],{"class":446},[342,14171,13196],{"class":362},[342,14173,487],{"class":496},[342,14175,487],{"class":446},[342,14177,13203],{"class":2819},[342,14179,1047],{"class":446},[342,14181,13208],{"class":2819},[342,14183,294],{"class":446},[342,14185,1011],{"class":830},[342,14187,503],{"class":446},[342,14189,14190,14193,14195,14197,14199,14201,14203,14205,14207,14209,14211,14213,14215,14217,14219,14221,14223,14225],{"class":344,"line":1478},[342,14191,14192],{"class":450},"        acc",[342,14194,1205],{"class":496},[342,14196,13224],{"class":450},[342,14198,6682],{"class":496},[342,14200,837],{"class":446},[342,14202,13231],{"class":446},[342,14204,13234],{"class":2819},[342,14206,294],{"class":446},[342,14208,1011],{"class":830},[342,14210,447],{"class":446},[342,14212,14088],{"class":362},[342,14214,487],{"class":496},[342,14216,13224],{"class":450},[342,14218,1047],{"class":446},[342,14220,10193],{"class":446},[342,14222,13234],{"class":450},[342,14224,1151],{"class":496},[342,14226,956],{"class":446},[342,14228,14229,14232],{"class":344,"line":1495},[342,14230,14231],{"class":442},"        return",[342,14233,13268],{"class":450},[342,14235,14236,14239,14241],{"class":344,"line":1502},[342,14237,14238],{"class":446},"      },",[342,14240,13276],{"class":446},[342,14242,706],{"class":496},[342,14244,14245],{"class":344,"line":1512},[342,14246,473],{"emptyLinePlaceholder":472},[342,14248,14249,14251,14254,14256,14259,14261,14264,14266,14269,14271,14273],{"class":344,"line":1517},[342,14250,14075],{"class":830},[342,14252,14253],{"class":450}," vueFormSlotNames",[342,14255,1107],{"class":446},[342,14257,14258],{"class":450}," Object",[342,14260,158],{"class":446},[342,14262,14263],{"class":362},"keys",[342,14265,487],{"class":496},[342,14267,14268],{"class":450},"elementSlots",[342,14270,158],{"class":446},[342,14272,1148],{"class":450},[342,14274,706],{"class":496},[342,14276,14277,14279,14282,14284,14286,14288,14290,14292,14295],{"class":344,"line":1527},[342,14278,14075],{"class":830},[342,14280,14281],{"class":450}," allSlotNames",[342,14283,1107],{"class":446},[342,14285,14258],{"class":450},[342,14287,158],{"class":446},[342,14289,14263],{"class":362},[342,14291,487],{"class":496},[342,14293,14294],{"class":450},"slots",[342,14296,706],{"class":496},[342,14298,14299,14301,14303,14305,14307,14309,14312],{"class":344,"line":1550},[342,14300,14075],{"class":830},[342,14302,13460],{"class":450},[342,14304,1107],{"class":446},[342,14306,14281],{"class":450},[342,14308,158],{"class":446},[342,14310,14311],{"class":362},"filter",[342,14313,14314],{"class":496},"(\n",[342,14316,14317,14320,14322,14325,14328,14330,14332,14334,14336],{"class":344,"line":1581},[342,14318,14319],{"class":2819},"        name",[342,14321,1011],{"class":830},[342,14323,14324],{"class":446}," !",[342,14326,14327],{"class":450},"vueFormSlotNames",[342,14329,158],{"class":446},[342,14331,4595],{"class":362},[342,14333,487],{"class":496},[342,14335,4582],{"class":450},[342,14337,706],{"class":496},[342,14339,14340],{"class":344,"line":1601},[342,14341,14342],{"class":496},"      )\n",[342,14344,14345],{"class":344,"line":1610},[342,14346,473],{"emptyLinePlaceholder":472},[342,14348,14349,14351,14353,14355,14357,14359,14361,14363],{"class":344,"line":1625},[342,14350,14075],{"class":830},[342,14352,13543],{"class":450},[342,14354,1107],{"class":446},[342,14356,1423],{"class":362},[342,14358,487],{"class":496},[342,14360,941],{"class":446},[342,14362,1011],{"class":830},[342,14364,503],{"class":446},[342,14366,14367,14370,14373,14375],{"class":344,"line":1640},[342,14368,14369],{"class":830},"        const",[342,14371,14372],{"class":450}," result",[342,14374,1107],{"class":446},[342,14376,14377],{"class":446}," {}\n",[342,14379,14380,14383,14385,14387,14390,14393,14395,14397,14399,14401],{"class":344,"line":1655},[342,14381,14382],{"class":442},"        for",[342,14384,1138],{"class":496},[342,14386,831],{"class":830},[342,14388,14389],{"class":450}," key",[342,14391,14392],{"class":446}," in",[342,14394,13025],{"class":450},[342,14396,158],{"class":446},[342,14398,14294],{"class":450},[342,14400,1151],{"class":496},[342,14402,490],{"class":446},[342,14404,14405,14408,14410,14412,14414,14416,14418,14420,14423,14426],{"class":344,"line":1670},[342,14406,14407],{"class":442},"          if",[342,14409,1138],{"class":496},[342,14411,1141],{"class":446},[342,14413,14327],{"class":450},[342,14415,158],{"class":446},[342,14417,4595],{"class":362},[342,14419,487],{"class":496},[342,14421,14422],{"class":450},"key",[342,14424,14425],{"class":496},")) ",[342,14427,490],{"class":446},[342,14429,14430,14433,14435,14437,14439,14441,14443,14445,14447,14449,14451],{"class":344,"line":1676},[342,14431,14432],{"class":450},"            result",[342,14434,1205],{"class":496},[342,14436,14422],{"class":450},[342,14438,6682],{"class":496},[342,14440,837],{"class":446},[342,14442,13025],{"class":450},[342,14444,158],{"class":446},[342,14446,14294],{"class":450},[342,14448,1205],{"class":496},[342,14450,14422],{"class":450},[342,14452,3699],{"class":496},[342,14454,14455],{"class":344,"line":1685},[342,14456,14457],{"class":446},"          }\n",[342,14459,14460],{"class":344,"line":1700},[342,14461,14462],{"class":446},"        }\n",[342,14464,14465,14467],{"class":344,"line":1714},[342,14466,14231],{"class":442},[342,14468,14469],{"class":450}," result\n",[342,14471,14472,14474],{"class":344,"line":1729},[342,14473,9177],{"class":446},[342,14475,706],{"class":496},[342,14477,14478],{"class":344,"line":1744},[342,14479,473],{"emptyLinePlaceholder":472},[342,14481,14482,14485],{"class":344,"line":1750},[342,14483,14484],{"class":442},"      return",[342,14486,503],{"class":446},[342,14488,14489,14492],{"class":344,"line":1779},[342,14490,14491],{"class":450},"        value",[342,14493,527],{"class":446},[342,14495,14496,14499,14501,14503],{"class":344,"line":1790},[342,14497,14498],{"class":496},"        props",[342,14500,500],{"class":446},[342,14502,12989],{"class":450},[342,14504,527],{"class":446},[342,14506,14507,14510],{"class":344,"line":1800},[342,14508,14509],{"class":450},"        listeners",[342,14511,527],{"class":446},[342,14513,14514,14517],{"class":344,"line":1810},[342,14515,14516],{"class":450},"        vuesticSlotKeys",[342,14518,527],{"class":446},[342,14520,14521,14524],{"class":344,"line":2451},[342,14522,14523],{"class":450},"        schemaSlots",[342,14525,527],{"class":446},[342,14527,14528,14531],{"class":344,"line":2465},[342,14529,14530],{"class":450},"        handleInput",[342,14532,527],{"class":446},[342,14534,14535],{"class":344,"line":2480},[342,14536,14537],{"class":446},"      }\n",[342,14539,14540],{"class":344,"line":2486},[342,14541,691],{"class":446},[342,14543,14544,14546],{"class":344,"line":2492},[342,14545,1401],{"class":446},[342,14547,706],{"class":496},[342,14549,14550],{"class":344,"line":2503},[342,14551,956],{"class":446},[26,14553,14555],{"id":14554},"step-11-use-the-extracted-wrapper-function","Step 11: Use the extracted wrapper function",[333,14557,14559],{"className":1246,"code":14558,"language":1248,"meta":338,"style":338},"\u003Cscript>\nimport { defineVuesticElement } from '.\u002FdefineVuesticElement';\nimport { VaInput } from 'vuestic-ui';\nimport { omit } from '.\u002Fomit';\n\nconst propsToOmit = ['rules', 'label']\nconst props = {\n  ...omit(VaInput.props, propsToOmit)\n}\n\nexport default defineVuesticElement({\n  name: 'VaInputElement',\n  components: [VaInput],\n  props,\n  emits: VaInput.emits,\n  propsToOmit\n})\n\u003C\u002Fscript>\n",[63,14560,14561,14569,14590,14610,14630,14634,14660,14670,14688,14692,14696,14708,14722,14732,14738,14753,14758,14764],{"__ignoreMap":338},[342,14562,14563,14565,14567],{"class":344,"line":345},[342,14564,1023],{"class":446},[342,14566,1262],{"class":496},[342,14568,1280],{"class":446},[342,14570,14571,14573,14575,14577,14579,14581,14583,14586,14588],{"class":344,"line":359},[342,14572,443],{"class":442},[342,14574,447],{"class":446},[342,14576,13982],{"class":450},[342,14578,454],{"class":446},[342,14580,457],{"class":442},[342,14582,4017],{"class":446},[342,14584,14585],{"class":352},".\u002FdefineVuesticElement",[342,14587,3229],{"class":446},[342,14589,7819],{"class":446},[342,14591,14592,14594,14596,14598,14600,14602,14604,14606,14608],{"class":344,"line":469},[342,14593,443],{"class":442},[342,14595,447],{"class":446},[342,14597,11905],{"class":450},[342,14599,454],{"class":446},[342,14601,457],{"class":442},[342,14603,4017],{"class":446},[342,14605,11914],{"class":352},[342,14607,3229],{"class":446},[342,14609,7819],{"class":446},[342,14611,14612,14614,14616,14618,14620,14622,14624,14626,14628],{"class":344,"line":476},[342,14613,443],{"class":442},[342,14615,447],{"class":446},[342,14617,12795],{"class":450},[342,14619,454],{"class":446},[342,14621,457],{"class":442},[342,14623,4017],{"class":446},[342,14625,12804],{"class":352},[342,14627,3229],{"class":446},[342,14629,7819],{"class":446},[342,14631,14632],{"class":344,"line":493},[342,14633,473],{"emptyLinePlaceholder":472},[342,14635,14636,14638,14640,14642,14644,14646,14648,14650,14652,14654,14656,14658],{"class":344,"line":506},[342,14637,831],{"class":830},[342,14639,12835],{"class":450},[342,14641,837],{"class":446},[342,14643,3226],{"class":450},[342,14645,3229],{"class":446},[342,14647,12844],{"class":352},[342,14649,3229],{"class":446},[342,14651,1047],{"class":446},[342,14653,4017],{"class":446},[342,14655,12020],{"class":352},[342,14657,3229],{"class":446},[342,14659,3699],{"class":450},[342,14661,14662,14664,14666,14668],{"class":344,"line":530},[342,14663,831],{"class":830},[342,14665,8926],{"class":450},[342,14667,837],{"class":446},[342,14669,503],{"class":446},[342,14671,14672,14675,14677,14679,14681,14683,14685],{"class":344,"line":540},[342,14673,14674],{"class":446},"  ...",[342,14676,12904],{"class":362},[342,14678,12907],{"class":450},[342,14680,158],{"class":446},[342,14682,10196],{"class":450},[342,14684,1047],{"class":446},[342,14686,14687],{"class":450}," propsToOmit)\n",[342,14689,14690],{"class":344,"line":571},[342,14691,956],{"class":446},[342,14693,14694],{"class":344,"line":600},[342,14695,473],{"emptyLinePlaceholder":472},[342,14697,14698,14700,14702,14704,14706],{"class":344,"line":629},[342,14699,479],{"class":442},[342,14701,482],{"class":442},[342,14703,13982],{"class":362},[342,14705,487],{"class":450},[342,14707,490],{"class":446},[342,14709,14710,14712,14714,14716,14718,14720],{"class":344,"line":659},[342,14711,11937],{"class":496},[342,14713,500],{"class":446},[342,14715,4017],{"class":446},[342,14717,11944],{"class":352},[342,14719,3229],{"class":446},[342,14721,527],{"class":446},[342,14723,14724,14726,14728,14730],{"class":344,"line":688},[342,14725,11953],{"class":496},[342,14727,500],{"class":446},[342,14729,11958],{"class":450},[342,14731,527],{"class":446},[342,14733,14734,14736],{"class":344,"line":694},[342,14735,5291],{"class":450},[342,14737,527],{"class":446},[342,14739,14740,14743,14745,14747,14749,14751],{"class":344,"line":700},[342,14741,14742],{"class":496},"  emits",[342,14744,500],{"class":446},[342,14746,11905],{"class":450},[342,14748,158],{"class":446},[342,14750,13191],{"class":450},[342,14752,527],{"class":446},[342,14754,14755],{"class":344,"line":1461},[342,14756,14757],{"class":450},"  propsToOmit\n",[342,14759,14760,14762],{"class":344,"line":1478},[342,14761,703],{"class":446},[342,14763,706],{"class":450},[342,14765,14766,14768,14770],{"class":344,"line":1495},[342,14767,1505],{"class":446},[342,14769,1262],{"class":496},[342,14771,1280],{"class":446},[13,14773,14774,14775,14778,14779,14781],{},"Note the ",[63,14776,14777],{},"ref=\"input\""," template ref you can add to ",[63,14780,11682],{}," — you can reference it like this:",[333,14783,14785],{"className":1246,"code":14784,"language":1248,"meta":338,"style":338},"\u003Cscript setup>\nimport { onMounted, ref } from 'vue'\nimport VaInputElement from '.\u002FVaInputElement.vue'\n\nconst form$ = ref(null)\n\nonMounted(() => {\n  console.log(form$.value.el$('custom').input)\n})\n\u003C\u002Fscript>\n",[63,14786,14787,14797,14819,14833,14837,14854,14858,14871,14911,14917],{"__ignoreMap":338},[342,14788,14789,14791,14793,14795],{"class":344,"line":345},[342,14790,1023],{"class":446},[342,14792,1262],{"class":496},[342,14794,1265],{"class":830},[342,14796,1280],{"class":446},[342,14798,14799,14801,14803,14805,14807,14809,14811,14813,14815,14817],{"class":344,"line":359},[342,14800,443],{"class":442},[342,14802,447],{"class":446},[342,14804,9238],{"class":450},[342,14806,1047],{"class":446},[342,14808,2132],{"class":450},[342,14810,454],{"class":446},[342,14812,457],{"class":442},[342,14814,4017],{"class":446},[342,14816,1248],{"class":352},[342,14818,5273],{"class":446},[342,14820,14821,14823,14825,14827,14829,14831],{"class":344,"line":469},[342,14822,443],{"class":442},[342,14824,12133],{"class":450},[342,14826,815],{"class":442},[342,14828,4017],{"class":446},[342,14830,12315],{"class":352},[342,14832,5273],{"class":446},[342,14834,14835],{"class":344,"line":476},[342,14836,473],{"emptyLinePlaceholder":472},[342,14838,14839,14841,14844,14846,14848,14850,14852],{"class":344,"line":493},[342,14840,831],{"class":830},[342,14842,14843],{"class":450}," form$ ",[342,14845,837],{"class":446},[342,14847,2132],{"class":362},[342,14849,487],{"class":450},[342,14851,9044],{"class":446},[342,14853,706],{"class":450},[342,14855,14856],{"class":344,"line":506},[342,14857,473],{"emptyLinePlaceholder":472},[342,14859,14860,14863,14865,14867,14869],{"class":344,"line":530},[342,14861,14862],{"class":362},"onMounted",[342,14864,487],{"class":450},[342,14866,941],{"class":446},[342,14868,1011],{"class":830},[342,14870,503],{"class":446},[342,14872,14873,14876,14878,14880,14882,14885,14887,14889,14891,14893,14895,14897,14900,14902,14904,14906,14909],{"class":344,"line":540},[342,14874,14875],{"class":450},"  console",[342,14877,158],{"class":446},[342,14879,5783],{"class":362},[342,14881,487],{"class":496},[342,14883,14884],{"class":450},"form$",[342,14886,158],{"class":446},[342,14888,1148],{"class":450},[342,14890,158],{"class":446},[342,14892,11799],{"class":362},[342,14894,487],{"class":496},[342,14896,3229],{"class":446},[342,14898,14899],{"class":352},"custom",[342,14901,3229],{"class":446},[342,14903,294],{"class":496},[342,14905,158],{"class":446},[342,14907,14908],{"class":450},"input",[342,14910,706],{"class":496},[342,14912,14913,14915],{"class":344,"line":571},[342,14914,703],{"class":446},[342,14916,706],{"class":450},[342,14918,14919,14921,14923],{"class":344,"line":600},[342,14920,1505],{"class":446},[342,14922,1262],{"class":496},[342,14924,1280],{"class":446},[26,14926,14928],{"id":14927},"step-12-render-a-form-through-the-schema","Step 12: Render a form through the schema",[333,14930,14932],{"className":1246,"code":14931,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CVueform :schema=\"schema\" \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nconst schema = {\n  MyCustomInput: {\n    type: 'VaInput',\n    rules: \"required|email|min:5\",\n    ['onUpdate:modelValue']: (v) => console.log(v),\n    slots: {\n      appendInner: () => 'appendInner',\n    }\n  }\n}\n\u003C\u002Fscript>\n",[63,14933,14934,14942,14962,14970,14974,14984,14995,15004,15019,15033,15070,15079,15098,15102,15106,15110],{"__ignoreMap":338},[342,14935,14936,14938,14940],{"class":344,"line":345},[342,14937,1023],{"class":446},[342,14939,1522],{"class":496},[342,14941,1280],{"class":446},[342,14943,14944,14946,14948,14951,14953,14955,14958,14960],{"class":344,"line":359},[342,14945,1530],{"class":446},[342,14947,10398],{"class":496},[342,14949,14950],{"class":830}," :schema",[342,14952,837],{"class":446},[342,14954,566],{"class":446},[342,14956,14957],{"class":352},"schema",[342,14959,566],{"class":446},[342,14961,7779],{"class":446},[342,14963,14964,14966,14968],{"class":344,"line":469},[342,14965,1505],{"class":446},[342,14967,1522],{"class":496},[342,14969,1280],{"class":446},[342,14971,14972],{"class":344,"line":476},[342,14973,473],{"emptyLinePlaceholder":472},[342,14975,14976,14978,14980,14982],{"class":344,"line":493},[342,14977,1023],{"class":446},[342,14979,1262],{"class":496},[342,14981,1265],{"class":830},[342,14983,1280],{"class":446},[342,14985,14986,14988,14991,14993],{"class":344,"line":506},[342,14987,831],{"class":830},[342,14989,14990],{"class":450}," schema ",[342,14992,837],{"class":446},[342,14994,503],{"class":446},[342,14996,14997,15000,15002],{"class":344,"line":530},[342,14998,14999],{"class":496},"  MyCustomInput",[342,15001,500],{"class":446},[342,15003,503],{"class":446},[342,15005,15006,15009,15011,15013,15015,15017],{"class":344,"line":540},[342,15007,15008],{"class":496},"    type",[342,15010,500],{"class":446},[342,15012,4017],{"class":446},[342,15014,11682],{"class":352},[342,15016,3229],{"class":446},[342,15018,527],{"class":446},[342,15020,15021,15023,15025,15027,15029,15031],{"class":344,"line":571},[342,15022,6549],{"class":496},[342,15024,500],{"class":446},[342,15026,460],{"class":446},[342,15028,13120],{"class":352},[342,15030,566],{"class":446},[342,15032,527],{"class":446},[342,15034,15035,15038,15040,15043,15045,15047,15049,15051,15054,15056,15058,15061,15063,15065,15068],{"class":344,"line":600},[342,15036,15037],{"class":496},"    [",[342,15039,3229],{"class":446},[342,15041,15042],{"class":496},"onUpdate:modelValue",[342,15044,3229],{"class":446},[342,15046,1212],{"class":496},[342,15048,500],{"class":446},[342,15050,1138],{"class":446},[342,15052,15053],{"class":2819},"v",[342,15055,294],{"class":446},[342,15057,1011],{"class":830},[342,15059,15060],{"class":450}," console",[342,15062,158],{"class":446},[342,15064,5783],{"class":362},[342,15066,15067],{"class":450},"(v)",[342,15069,527],{"class":446},[342,15071,15072,15075,15077],{"class":344,"line":629},[342,15073,15074],{"class":496},"    slots",[342,15076,500],{"class":446},[342,15078,503],{"class":446},[342,15080,15081,15084,15086,15088,15090,15092,15094,15096],{"class":344,"line":659},[342,15082,15083],{"class":362},"      appendInner",[342,15085,500],{"class":446},[342,15087,1008],{"class":446},[342,15089,1011],{"class":830},[342,15091,4017],{"class":446},[342,15093,13861],{"class":352},[342,15095,3229],{"class":446},[342,15097,527],{"class":446},[342,15099,15100],{"class":344,"line":688},[342,15101,4670],{"class":446},[342,15103,15104],{"class":344,"line":694},[342,15105,1229],{"class":446},[342,15107,15108],{"class":344,"line":700},[342,15109,956],{"class":446},[342,15111,15112,15114,15116],{"class":344,"line":1461},[342,15113,1505],{"class":446},[342,15115,1262],{"class":496},[342,15117,1280],{"class":446},[13,15119,15120,15121,15123,15124,15126,15127,15129,15130,15132,15133,15136,15137,15139,15140,15143,15144,15147,15148,15150,15151,15154,15155,15157,15158,158],{},"Bind the ",[63,15122,14957],{}," object via props to ",[63,15125,10398],{}," to get the result. The key (",[63,15128,12257],{},") matches the mandatory ",[63,15131,4582],{}," prop. ",[63,15134,15135],{},"type: 'VaInput'"," is the argument from ",[63,15138,12007],{}," (it must always end with ",[63,15141,15142],{},"Element",", which we skip here). ",[63,15145,15146],{},"type"," can be written as PascalCase (",[63,15149,11682],{},") or kebab-case (",[63,15152,15153],{},"va-input","). For events, replace the ",[63,15156,4602],{}," character with ",[63,15159,15160],{},"on",[26,15162,15164],{"id":15163},"step-13-define-slots-with-different-methods","Step 13: Define slots with different methods",[13,15166,15167],{},[17,15168,15169],{},"Method 1 — arrow function:",[333,15171,15173],{"className":4501,"code":15172,"language":4503,"meta":338,"style":338},"slots: {\n  appendInner: () => 'appendInner',\n}\n",[63,15174,15175,15183,15202],{"__ignoreMap":338},[342,15176,15177,15179,15181],{"class":344,"line":345},[342,15178,14294],{"class":348},[342,15180,500],{"class":446},[342,15182,503],{"class":446},[342,15184,15185,15188,15190,15192,15194,15196,15198,15200],{"class":344,"line":359},[342,15186,15187],{"class":348},"  appendInner",[342,15189,500],{"class":446},[342,15191,1008],{"class":446},[342,15193,1011],{"class":830},[342,15195,4017],{"class":446},[342,15197,13861],{"class":352},[342,15199,3229],{"class":446},[342,15201,527],{"class":446},[342,15203,15204],{"class":344,"line":469},[342,15205,956],{"class":446},[13,15207,15208],{},[17,15209,15210],{},"Method 2 — render() function:",[333,15212,15214],{"className":4501,"code":15213,"language":4503,"meta":338,"style":338},"import { h } from 'vue';\n\nconst schema = {\n  MyCustomInput: {\n    type: 'va-input',\n    slots: {\n      appendInner: {\n        props: ['el$'],\n        render() {\n          console.log(this.el$)\n          return h('div', 'Schema Slot')\n        }\n      }\n    }\n  }\n}\n",[63,15215,15216,15237,15241,15251,15259,15273,15281,15289,15307,15316,15333,15359,15363,15367,15371,15375],{"__ignoreMap":338},[342,15217,15218,15220,15222,15225,15227,15229,15231,15233,15235],{"class":344,"line":345},[342,15219,443],{"class":442},[342,15221,447],{"class":446},[342,15223,15224],{"class":450}," h",[342,15226,454],{"class":446},[342,15228,457],{"class":442},[342,15230,4017],{"class":446},[342,15232,1248],{"class":352},[342,15234,3229],{"class":446},[342,15236,7819],{"class":446},[342,15238,15239],{"class":344,"line":359},[342,15240,473],{"emptyLinePlaceholder":472},[342,15242,15243,15245,15247,15249],{"class":344,"line":469},[342,15244,831],{"class":830},[342,15246,14990],{"class":450},[342,15248,837],{"class":446},[342,15250,503],{"class":446},[342,15252,15253,15255,15257],{"class":344,"line":476},[342,15254,14999],{"class":496},[342,15256,500],{"class":446},[342,15258,503],{"class":446},[342,15260,15261,15263,15265,15267,15269,15271],{"class":344,"line":493},[342,15262,15008],{"class":496},[342,15264,500],{"class":446},[342,15266,4017],{"class":446},[342,15268,15153],{"class":352},[342,15270,3229],{"class":446},[342,15272,527],{"class":446},[342,15274,15275,15277,15279],{"class":344,"line":506},[342,15276,15074],{"class":496},[342,15278,500],{"class":446},[342,15280,503],{"class":446},[342,15282,15283,15285,15287],{"class":344,"line":530},[342,15284,15083],{"class":496},[342,15286,500],{"class":446},[342,15288,503],{"class":446},[342,15290,15291,15293,15295,15297,15299,15301,15303,15305],{"class":344,"line":540},[342,15292,14498],{"class":496},[342,15294,500],{"class":446},[342,15296,3226],{"class":450},[342,15298,3229],{"class":446},[342,15300,11799],{"class":352},[342,15302,3229],{"class":446},[342,15304,1212],{"class":450},[342,15306,527],{"class":446},[342,15308,15309,15312,15314],{"class":344,"line":571},[342,15310,15311],{"class":496},"        render",[342,15313,941],{"class":446},[342,15315,503],{"class":446},[342,15317,15318,15321,15323,15325,15327,15329,15331],{"class":344,"line":600},[342,15319,15320],{"class":450},"          console",[342,15322,158],{"class":446},[342,15324,5783],{"class":362},[342,15326,487],{"class":496},[342,15328,4640],{"class":446},[342,15330,11799],{"class":450},[342,15332,706],{"class":496},[342,15334,15335,15338,15340,15342,15344,15346,15348,15350,15352,15355,15357],{"class":344,"line":629},[342,15336,15337],{"class":442},"          return",[342,15339,15224],{"class":362},[342,15341,487],{"class":496},[342,15343,3229],{"class":446},[342,15345,1533],{"class":352},[342,15347,3229],{"class":446},[342,15349,1047],{"class":446},[342,15351,4017],{"class":446},[342,15353,15354],{"class":352},"Schema Slot",[342,15356,3229],{"class":446},[342,15358,706],{"class":496},[342,15360,15361],{"class":344,"line":659},[342,15362,14462],{"class":446},[342,15364,15365],{"class":344,"line":688},[342,15366,14537],{"class":446},[342,15368,15369],{"class":344,"line":694},[342,15370,4670],{"class":446},[342,15372,15373],{"class":344,"line":700},[342,15374,1229],{"class":446},[342,15376,15377],{"class":344,"line":1461},[342,15378,956],{"class":446},[13,15380,15381],{},[17,15382,15383],{},"Method 3 — return h():",[333,15385,15387],{"className":4501,"code":15386,"language":4503,"meta":338,"style":338},"import { h } from 'vue';\nimport TestComponent from '.\u002FTestComponent.vue';\n\nconst schema = {\n  MyCustomInput: {\n    type: 'va-input',\n    slots: {\n      appendInner: () => h(TestComponent, {\n        label: 'hello',\n        onSomeEvent: () => console.log('onSomeEvent')\n      }, {\n        icon: () => '👋'\n      })\n    }\n  }\n}\n",[63,15388,15389,15409,15427,15431,15441,15449,15463,15471,15490,15506,15534,15540,15558,15564,15568,15572],{"__ignoreMap":338},[342,15390,15391,15393,15395,15397,15399,15401,15403,15405,15407],{"class":344,"line":345},[342,15392,443],{"class":442},[342,15394,447],{"class":446},[342,15396,15224],{"class":450},[342,15398,454],{"class":446},[342,15400,457],{"class":442},[342,15402,4017],{"class":446},[342,15404,1248],{"class":352},[342,15406,3229],{"class":446},[342,15408,7819],{"class":446},[342,15410,15411,15413,15416,15418,15420,15423,15425],{"class":344,"line":359},[342,15412,443],{"class":442},[342,15414,15415],{"class":450}," TestComponent ",[342,15417,815],{"class":442},[342,15419,4017],{"class":446},[342,15421,15422],{"class":352},".\u002FTestComponent.vue",[342,15424,3229],{"class":446},[342,15426,7819],{"class":446},[342,15428,15429],{"class":344,"line":469},[342,15430,473],{"emptyLinePlaceholder":472},[342,15432,15433,15435,15437,15439],{"class":344,"line":476},[342,15434,831],{"class":830},[342,15436,14990],{"class":450},[342,15438,837],{"class":446},[342,15440,503],{"class":446},[342,15442,15443,15445,15447],{"class":344,"line":493},[342,15444,14999],{"class":496},[342,15446,500],{"class":446},[342,15448,503],{"class":446},[342,15450,15451,15453,15455,15457,15459,15461],{"class":344,"line":506},[342,15452,15008],{"class":496},[342,15454,500],{"class":446},[342,15456,4017],{"class":446},[342,15458,15153],{"class":352},[342,15460,3229],{"class":446},[342,15462,527],{"class":446},[342,15464,15465,15467,15469],{"class":344,"line":530},[342,15466,15074],{"class":496},[342,15468,500],{"class":446},[342,15470,503],{"class":446},[342,15472,15473,15475,15477,15479,15481,15483,15486,15488],{"class":344,"line":540},[342,15474,15083],{"class":362},[342,15476,500],{"class":446},[342,15478,1008],{"class":446},[342,15480,1011],{"class":830},[342,15482,15224],{"class":362},[342,15484,15485],{"class":450},"(TestComponent",[342,15487,1047],{"class":446},[342,15489,503],{"class":446},[342,15491,15492,15495,15497,15499,15502,15504],{"class":344,"line":571},[342,15493,15494],{"class":496},"        label",[342,15496,500],{"class":446},[342,15498,4017],{"class":446},[342,15500,15501],{"class":352},"hello",[342,15503,3229],{"class":446},[342,15505,527],{"class":446},[342,15507,15508,15511,15513,15515,15517,15519,15521,15523,15525,15527,15530,15532],{"class":344,"line":600},[342,15509,15510],{"class":362},"        onSomeEvent",[342,15512,500],{"class":446},[342,15514,1008],{"class":446},[342,15516,1011],{"class":830},[342,15518,15060],{"class":450},[342,15520,158],{"class":446},[342,15522,5783],{"class":362},[342,15524,487],{"class":450},[342,15526,3229],{"class":446},[342,15528,15529],{"class":352},"onSomeEvent",[342,15531,3229],{"class":446},[342,15533,706],{"class":450},[342,15535,15536,15538],{"class":344,"line":629},[342,15537,14238],{"class":446},[342,15539,503],{"class":446},[342,15541,15542,15545,15547,15549,15551,15553,15556],{"class":344,"line":659},[342,15543,15544],{"class":362},"        icon",[342,15546,500],{"class":446},[342,15548,1008],{"class":446},[342,15550,1011],{"class":830},[342,15552,4017],{"class":446},[342,15554,15555],{"class":352},"👋",[342,15557,5273],{"class":446},[342,15559,15560,15562],{"class":344,"line":688},[342,15561,9177],{"class":446},[342,15563,706],{"class":450},[342,15565,15566],{"class":344,"line":694},[342,15567,4670],{"class":446},[342,15569,15570],{"class":344,"line":700},[342,15571,1229],{"class":446},[342,15573,15574],{"class":344,"line":1461},[342,15575,956],{"class":446},[13,15577,15578,15581],{},[63,15579,15580],{},"h()"," takes a component as the first argument, props and events as the second, and slots as the third.",[26,15583,15584],{"id":5872},"Final thoughts",[13,15586,15587],{},"In this guide, we walked through building a custom wrapper around Vuestic UI components for use inside Vueform. We covered everything you need for full compatibility:",[41,15589,15590,15593,15596,15602,15605],{},[44,15591,15592],{},"✅ Handling v-model and validation",[44,15594,15595],{},"✅ Passing props with automatic omission",[44,15597,15598,15599,15601],{},"✅ Listening to events using ",[63,15600,13163],{}," for schema support",[44,15603,15604],{},"✅ Managing generic and custom slots without conflicts",[44,15606,15607],{},"✅ Using render functions in schema configuration",[13,15609,15610],{},"This approach gives you the flexibility of Vueform with the design power of Vuestic. If you're working with many components, abstract your wrapper logic for easier maintenance. Happy building! 🚀",[3114,15612],{"label":10273,"to":3117},[3119,15614,15615],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":338,"searchDepth":469,"depth":469,"links":15617},[15618,15619,15620,15621,15622,15623,15624,15625,15626,15627,15628,15629,15630,15631,15632],{"id":11648,"depth":359,"text":11649},{"id":11675,"depth":359,"text":11676},{"id":12049,"depth":359,"text":12050},{"id":12220,"depth":359,"text":12221},{"id":12333,"depth":359,"text":12334},{"id":12733,"depth":359,"text":12734},{"id":13066,"depth":359,"text":13067},{"id":13146,"depth":359,"text":13147},{"id":13351,"depth":359,"text":13352},{"id":13788,"depth":359,"text":13789},{"id":13903,"depth":359,"text":13904},{"id":14554,"depth":359,"text":14555},{"id":14927,"depth":359,"text":14928},{"id":15163,"depth":359,"text":15164},{"id":5872,"depth":359,"text":15584},"2025-03-20","A step-by-step guide to wrapping Vuestic UI components as custom Vueform elements — v-model, validation, props, events, and schema-driven slots.","\u002Fimages\u002Fblog\u002Fvueform\u002Fcard.png",{},"\u002Fblog\u002Fvueform",{"title":11640,"description":15634},"blog\u002Fvueform",[3164,10747,15641],"Forms","pZE1UijKk31USaSOy5qQ6TeKyNQqbsG26WNHJ15E7bs",{"id":15644,"title":15645,"author":7,"authorSlug":8,"body":15646,"date":16212,"description":16213,"extension":3154,"faq":8,"featured":3155,"image":16214,"meta":16215,"navigation":472,"path":16216,"readTime":8,"seo":16217,"stem":16218,"tags":16219,"__hash__":16221},"blog\u002Fblog\u002Fcyber-resilience-act.md","Vue.js and the Cyber Resilience Act: How Developers Can Avoid Mistakes",{"type":10,"value":15647,"toc":16198},[15648,15658,15662,15665,15680,15683,15687,15692,15718,15722,15726,15729,15792,15795,15826,15829,15833,15836,15868,15871,15909,15913,15916,16075,16083,16087,16101,16105,16112,16130,16134,16141,16145,16177,16181,16184,16192,16195],[13,15649,784,15650,15653,15654,15657],{},[17,15651,15652],{},"Cyber Resilience Act (CRA)"," is a new EU regulation that requires all digital products to be ",[5161,15655,15656],{},"secure by default"," and properly maintained throughout their entire lifecycle. The goal is simple: reduce security risks in software and ensure that companies ship products that meet modern cybersecurity standards.",[26,15659,15661],{"id":15660},"what-is-cra-and-why-does-it-matter-for-frontend-teams","What is CRA and why does it matter for frontend teams?",[13,15663,15664],{},"For Vue developers and frontend engineering teams, CRA creates concrete technical responsibilities that directly affect product compliance:",[41,15666,15667,15674,15677],{},[44,15668,15669,15670,15673],{},"you can't ignore ",[17,15671,15672],{},"vulnerabilities in npm packages"," or outdated dependencies",[44,15675,15676],{},"security by design becomes an expected requirement for EU client projects under CRA",[44,15678,15679],{},"the way we structure code, handle dependencies, and manage updates now directly affects whether clients stay compliant or face legal, financial, or reputational risks",[13,15681,15682],{},"If you build or maintain Vue applications for European businesses, understanding CRA obligations is essential to avoid security gaps and ensure your software meets EU-level compliance.",[26,15684,15686],{"id":15685},"why-this-matters-for-clients","Why this matters for clients",[13,15688,15689],{},[17,15690,15691],{},"Business-friendly arguments:",[2954,15693,15694,15700,15706,15712],{},[44,15695,15696,15699],{},[17,15697,15698],{},"Fines and reputation risk."," Under the EU Cyber Resilience Act, companies may face penalties if they ship digital products without proper security measures, maintenance, and vulnerability handling.",[44,15701,15702,15705],{},[17,15703,15704],{},"Transparency and trust through SBOMs."," Providing an SBOM (software bill of materials) and clear security changelogs demonstrates reliability, professionalism, and proactive risk management. Many enterprise clients now require this as part of vendor evaluation.",[44,15707,15708,15711],{},[17,15709,15710],{},"Lower long-term maintenance costs."," Continuous small updates, dependency patches, and vulnerability monitoring cost significantly less than urgent large-scale remediation when a critical issue appears.",[44,15713,15714,15717],{},[17,15715,15716],{},"Competitive advantage in the EU market."," Products that are aligned with CRA requirements, secure by design, or EU-ready gain trust faster.",[26,15719,15721],{"id":15720},"what-vue-developers-should-do-right-now","What Vue developers should do right now",[328,15723,15725],{"id":15724},"_1-keep-dependencies-healthy","1. Keep dependencies healthy",[13,15727,15728],{},"Bad dependencies → better alternatives:",[41,15730,15731,15745,15757,15763,15773,15783],{},[44,15732,15733,15734,15737,15738,3011,15741,15744],{},"❌ ",[17,15735,15736],{},"moment.js"," — deprecated, huge bundle size. ✅ Use ",[17,15739,15740],{},"date-fns",[17,15742,15743],{},"dayjs"," (lightweight, active support).",[44,15746,15733,15747,15750,15751,3011,15754,158],{},[17,15748,15749],{},"request"," — old HTTP client, unmaintained. ✅ Use ",[17,15752,15753],{},"axios",[17,15755,15756],{},"ky",[44,15758,15733,15759,15762],{},[17,15760,15761],{},"left-pad"," (and similar tiny packages). ✅ Inline small utilities yourself — less supply-chain risk.",[44,15764,15733,15765,15768,15769,15772],{},[17,15766,15767],{},"lodash (full package)"," — bloats the bundle. ✅ Use ",[17,15770,15771],{},"lodash-es"," with tree-shaking or minimal libs like just \u002F radash.",[44,15774,15733,15775,15778,15779,15782],{},[17,15776,15777],{},"crypto-js"," — outdated crypto, known issues. ✅ Use the ",[17,15780,15781],{},"Web Crypto API"," (built into modern browsers).",[44,15784,15733,15785,15788,15789,158],{},[17,15786,15787],{},"core-js@2"," — deprecated. ✅ Upgrade to ",[17,15790,15791],{},"core-js@3",[13,15793,15794],{},"Vulnerability checks:",[333,15796,15798],{"className":335,"code":15797,"language":337,"meta":338,"style":338},"npx osv-scanner --lockfile pnpm-lock.yaml\nnpx knip --include vue,nuxt,vitest\n",[63,15799,15800,15813],{"__ignoreMap":338},[342,15801,15802,15804,15807,15810],{"class":344,"line":345},[342,15803,349],{"class":348},[342,15805,15806],{"class":352}," osv-scanner",[342,15808,15809],{"class":352}," --lockfile",[342,15811,15812],{"class":352}," pnpm-lock.yaml\n",[342,15814,15815,15817,15820,15823],{"class":344,"line":359},[342,15816,349],{"class":348},[342,15818,15819],{"class":352}," knip",[342,15821,15822],{"class":352}," --include",[342,15824,15825],{"class":352}," vue,nuxt,vitest\n",[13,15827,15828],{},"Auto-updates: Dependabot or Renovate.",[328,15830,15832],{"id":15831},"_2-block-unsafe-html","2. Block unsafe HTML",[13,15834,15835],{},"ESLint rule:",[333,15837,15839],{"className":4501,"code":15838,"language":4503,"meta":338,"style":338},"rules: { 'vue\u002Fno-v-html': 'error' }\n",[63,15840,15841],{"__ignoreMap":338},[342,15842,15843,15845,15847,15849,15851,15854,15856,15859,15861,15864,15866],{"class":344,"line":345},[342,15844,12844],{"class":348},[342,15846,500],{"class":446},[342,15848,447],{"class":446},[342,15850,4017],{"class":446},[342,15852,15853],{"class":352},"vue\u002Fno-v-html",[342,15855,3229],{"class":446},[342,15857,15858],{"class":496},": ",[342,15860,3229],{"class":446},[342,15862,15863],{"class":352},"error",[342,15865,3229],{"class":446},[342,15867,2929],{"class":446},[13,15869,15870],{},"If CMS content requires HTML, wrap it with DOMPurify:",[333,15872,15874],{"className":1246,"code":15873,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\u003Cdiv v-html=\"$sanitize(html)\">\u003C\u002Fdiv>\u003C\u002Ftemplate>\n",[63,15875,15876],{"__ignoreMap":338},[342,15877,15878,15880,15882,15885,15887,15890,15892,15894,15897,15899,15901,15903,15905,15907],{"class":344,"line":345},[342,15879,1023],{"class":446},[342,15881,1522],{"class":496},[342,15883,15884],{"class":446},">\u003C",[342,15886,1533],{"class":496},[342,15888,15889],{"class":830}," v-html",[342,15891,837],{"class":446},[342,15893,566],{"class":446},[342,15895,15896],{"class":352},"$sanitize(html)",[342,15898,566],{"class":446},[342,15900,8243],{"class":446},[342,15902,1533],{"class":496},[342,15904,8243],{"class":446},[342,15906,1522],{"class":496},[342,15908,1280],{"class":446},[328,15910,15912],{"id":15911},"_3-add-basic-ssr-security-headers","3. Add basic SSR security headers",[13,15914,15915],{},"For Nuxt projects, you can add these in a few lines:",[333,15917,15919],{"className":12060,"code":15918,"language":1275,"meta":338,"style":338},"export default defineNuxtConfig({\n  nitro: {\n    routeRules: {\n      '\u002F**': {\n        headers: {\n          'x-content-type-options': 'nosniff',\n          'referrer-policy': 'strict-origin-when-cross-origin',\n          'strict-transport-security': 'max-age=31536000; includeSubDomains',\n          'content-security-policy': \"default-src 'self'\"\n        }\n      }\n    }\n  }\n})\n",[63,15920,15921,15933,15942,15951,15965,15974,15995,16015,16035,16053,16057,16061,16065,16069],{"__ignoreMap":338},[342,15922,15923,15925,15927,15929,15931],{"class":344,"line":345},[342,15924,479],{"class":442},[342,15926,482],{"class":442},[342,15928,3212],{"class":362},[342,15930,487],{"class":450},[342,15932,490],{"class":446},[342,15934,15935,15938,15940],{"class":344,"line":359},[342,15936,15937],{"class":496},"  nitro",[342,15939,500],{"class":446},[342,15941,503],{"class":446},[342,15943,15944,15947,15949],{"class":344,"line":469},[342,15945,15946],{"class":496},"    routeRules",[342,15948,500],{"class":446},[342,15950,503],{"class":446},[342,15952,15953,15956,15959,15961,15963],{"class":344,"line":476},[342,15954,15955],{"class":446},"      '",[342,15957,15958],{"class":496},"\u002F**",[342,15960,3229],{"class":446},[342,15962,500],{"class":446},[342,15964,503],{"class":446},[342,15966,15967,15970,15972],{"class":344,"line":493},[342,15968,15969],{"class":496},"        headers",[342,15971,500],{"class":446},[342,15973,503],{"class":446},[342,15975,15976,15979,15982,15984,15986,15988,15991,15993],{"class":344,"line":506},[342,15977,15978],{"class":446},"          '",[342,15980,15981],{"class":496},"x-content-type-options",[342,15983,3229],{"class":446},[342,15985,500],{"class":446},[342,15987,4017],{"class":446},[342,15989,15990],{"class":352},"nosniff",[342,15992,3229],{"class":446},[342,15994,527],{"class":446},[342,15996,15997,15999,16002,16004,16006,16008,16011,16013],{"class":344,"line":530},[342,15998,15978],{"class":446},[342,16000,16001],{"class":496},"referrer-policy",[342,16003,3229],{"class":446},[342,16005,500],{"class":446},[342,16007,4017],{"class":446},[342,16009,16010],{"class":352},"strict-origin-when-cross-origin",[342,16012,3229],{"class":446},[342,16014,527],{"class":446},[342,16016,16017,16019,16022,16024,16026,16028,16031,16033],{"class":344,"line":540},[342,16018,15978],{"class":446},[342,16020,16021],{"class":496},"strict-transport-security",[342,16023,3229],{"class":446},[342,16025,500],{"class":446},[342,16027,4017],{"class":446},[342,16029,16030],{"class":352},"max-age=31536000; includeSubDomains",[342,16032,3229],{"class":446},[342,16034,527],{"class":446},[342,16036,16037,16039,16042,16044,16046,16048,16051],{"class":344,"line":571},[342,16038,15978],{"class":446},[342,16040,16041],{"class":496},"content-security-policy",[342,16043,3229],{"class":446},[342,16045,500],{"class":446},[342,16047,460],{"class":446},[342,16049,16050],{"class":352},"default-src 'self'",[342,16052,466],{"class":446},[342,16054,16055],{"class":344,"line":600},[342,16056,14462],{"class":446},[342,16058,16059],{"class":344,"line":629},[342,16060,14537],{"class":446},[342,16062,16063],{"class":344,"line":659},[342,16064,4670],{"class":446},[342,16066,16067],{"class":344,"line":688},[342,16068,1229],{"class":446},[342,16070,16071,16073],{"class":344,"line":694},[342,16072,703],{"class":446},[342,16074,706],{"class":450},[13,16076,16077,16078,16082],{},"If your team needs help implementing a secure Nuxt setup from scratch, our ",[4036,16079,16081],{"href":16080},"\u002Fservices\u002Fnuxt-development","Nuxt.js development services"," include architecture and security configuration.",[328,16084,16086],{"id":16085},"_4-secrets-server-only","4. Secrets → server only",[13,16088,16089,16090,16092,16093,16096,16097,16100],{},"Private keys stay in ",[63,16091,962],{},", only variables prefixed with ",[63,16094,16095],{},"PUBLIC_"," go to the client. Always add ",[63,16098,16099],{},".env.example"," so colleagues know what's needed. Never store tokens in localStorage → use HttpOnly cookies.",[328,16102,16104],{"id":16103},"_5-transparency-documentation","5. Transparency & documentation",[13,16106,16107,16108,16111],{},"Add a ",[17,16109,16110],{},"SECURITY.md"," with instructions on how to report vulnerabilities and fix timelines (Critical ≤ 72h, High ≤ 7d). Add a dedicated Security section to your changelog. Generate an SBOM at release time:",[333,16113,16115],{"className":335,"code":16114,"language":337,"meta":338,"style":338},"npx @cyclonedx\u002Fcyclonedx-npm --output sbom.json\n",[63,16116,16117],{"__ignoreMap":338},[342,16118,16119,16121,16124,16127],{"class":344,"line":345},[342,16120,349],{"class":348},[342,16122,16123],{"class":352}," @cyclonedx\u002Fcyclonedx-npm",[342,16125,16126],{"class":352}," --output",[342,16128,16129],{"class":352}," sbom.json\n",[328,16131,16133],{"id":16132},"_6-lightweight-incident-process","6. Lightweight incident process",[13,16135,16136,16137,16140],{},"Create a ",[63,16138,16139],{},"#security-incidents"," channel (Slack\u002FTeams). Use a simple ticket template: \"Issue → Affected → Temporary mitigation → Fix timeline.\" This builds trust and discipline without bureaucracy.",[26,16142,16144],{"id":16143},"how-to-introduce-this-without-pain","How to introduce this without pain",[41,16146,16147,16160,16163],{},[44,16148,16149,16150,16153,16154,16157,16158,158],{},"Start with ",[17,16151,16152],{},"one PR",": add ",[63,16155,16156],{},"dependency-review.yml"," + ",[63,16159,16110],{},[44,16161,16162],{},"Then gradually add: CSP → SafeHtml → Renovate.",[44,16164,16165,16166],{},"Don't ask the client to \"rewrite everything.\" Instead, show small wins:\n",[41,16167,16168,16171,16174],{},[44,16169,16170],{},"Replacing moment.js → bundle size drops by 200 KB.",[44,16172,16173],{},"Adding CSP → browser blocks XSS attempts.",[44,16175,16176],{},"Generating SBOM → client can show auditors what's inside.",[26,16178,16180],{"id":16179},"bottom-line","Bottom line",[13,16182,16183],{},"CRA isn't just red tape — it's an opportunity to strengthen security and maintenance practices before they become a problem. For Vue developers working in outstaff and service companies, being future-proof means keeping dependencies up to date, building applications with security by design, and documenting security processes so clients can clearly demonstrate compliance.",[13,16185,16186,16187,16191],{},"At Epicmax, we help companies turn these requirements into clear, actionable steps. We can ",[4036,16188,16190],{"href":16189},"\u002Fservices\u002Fcode-audit","audit your existing Vue or Nuxt application",", identify security and compliance gaps, and provide a practical, prioritized action plan — so you know exactly what to fix, why it matters, and how to do it without unnecessary refactoring.",[3114,16193],{"label":16194,"to":3117},"Get a CRA-ready audit",[3119,16196,16197],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":338,"searchDepth":469,"depth":469,"links":16199},[16200,16201,16202,16210,16211],{"id":15660,"depth":359,"text":15661},{"id":15685,"depth":359,"text":15686},{"id":15720,"depth":359,"text":15721,"children":16203},[16204,16205,16206,16207,16208,16209],{"id":15724,"depth":469,"text":15725},{"id":15831,"depth":469,"text":15832},{"id":15911,"depth":469,"text":15912},{"id":16085,"depth":469,"text":16086},{"id":16103,"depth":469,"text":16104},{"id":16132,"depth":469,"text":16133},{"id":16143,"depth":359,"text":16144},{"id":16179,"depth":359,"text":16180},"2025-01-15","The EU Cyber Resilience Act makes software secure-by-default a legal requirement. Here's what Vue and Nuxt developers should do right now to stay compliant.","\u002Fimages\u002Fblog\u002Fcyber-resilience-act\u002Fcard.png",{},"\u002Fblog\u002Fcyber-resilience-act",{"title":15645,"description":16213},"blog\u002Fcyber-resilience-act",[3164,16220,11635],"Security","be2HbsdNr1-i1OaLLeNuXszXFpeg7InN0D8kHekce5c",{"id":16223,"title":16224,"author":7,"authorSlug":8,"body":16225,"date":16361,"description":16362,"extension":3154,"faq":8,"featured":3155,"image":16363,"meta":16364,"navigation":472,"path":16365,"readTime":8,"seo":16366,"stem":16367,"tags":16368,"__hash__":16369},"blog\u002Fblog\u002Fvue-js-talks-3.md","Vue.js Talks #3 – Free Online Meetup for Frontend Developers",{"type":10,"value":16226,"toc":16352},[16227,16230,16233,16237,16241,16247,16250,16261,16265,16270,16273,16287,16291,16296,16299,16310,16314,16328,16332],[13,16228,16229],{},"We're thrilled to invite you to the third edition of Vue.js Talks, a live event dedicated to exploring advanced topics in Vue.js and modern frontend development. This time, we're raising the bar with not just Epicmax speakers — but a special guest expert from the industry.",[13,16231,16232],{},"Whether you're a seasoned Vue developer or just starting out, this session is packed with actionable insights, real-world examples, and cutting-edge techniques to level up your development game.",[26,16234,16236],{"id":16235},"event-agenda","🔥 Event agenda",[328,16238,16240],{"id":16239},"_1-micro-frontends","1. Micro Frontends",[13,16242,16243,16246],{},[17,16244,16245],{},"Speaker:"," Klaus Hamperl – Frontend Team Lead at Einhell Germany, Public Speaker",[13,16248,16249],{},"Discover how micro frontends can revolutionize your application architecture. Klaus will present a hands-on demo and walk through a real-world use case from Einhell's ecosystem. Learn how to:",[41,16251,16252,16255,16258],{},[44,16253,16254],{},"Build distributed frontend applications",[44,16256,16257],{},"Improve component reusability",[44,16259,16260],{},"Organize complex systems with modern frontend practices",[328,16262,16264],{"id":16263},"_2-maxing-component-customization-in-vuejs","2. Maxing Component Customization in Vue.js",[13,16266,16267,16269],{},[17,16268,16245],{}," Yauheni Prakopchuk – CTO at Epicmax",[13,16271,16272],{},"Explore advanced customization strategies for Vue.js components. Yauheni will dive into:",[41,16274,16275,16278,16281,16284],{},[44,16276,16277],{},"Data-driven design patterns",[44,16279,16280],{},"Smart use of props and slots",[44,16282,16283],{},"Scoped CSS, tokens, and theming techniques",[44,16285,16286],{},"Component composition for scalable apps",[328,16288,16290],{"id":16289},"_3-rethinking-solid-principles-in-javascript-part-2","3. Rethinking SOLID Principles in JavaScript – Part 2",[13,16292,16293,16295],{},[17,16294,16245],{}," Dmitry Raichev – Frontend Developer at Epicmax",[13,16297,16298],{},"Continue the journey into object-oriented design in JavaScript. Dmitry explores how to:",[41,16300,16301,16304,16307],{},[44,16302,16303],{},"Apply SOLID principles in real-world JS code",[44,16305,16306],{},"Build maintainable, efficient, and modular systems",[44,16308,16309],{},"Bring clarity and structure to your frontend projects",[26,16311,16313],{"id":16312},"why-you-should-join","✨ Why you should join",[41,16315,16316,16319,16322,16325],{},[44,16317,16318],{},"Learn from Vue.js experts and industry leaders",[44,16320,16321],{},"See live demos and real-world case studies",[44,16323,16324],{},"Gain actionable tips to improve your codebase",[44,16326,16327],{},"Be part of the growing Vue developer community",[26,16329,16331],{"id":16330},"event-details","Event details",[41,16333,16334,16340,16346],{},[44,16335,16336,16339],{},[17,16337,16338],{},"Date:"," Nov 27, 2024",[44,16341,16342,16345],{},[17,16343,16344],{},"Time:"," 6 PM CET",[44,16347,16348,16351],{},[17,16349,16350],{},"Location:"," Online – link provided after registration",{"title":338,"searchDepth":469,"depth":469,"links":16353},[16354,16359,16360],{"id":16235,"depth":359,"text":16236,"children":16355},[16356,16357,16358],{"id":16239,"depth":469,"text":16240},{"id":16263,"depth":469,"text":16264},{"id":16289,"depth":469,"text":16290},{"id":16312,"depth":359,"text":16313},{"id":16330,"depth":359,"text":16331},"2024-11-27","Join the third edition of Vue.js Talks: a free live meetup on micro frontends, Vue component customization, and SOLID principles in JavaScript.","\u002Fimages\u002Fblog\u002Fvue-js-talks-3\u002Fcard.png",{},"\u002Fblog\u002Fvue-js-talks-3",{"title":16224,"description":16362},"blog\u002Fvue-js-talks-3",[8780,3164],"eNBSRxibBOBrejtPr2ZtMONa5eJIOQIOt_yZ3xWGVhk",{"id":16371,"title":16372,"author":7,"authorSlug":8,"body":16373,"date":16457,"description":16458,"extension":3154,"faq":8,"featured":3155,"image":16459,"meta":16460,"navigation":472,"path":16461,"readTime":8,"seo":16462,"stem":16463,"tags":16464,"__hash__":16465},"blog\u002Fblog\u002Fepicmax-partners-with-vuetify.md","Epicmax x Vuetify: A New Chapter in Vue.js Excellence",{"type":10,"value":16374,"toc":16452},[16375,16385,16389,16392,16396,16407,16410,16414,16417,16420,16437,16440,16450],[13,16376,16377,16378,16380,16381,16384],{},"When we began building our own open-source Vue.js framework, ",[17,16379,10747],{},", we drew inspiration from the pioneers of modern frontend development. Among them, ",[17,16382,16383],{},"Vuetify"," stood out as one of the most robust and thoughtfully designed UI libraries in the Vue ecosystem. Its focus on elegant design, usability, and scalability perfectly aligned with our values at Epicmax.",[26,16386,16388],{"id":16387},"inspired-by-the-best-how-vuetify-shaped-our-vision","Inspired by the best: how Vuetify shaped our vision",[13,16390,16391],{},"Over the past seven years, we've successfully delivered dozens of enterprise-grade applications using Vuetify and Vue.js, helping businesses build fast, beautiful, and reliable user interfaces. Through this hands-on experience, we gained deep expertise in Vuetify's capabilities — and saw firsthand how it accelerates frontend development without compromising design standards.",[26,16393,16395],{"id":16394},"official-vuetify-partnership-built-for-innovation","Official Vuetify partnership: built for innovation",[13,16397,16398,16399,16402,16403,16406],{},"We're proud to announce that Epicmax is now an ",[17,16400,16401],{},"official Vuetify partner",", collaborating directly with its creator, ",[17,16404,16405],{},"John Leider",". This partnership is more than a milestone — it's a shared mission to push the boundaries of what's possible with Vue.js.",[13,16408,16409],{},"With direct access to Vuetify's roadmap, insights, and enterprise support, we're uniquely positioned to deliver even more powerful, future-ready solutions to our clients. Whether you're starting a new Vue project or scaling an existing app, you can now benefit from our combined expertise and innovation.",[26,16411,16413],{"id":16412},"why-choose-epicmax-for-vuetify-vuejs-development","Why choose Epicmax for Vuetify & Vue.js development?",[13,16415,16416],{},"As a trusted Vue.js development company, Epicmax helps businesses turn ideas into scalable applications using best-in-class tools and frameworks like Vuetify. Our services go beyond coding — we bring strategic thinking, architectural clarity, and design-driven development to every project.",[13,16418,16419],{},"Our Vuetify & Vue.js consulting services include:",[41,16421,16422,16425,16428,16431,16434],{},[44,16423,16424],{},"Detailed project assessments and accurate estimates",[44,16426,16427],{},"Architecture planning and UI\u002FUX strategy",[44,16429,16430],{},"Full-cycle Vue.js and Vuetify development",[44,16432,16433],{},"Migration to Vuetify 3 and performance optimization",[44,16435,16436],{},"Component design systems and scalable frontend architecture",[13,16438,16439],{},"Whether you need to build from scratch, modernize your existing codebase, or simply get expert advice — we're here to help.",[13,16441,16442,16443,3011,16446,16449],{},"Looking for top-tier ",[17,16444,16445],{},"Vuetify consulting",[17,16447,16448],{},"Vue.js experts","? Let's talk.",[3114,16451],{"label":10273,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":16453},[16454,16455,16456],{"id":16387,"depth":359,"text":16388},{"id":16394,"depth":359,"text":16395},{"id":16412,"depth":359,"text":16413},"2024-10-14","Epicmax is now an official Vuetify partner, collaborating directly with creator John Leider to deliver more powerful, future-ready Vue.js solutions.","\u002Fimages\u002Fblog\u002Fepicmax-partners-with-vuetify\u002Fcard.png",{},"\u002Fblog\u002Fepicmax-partners-with-vuetify",{"title":16372,"description":16458},"blog\u002Fepicmax-partners-with-vuetify",[3164,11636],"8vDLCJH73OF8_O-1cOpc_cGt-dQ-9bFVJdf6QZkOHtI",{"id":16467,"title":8712,"author":7,"authorSlug":8,"body":16468,"date":18546,"description":18547,"extension":3154,"faq":8,"featured":3155,"image":18548,"meta":18549,"navigation":472,"path":8711,"readTime":8,"seo":18550,"stem":18551,"tags":18552,"__hash__":18553},"blog\u002Fblog\u002Ffixing-vue-sfc-templates-for-react.md",{"type":10,"value":16469,"toc":18539},[16470,16473,16477,16480,16483,16486,16699,16702,16912,16916,16926,16930,16937,16952,16958,16969,17043,17057,17257,17266,17272,17404,17410,17606,17610,17613,17842,17856,17944,17957,18190,18213,18523,18527,18534,18536],[13,16471,16472],{},"Moving from React to Vue.js can be tricky. This article shares tips to help you get comfortable with Vue, especially with reusing templates. Learn how to make your Vue.js development smoother with VueUse.",[26,16474,16476],{"id":16475},"the-pain-with-vue-templates","The pain with Vue templates",[13,16478,16479],{},"One of the most common bummers with templates comes from the inability to define a template part and reuse it across the template — for example, when some block needs to appear in different places in the markup depending on a condition.",[13,16481,16482],{},"It doesn't always make sense to extract a part into a separate component of its own. Even with a separate component it's quite common that they need to be passed numerous props, and simply duplicating prop declarations multiple times is too much repetition that grows your template and leaves you open to bugs: when a new prop needs to be added to all of the occurrences it's easy to overlook one (and then debug it for half an hour 🥲).",[13,16484,16485],{},"Let's consider a case where you need to render some template block in the header or footer depending on whether the view is compact. This is not a problem in React, where you can easily do something like this:",[333,16487,16489],{"className":8838,"code":16488,"language":8840,"meta":338,"style":338},"\u002F\u002F Component.jsx\n\nconst block = (\n  \u003Cdiv\n    class=\"block\"\n    onClick={handleClick}\n  >\n    Block that should be reused\n  \u003C\u002Fdiv>\n);\n\nreturn (\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      {!isCompact && block}\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      {isCompact && block}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n);\n",[63,16490,16491,16495,16499,16511,16518,16532,16544,16549,16554,16562,16568,16572,16578,16597,16615,16620,16634,16642,16660,16665,16677,16685,16693],{"__ignoreMap":338},[342,16492,16493],{"class":344,"line":345},[342,16494,8847],{"class":436},[342,16496,16497],{"class":344,"line":359},[342,16498,473],{"emptyLinePlaceholder":472},[342,16500,16501,16503,16506,16508],{"class":344,"line":469},[342,16502,831],{"class":830},[342,16504,16505],{"class":450}," block ",[342,16507,837],{"class":446},[342,16509,16510],{"class":450}," (\n",[342,16512,16513,16515],{"class":344,"line":476},[342,16514,1530],{"class":446},[342,16516,16517],{"class":496},"div\n",[342,16519,16520,16523,16525,16527,16530],{"class":344,"line":493},[342,16521,16522],{"class":830},"    class",[342,16524,837],{"class":446},[342,16526,566],{"class":446},[342,16528,16529],{"class":352},"block",[342,16531,466],{"class":446},[342,16533,16534,16537,16539,16542],{"class":344,"line":506},[342,16535,16536],{"class":830},"    onClick",[342,16538,9455],{"class":446},[342,16540,16541],{"class":450},"handleClick",[342,16543,956],{"class":446},[342,16545,16546],{"class":344,"line":530},[342,16547,16548],{"class":446},"  >\n",[342,16550,16551],{"class":344,"line":540},[342,16552,16553],{"class":450},"    Block that should be reused\n",[342,16555,16556,16558,16560],{"class":344,"line":571},[342,16557,1803],{"class":446},[342,16559,1533],{"class":496},[342,16561,1280],{"class":446},[342,16563,16564,16566],{"class":344,"line":600},[342,16565,294],{"class":450},[342,16567,7819],{"class":446},[342,16569,16570],{"class":344,"line":629},[342,16571,473],{"emptyLinePlaceholder":472},[342,16573,16574,16576],{"class":344,"line":659},[342,16575,2698],{"class":442},[342,16577,16510],{"class":450},[342,16579,16580,16582,16584,16586,16588,16590,16593,16595],{"class":344,"line":688},[342,16581,1530],{"class":446},[342,16583,1533],{"class":496},[342,16585,1536],{"class":830},[342,16587,837],{"class":446},[342,16589,566],{"class":446},[342,16591,16592],{"class":352},"container",[342,16594,566],{"class":446},[342,16596,1280],{"class":446},[342,16598,16599,16601,16603,16605,16607,16609,16611,16613],{"class":344,"line":694},[342,16600,1553],{"class":446},[342,16602,1533],{"class":496},[342,16604,1536],{"class":830},[342,16606,837],{"class":446},[342,16608,566],{"class":446},[342,16610,9817],{"class":352},[342,16612,566],{"class":446},[342,16614,1280],{"class":446},[342,16616,16617],{"class":344,"line":700},[342,16618,16619],{"class":450},"      Header content\n",[342,16621,16622,16625,16628,16630,16632],{"class":344,"line":1461},[342,16623,16624],{"class":446},"      {!",[342,16626,16627],{"class":450},"isCompact ",[342,16629,4757],{"class":446},[342,16631,8291],{"class":450},[342,16633,956],{"class":446},[342,16635,16636,16638,16640],{"class":344,"line":1478},[342,16637,1793],{"class":446},[342,16639,1533],{"class":496},[342,16641,1280],{"class":446},[342,16643,16644,16646,16648,16650,16652,16654,16656,16658],{"class":344,"line":1495},[342,16645,1553],{"class":446},[342,16647,1533],{"class":496},[342,16649,1536],{"class":830},[342,16651,837],{"class":446},[342,16653,566],{"class":446},[342,16655,9855],{"class":352},[342,16657,566],{"class":446},[342,16659,1280],{"class":446},[342,16661,16662],{"class":344,"line":1502},[342,16663,16664],{"class":450},"      Footer content\n",[342,16666,16667,16669,16671,16673,16675],{"class":344,"line":1512},[342,16668,6559],{"class":446},[342,16670,16627],{"class":450},[342,16672,4757],{"class":446},[342,16674,8291],{"class":450},[342,16676,956],{"class":446},[342,16678,16679,16681,16683],{"class":344,"line":1517},[342,16680,1793],{"class":446},[342,16682,1533],{"class":496},[342,16684,1280],{"class":446},[342,16686,16687,16689,16691],{"class":344,"line":1527},[342,16688,1803],{"class":446},[342,16690,1533],{"class":496},[342,16692,1280],{"class":446},[342,16694,16695,16697],{"class":344,"line":1550},[342,16696,294],{"class":450},[342,16698,7819],{"class":446},[13,16700,16701],{},"With Vue, by default you have no other option but to duplicate the block:",[333,16703,16705],{"className":1246,"code":16704,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      \u003Cdiv v-if=\"!isCompact\" class=\"block\" @click=\"handleClick\">\n        Block that should be reused\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      \u003Cdiv v-if=\"isCompact\" class=\"block\" @click=\"handleClick\">\n        Block that should be reused\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[63,16706,16707,16715,16733,16751,16755,16794,16799,16807,16815,16833,16837,16876,16880,16888,16896,16904],{"__ignoreMap":338},[342,16708,16709,16711,16713],{"class":344,"line":345},[342,16710,1023],{"class":446},[342,16712,1522],{"class":496},[342,16714,1280],{"class":446},[342,16716,16717,16719,16721,16723,16725,16727,16729,16731],{"class":344,"line":359},[342,16718,1530],{"class":446},[342,16720,1533],{"class":496},[342,16722,1536],{"class":830},[342,16724,837],{"class":446},[342,16726,566],{"class":446},[342,16728,16592],{"class":352},[342,16730,566],{"class":446},[342,16732,1280],{"class":446},[342,16734,16735,16737,16739,16741,16743,16745,16747,16749],{"class":344,"line":469},[342,16736,1553],{"class":446},[342,16738,1533],{"class":496},[342,16740,1536],{"class":830},[342,16742,837],{"class":446},[342,16744,566],{"class":446},[342,16746,9817],{"class":352},[342,16748,566],{"class":446},[342,16750,1280],{"class":446},[342,16752,16753],{"class":344,"line":476},[342,16754,16619],{"class":450},[342,16756,16757,16759,16761,16763,16765,16767,16770,16772,16774,16776,16778,16780,16782,16784,16786,16788,16790,16792],{"class":344,"line":493},[342,16758,1604],{"class":446},[342,16760,1533],{"class":496},[342,16762,2276],{"class":830},[342,16764,837],{"class":446},[342,16766,566],{"class":446},[342,16768,16769],{"class":352},"!isCompact",[342,16771,566],{"class":446},[342,16773,1536],{"class":830},[342,16775,837],{"class":446},[342,16777,566],{"class":446},[342,16779,16529],{"class":352},[342,16781,566],{"class":446},[342,16783,9670],{"class":830},[342,16785,837],{"class":446},[342,16787,566],{"class":446},[342,16789,16541],{"class":352},[342,16791,566],{"class":446},[342,16793,1280],{"class":446},[342,16795,16796],{"class":344,"line":506},[342,16797,16798],{"class":450},"        Block that should be reused\n",[342,16800,16801,16803,16805],{"class":344,"line":530},[342,16802,1782],{"class":446},[342,16804,1533],{"class":496},[342,16806,1280],{"class":446},[342,16808,16809,16811,16813],{"class":344,"line":540},[342,16810,1793],{"class":446},[342,16812,1533],{"class":496},[342,16814,1280],{"class":446},[342,16816,16817,16819,16821,16823,16825,16827,16829,16831],{"class":344,"line":571},[342,16818,1553],{"class":446},[342,16820,1533],{"class":496},[342,16822,1536],{"class":830},[342,16824,837],{"class":446},[342,16826,566],{"class":446},[342,16828,9855],{"class":352},[342,16830,566],{"class":446},[342,16832,1280],{"class":446},[342,16834,16835],{"class":344,"line":600},[342,16836,16664],{"class":450},[342,16838,16839,16841,16843,16845,16847,16849,16852,16854,16856,16858,16860,16862,16864,16866,16868,16870,16872,16874],{"class":344,"line":629},[342,16840,1604],{"class":446},[342,16842,1533],{"class":496},[342,16844,2276],{"class":830},[342,16846,837],{"class":446},[342,16848,566],{"class":446},[342,16850,16851],{"class":352},"isCompact",[342,16853,566],{"class":446},[342,16855,1536],{"class":830},[342,16857,837],{"class":446},[342,16859,566],{"class":446},[342,16861,16529],{"class":352},[342,16863,566],{"class":446},[342,16865,9670],{"class":830},[342,16867,837],{"class":446},[342,16869,566],{"class":446},[342,16871,16541],{"class":352},[342,16873,566],{"class":446},[342,16875,1280],{"class":446},[342,16877,16878],{"class":344,"line":659},[342,16879,16798],{"class":450},[342,16881,16882,16884,16886],{"class":344,"line":688},[342,16883,1782],{"class":446},[342,16885,1533],{"class":496},[342,16887,1280],{"class":446},[342,16889,16890,16892,16894],{"class":344,"line":694},[342,16891,1793],{"class":446},[342,16893,1533],{"class":496},[342,16895,1280],{"class":446},[342,16897,16898,16900,16902],{"class":344,"line":700},[342,16899,1803],{"class":446},[342,16901,1533],{"class":496},[342,16903,1280],{"class":446},[342,16905,16906,16908,16910],{"class":344,"line":1461},[342,16907,1505],{"class":446},[342,16909,1522],{"class":496},[342,16911,1280],{"class":446},[26,16913,16915],{"id":16914},"what-is-vueuse","What is VueUse",[13,16917,16918,16921,16922,16925],{},[17,16919,16920],{},"VueUse"," is a utility library aimed at solving common use-cases, packed with useful composables and other helpers — similar to ",[17,16923,16924],{},"react-use"," in the React ecosystem.",[26,16927,16929],{"id":16928},"reusing-parts-of-the-template","Reusing parts of the template",[13,16931,16932,16933,16936],{},"VueUse provides a ",[63,16934,16935],{},"createReusableTemplate"," function that can help to DRY your templates. As it's a separate package and not part of the Vue framework API, it needs to be installed:",[333,16938,16940],{"className":335,"code":16939,"language":337,"meta":338,"style":338},"npm i @vueuse\u002Fcore\n",[63,16941,16942],{"__ignoreMap":338},[342,16943,16944,16946,16949],{"class":344,"line":345},[342,16945,393],{"class":348},[342,16947,16948],{"class":352}," i",[342,16950,16951],{"class":352}," @vueuse\u002Fcore\n",[13,16953,16954,16955,16957],{},"Check out the installation guide for instructions for your package manager of choice or meta-framework (if you're using ",[17,16956,11635],{},").",[13,16959,16960,16961,16964,16965,16968],{},"To reuse markup you need to first call ",[63,16962,16963],{},"createReusableTemplate()"," in the ",[63,16966,16967],{},"\u003Cscript>"," section:",[333,16970,16972],{"className":1246,"code":16971,"language":1248,"meta":338,"style":338},"\u003Cscript setup lang=\"ts\">\nimport { createReusableTemplate } from '@vueuse\u002Fcore'\nconst [DefineTemplate, ReuseTemplate] = createReusableTemplate()\n\u003C\u002Fscript>\n",[63,16973,16974,16994,17013,17035],{"__ignoreMap":338},[342,16975,16976,16978,16980,16982,16984,16986,16988,16990,16992],{"class":344,"line":345},[342,16977,1023],{"class":446},[342,16979,1262],{"class":496},[342,16981,1265],{"class":830},[342,16983,1268],{"class":830},[342,16985,837],{"class":446},[342,16987,566],{"class":446},[342,16989,1275],{"class":352},[342,16991,566],{"class":446},[342,16993,1280],{"class":446},[342,16995,16996,16998,17000,17003,17005,17007,17009,17011],{"class":344,"line":359},[342,16997,443],{"class":442},[342,16999,447],{"class":446},[342,17001,17002],{"class":450}," createReusableTemplate",[342,17004,454],{"class":446},[342,17006,457],{"class":442},[342,17008,4017],{"class":446},[342,17010,5623],{"class":352},[342,17012,5273],{"class":446},[342,17014,17015,17017,17019,17022,17024,17027,17029,17031,17033],{"class":344,"line":469},[342,17016,831],{"class":830},[342,17018,3226],{"class":446},[342,17020,17021],{"class":450},"DefineTemplate",[342,17023,1047],{"class":446},[342,17025,17026],{"class":450}," ReuseTemplate",[342,17028,1212],{"class":446},[342,17030,1107],{"class":446},[342,17032,17002],{"class":362},[342,17034,1112],{"class":450},[342,17036,17037,17039,17041],{"class":344,"line":476},[342,17038,1505],{"class":446},[342,17040,1262],{"class":496},[342,17042,1280],{"class":446},[13,17044,17045,17046,17048,17049,17052,17053,17056],{},"It returns two component constructors: ",[63,17047,17021],{}," to wrap the template part you want to reuse, and ",[63,17050,17051],{},"ReuseTemplate"," to render the part in place. You can then use them in ",[63,17054,17055],{},"\u003Ctemplate>"," like so:",[333,17058,17060],{"className":1246,"code":17059,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CDefineTemplate>\n    \u003Cdiv class=\"block\" @click=\"handleClick\">\n      Block that should be reused\n    \u003C\u002Fdiv>\n  \u003C\u002FDefineTemplate>\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      \u003CReuseTemplate v-if=\"!isCompact\" \u002F>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      \u003CReuseTemplate v-if=\"isCompact\" \u002F>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[63,17061,17062,17070,17078,17106,17111,17119,17127,17145,17163,17167,17185,17193,17211,17215,17233,17241,17249],{"__ignoreMap":338},[342,17063,17064,17066,17068],{"class":344,"line":345},[342,17065,1023],{"class":446},[342,17067,1522],{"class":496},[342,17069,1280],{"class":446},[342,17071,17072,17074,17076],{"class":344,"line":359},[342,17073,1530],{"class":446},[342,17075,17021],{"class":496},[342,17077,1280],{"class":446},[342,17079,17080,17082,17084,17086,17088,17090,17092,17094,17096,17098,17100,17102,17104],{"class":344,"line":469},[342,17081,1553],{"class":446},[342,17083,1533],{"class":496},[342,17085,1536],{"class":830},[342,17087,837],{"class":446},[342,17089,566],{"class":446},[342,17091,16529],{"class":352},[342,17093,566],{"class":446},[342,17095,9670],{"class":830},[342,17097,837],{"class":446},[342,17099,566],{"class":446},[342,17101,16541],{"class":352},[342,17103,566],{"class":446},[342,17105,1280],{"class":446},[342,17107,17108],{"class":344,"line":476},[342,17109,17110],{"class":450},"      Block that should be reused\n",[342,17112,17113,17115,17117],{"class":344,"line":493},[342,17114,1793],{"class":446},[342,17116,1533],{"class":496},[342,17118,1280],{"class":446},[342,17120,17121,17123,17125],{"class":344,"line":506},[342,17122,1803],{"class":446},[342,17124,17021],{"class":496},[342,17126,1280],{"class":446},[342,17128,17129,17131,17133,17135,17137,17139,17141,17143],{"class":344,"line":530},[342,17130,1530],{"class":446},[342,17132,1533],{"class":496},[342,17134,1536],{"class":830},[342,17136,837],{"class":446},[342,17138,566],{"class":446},[342,17140,16592],{"class":352},[342,17142,566],{"class":446},[342,17144,1280],{"class":446},[342,17146,17147,17149,17151,17153,17155,17157,17159,17161],{"class":344,"line":540},[342,17148,1553],{"class":446},[342,17150,1533],{"class":496},[342,17152,1536],{"class":830},[342,17154,837],{"class":446},[342,17156,566],{"class":446},[342,17158,9817],{"class":352},[342,17160,566],{"class":446},[342,17162,1280],{"class":446},[342,17164,17165],{"class":344,"line":571},[342,17166,16619],{"class":450},[342,17168,17169,17171,17173,17175,17177,17179,17181,17183],{"class":344,"line":600},[342,17170,1604],{"class":446},[342,17172,17051],{"class":496},[342,17174,2276],{"class":830},[342,17176,837],{"class":446},[342,17178,566],{"class":446},[342,17180,16769],{"class":352},[342,17182,566],{"class":446},[342,17184,7779],{"class":446},[342,17186,17187,17189,17191],{"class":344,"line":629},[342,17188,1793],{"class":446},[342,17190,1533],{"class":496},[342,17192,1280],{"class":446},[342,17194,17195,17197,17199,17201,17203,17205,17207,17209],{"class":344,"line":659},[342,17196,1553],{"class":446},[342,17198,1533],{"class":496},[342,17200,1536],{"class":830},[342,17202,837],{"class":446},[342,17204,566],{"class":446},[342,17206,9855],{"class":352},[342,17208,566],{"class":446},[342,17210,1280],{"class":446},[342,17212,17213],{"class":344,"line":688},[342,17214,16664],{"class":450},[342,17216,17217,17219,17221,17223,17225,17227,17229,17231],{"class":344,"line":694},[342,17218,1604],{"class":446},[342,17220,17051],{"class":496},[342,17222,2276],{"class":830},[342,17224,837],{"class":446},[342,17226,566],{"class":446},[342,17228,16851],{"class":352},[342,17230,566],{"class":446},[342,17232,7779],{"class":446},[342,17234,17235,17237,17239],{"class":344,"line":700},[342,17236,1793],{"class":446},[342,17238,1533],{"class":496},[342,17240,1280],{"class":446},[342,17242,17243,17245,17247],{"class":344,"line":1461},[342,17244,1803],{"class":446},[342,17246,1533],{"class":496},[342,17248,1280],{"class":446},[342,17250,17251,17253,17255],{"class":344,"line":1478},[342,17252,1505],{"class":446},[342,17254,1522],{"class":496},[342,17256,1280],{"class":446},[13,17258,17259,17262,17263,158],{},[63,17260,17261],{},"\u003CDefineTemplate>"," must be used before ",[63,17264,17265],{},"\u003CReuseTemplate>",[13,17267,17268,17269,17271],{},"You can't, however, use the return of one ",[63,17270,16963],{}," call to reuse different blocks simultaneously:",[333,17273,17275],{"className":1246,"code":17274,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CDefineTemplate>\n    \u003Cdiv>Reused block A\u003C\u002Fdiv>\n  \u003C\u002FDefineTemplate>\n  \u003CReuseTemplate \u002F>\n  \u003C!-- registers another template instead -->\n  \u003CDefineTemplate>\n    \u003Cdiv>Reused block B\u003C\u002Fdiv>\n  \u003C\u002FDefineTemplate>\n  \u003C!-- will now render new template -->\n  \u003CReuseTemplate \u002F>\n\u003C\u002Ftemplate>\n\n\u003C!-- Output: -->\n\u003C!-- Reused block A -->\n\u003C!-- Reused block B -->\n",[63,17276,17277,17285,17293,17310,17318,17326,17331,17339,17356,17364,17369,17377,17385,17389,17394,17399],{"__ignoreMap":338},[342,17278,17279,17281,17283],{"class":344,"line":345},[342,17280,1023],{"class":446},[342,17282,1522],{"class":496},[342,17284,1280],{"class":446},[342,17286,17287,17289,17291],{"class":344,"line":359},[342,17288,1530],{"class":446},[342,17290,17021],{"class":496},[342,17292,1280],{"class":446},[342,17294,17295,17297,17299,17301,17304,17306,17308],{"class":344,"line":469},[342,17296,1553],{"class":446},[342,17298,1533],{"class":496},[342,17300,1035],{"class":446},[342,17302,17303],{"class":450},"Reused block A",[342,17305,1505],{"class":446},[342,17307,1533],{"class":496},[342,17309,1280],{"class":446},[342,17311,17312,17314,17316],{"class":344,"line":476},[342,17313,1803],{"class":446},[342,17315,17021],{"class":496},[342,17317,1280],{"class":446},[342,17319,17320,17322,17324],{"class":344,"line":493},[342,17321,1530],{"class":446},[342,17323,17051],{"class":496},[342,17325,7779],{"class":446},[342,17327,17328],{"class":344,"line":506},[342,17329,17330],{"class":436},"  \u003C!-- registers another template instead -->\n",[342,17332,17333,17335,17337],{"class":344,"line":530},[342,17334,1530],{"class":446},[342,17336,17021],{"class":496},[342,17338,1280],{"class":446},[342,17340,17341,17343,17345,17347,17350,17352,17354],{"class":344,"line":540},[342,17342,1553],{"class":446},[342,17344,1533],{"class":496},[342,17346,1035],{"class":446},[342,17348,17349],{"class":450},"Reused block B",[342,17351,1505],{"class":446},[342,17353,1533],{"class":496},[342,17355,1280],{"class":446},[342,17357,17358,17360,17362],{"class":344,"line":571},[342,17359,1803],{"class":446},[342,17361,17021],{"class":496},[342,17363,1280],{"class":446},[342,17365,17366],{"class":344,"line":600},[342,17367,17368],{"class":436},"  \u003C!-- will now render new template -->\n",[342,17370,17371,17373,17375],{"class":344,"line":629},[342,17372,1530],{"class":446},[342,17374,17051],{"class":496},[342,17376,7779],{"class":446},[342,17378,17379,17381,17383],{"class":344,"line":659},[342,17380,1505],{"class":446},[342,17382,1522],{"class":496},[342,17384,1280],{"class":446},[342,17386,17387],{"class":344,"line":688},[342,17388,473],{"emptyLinePlaceholder":472},[342,17390,17391],{"class":344,"line":694},[342,17392,17393],{"class":436},"\u003C!-- Output: -->\n",[342,17395,17396],{"class":344,"line":700},[342,17397,17398],{"class":436},"\u003C!-- Reused block A -->\n",[342,17400,17401],{"class":344,"line":1461},[342,17402,17403],{"class":436},"\u003C!-- Reused block B -->\n",[13,17405,17406,17407,17409],{},"So instead you would need to call ",[63,17408,16963],{}," for each individual reused piece you want to use simultaneously in the markup:",[333,17411,17413],{"className":1246,"code":17412,"language":1248,"meta":338,"style":338},"\u003Cscript setup>\nimport { createReusableTemplate } from '@vueuse\u002Fcore'\n\nconst [DefineBlockA, ReuseBlockA] = createReusableTemplate()\nconst [DefineBlockB, ReuseBlockB] = createReusableTemplate()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CDefineBlockA>\n    \u003Cdiv>Reused block A\u003C\u002Fdiv>\n  \u003C\u002FDefineBlockA>\n  \u003CDefineBlockB>\n    \u003Cdiv>Reused block B\u003C\u002Fdiv>\n  \u003C\u002FDefineBlockB>\n  \u003C!-- the two can exist simultaneously -->\n  \u003CReuseBlockA \u002F>\n  \u003CReuseBlockB \u002F>\n\u003C\u002Ftemplate>\n",[63,17414,17415,17425,17443,17447,17469,17491,17499,17503,17511,17519,17535,17543,17551,17567,17575,17580,17589,17598],{"__ignoreMap":338},[342,17416,17417,17419,17421,17423],{"class":344,"line":345},[342,17418,1023],{"class":446},[342,17420,1262],{"class":496},[342,17422,1265],{"class":830},[342,17424,1280],{"class":446},[342,17426,17427,17429,17431,17433,17435,17437,17439,17441],{"class":344,"line":359},[342,17428,443],{"class":442},[342,17430,447],{"class":446},[342,17432,17002],{"class":450},[342,17434,454],{"class":446},[342,17436,457],{"class":442},[342,17438,4017],{"class":446},[342,17440,5623],{"class":352},[342,17442,5273],{"class":446},[342,17444,17445],{"class":344,"line":469},[342,17446,473],{"emptyLinePlaceholder":472},[342,17448,17449,17451,17453,17456,17458,17461,17463,17465,17467],{"class":344,"line":476},[342,17450,831],{"class":830},[342,17452,3226],{"class":446},[342,17454,17455],{"class":450},"DefineBlockA",[342,17457,1047],{"class":446},[342,17459,17460],{"class":450}," ReuseBlockA",[342,17462,1212],{"class":446},[342,17464,1107],{"class":446},[342,17466,17002],{"class":362},[342,17468,1112],{"class":450},[342,17470,17471,17473,17475,17478,17480,17483,17485,17487,17489],{"class":344,"line":493},[342,17472,831],{"class":830},[342,17474,3226],{"class":446},[342,17476,17477],{"class":450},"DefineBlockB",[342,17479,1047],{"class":446},[342,17481,17482],{"class":450}," ReuseBlockB",[342,17484,1212],{"class":446},[342,17486,1107],{"class":446},[342,17488,17002],{"class":362},[342,17490,1112],{"class":450},[342,17492,17493,17495,17497],{"class":344,"line":506},[342,17494,1505],{"class":446},[342,17496,1262],{"class":496},[342,17498,1280],{"class":446},[342,17500,17501],{"class":344,"line":530},[342,17502,473],{"emptyLinePlaceholder":472},[342,17504,17505,17507,17509],{"class":344,"line":540},[342,17506,1023],{"class":446},[342,17508,1522],{"class":496},[342,17510,1280],{"class":446},[342,17512,17513,17515,17517],{"class":344,"line":571},[342,17514,1530],{"class":446},[342,17516,17455],{"class":496},[342,17518,1280],{"class":446},[342,17520,17521,17523,17525,17527,17529,17531,17533],{"class":344,"line":600},[342,17522,1553],{"class":446},[342,17524,1533],{"class":496},[342,17526,1035],{"class":446},[342,17528,17303],{"class":450},[342,17530,1505],{"class":446},[342,17532,1533],{"class":496},[342,17534,1280],{"class":446},[342,17536,17537,17539,17541],{"class":344,"line":629},[342,17538,1803],{"class":446},[342,17540,17455],{"class":496},[342,17542,1280],{"class":446},[342,17544,17545,17547,17549],{"class":344,"line":659},[342,17546,1530],{"class":446},[342,17548,17477],{"class":496},[342,17550,1280],{"class":446},[342,17552,17553,17555,17557,17559,17561,17563,17565],{"class":344,"line":688},[342,17554,1553],{"class":446},[342,17556,1533],{"class":496},[342,17558,1035],{"class":446},[342,17560,17349],{"class":450},[342,17562,1505],{"class":446},[342,17564,1533],{"class":496},[342,17566,1280],{"class":446},[342,17568,17569,17571,17573],{"class":344,"line":694},[342,17570,1803],{"class":446},[342,17572,17477],{"class":496},[342,17574,1280],{"class":446},[342,17576,17577],{"class":344,"line":700},[342,17578,17579],{"class":436},"  \u003C!-- the two can exist simultaneously -->\n",[342,17581,17582,17584,17587],{"class":344,"line":1461},[342,17583,1530],{"class":446},[342,17585,17586],{"class":496},"ReuseBlockA",[342,17588,7779],{"class":446},[342,17590,17591,17593,17596],{"class":344,"line":1478},[342,17592,1530],{"class":446},[342,17594,17595],{"class":496},"ReuseBlockB",[342,17597,7779],{"class":446},[342,17599,17600,17602,17604],{"class":344,"line":1495},[342,17601,1505],{"class":446},[342,17603,1522],{"class":496},[342,17605,1280],{"class":446},[26,17607,17609],{"id":17608},"analogue-to-react-render-functions","Analogue to React render functions",[13,17611,17612],{},"Having the ability to reuse the same template part is good, but with JSX in React one can also define a render function to dynamically pass different props to the reused piece:",[333,17614,17616],{"className":8838,"code":17615,"language":8840,"meta":338,"style":338},"const renderBlock = (className) => (\n  \u003Cdiv\n    class={`block ${className}`}\n    onClick={handleClick}>\n    Block that should be reused and customized.\n  \u003C\u002Fdiv>\n);\n\nreturn (\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      {!isCompact && renderBlock(\"full\")}\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      {isCompact && renderBlock(\"compact\")}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n);\n",[63,17617,17618,17638,17644,17663,17674,17679,17687,17693,17697,17703,17721,17739,17743,17767,17775,17793,17797,17820,17828,17836],{"__ignoreMap":338},[342,17619,17620,17622,17625,17627,17629,17632,17634,17636],{"class":344,"line":345},[342,17621,831],{"class":830},[342,17623,17624],{"class":450}," renderBlock ",[342,17626,837],{"class":446},[342,17628,1138],{"class":446},[342,17630,17631],{"class":2819},"className",[342,17633,294],{"class":446},[342,17635,1011],{"class":830},[342,17637,16510],{"class":450},[342,17639,17640,17642],{"class":344,"line":359},[342,17641,1530],{"class":446},[342,17643,16517],{"class":496},[342,17645,17646,17648,17650,17652,17655,17657,17659,17661],{"class":344,"line":469},[342,17647,16522],{"class":830},[342,17649,9455],{"class":446},[342,17651,1913],{"class":446},[342,17653,17654],{"class":352},"block ",[342,17656,1919],{"class":446},[342,17658,17631],{"class":450},[342,17660,1935],{"class":446},[342,17662,956],{"class":446},[342,17664,17665,17667,17669,17671],{"class":344,"line":476},[342,17666,16536],{"class":830},[342,17668,9455],{"class":446},[342,17670,16541],{"class":450},[342,17672,17673],{"class":446},"}>\n",[342,17675,17676],{"class":344,"line":493},[342,17677,17678],{"class":450},"    Block that should be reused and customized.\n",[342,17680,17681,17683,17685],{"class":344,"line":506},[342,17682,1803],{"class":446},[342,17684,1533],{"class":496},[342,17686,1280],{"class":446},[342,17688,17689,17691],{"class":344,"line":530},[342,17690,294],{"class":450},[342,17692,7819],{"class":446},[342,17694,17695],{"class":344,"line":540},[342,17696,473],{"emptyLinePlaceholder":472},[342,17698,17699,17701],{"class":344,"line":571},[342,17700,2698],{"class":442},[342,17702,16510],{"class":450},[342,17704,17705,17707,17709,17711,17713,17715,17717,17719],{"class":344,"line":600},[342,17706,1530],{"class":446},[342,17708,1533],{"class":496},[342,17710,1536],{"class":830},[342,17712,837],{"class":446},[342,17714,566],{"class":446},[342,17716,16592],{"class":352},[342,17718,566],{"class":446},[342,17720,1280],{"class":446},[342,17722,17723,17725,17727,17729,17731,17733,17735,17737],{"class":344,"line":629},[342,17724,1553],{"class":446},[342,17726,1533],{"class":496},[342,17728,1536],{"class":830},[342,17730,837],{"class":446},[342,17732,566],{"class":446},[342,17734,9817],{"class":352},[342,17736,566],{"class":446},[342,17738,1280],{"class":446},[342,17740,17741],{"class":344,"line":659},[342,17742,16619],{"class":450},[342,17744,17745,17747,17749,17751,17754,17756,17758,17761,17763,17765],{"class":344,"line":688},[342,17746,16624],{"class":446},[342,17748,16627],{"class":450},[342,17750,4757],{"class":446},[342,17752,17753],{"class":362}," renderBlock",[342,17755,487],{"class":450},[342,17757,566],{"class":446},[342,17759,17760],{"class":352},"full",[342,17762,566],{"class":446},[342,17764,294],{"class":450},[342,17766,956],{"class":446},[342,17768,17769,17771,17773],{"class":344,"line":694},[342,17770,1793],{"class":446},[342,17772,1533],{"class":496},[342,17774,1280],{"class":446},[342,17776,17777,17779,17781,17783,17785,17787,17789,17791],{"class":344,"line":700},[342,17778,1553],{"class":446},[342,17780,1533],{"class":496},[342,17782,1536],{"class":830},[342,17784,837],{"class":446},[342,17786,566],{"class":446},[342,17788,9855],{"class":352},[342,17790,566],{"class":446},[342,17792,1280],{"class":446},[342,17794,17795],{"class":344,"line":1461},[342,17796,16664],{"class":450},[342,17798,17799,17801,17803,17805,17807,17809,17811,17814,17816,17818],{"class":344,"line":1478},[342,17800,6559],{"class":446},[342,17802,16627],{"class":450},[342,17804,4757],{"class":446},[342,17806,17753],{"class":362},[342,17808,487],{"class":450},[342,17810,566],{"class":446},[342,17812,17813],{"class":352},"compact",[342,17815,566],{"class":446},[342,17817,294],{"class":450},[342,17819,956],{"class":446},[342,17821,17822,17824,17826],{"class":344,"line":1495},[342,17823,1793],{"class":446},[342,17825,1533],{"class":496},[342,17827,1280],{"class":446},[342,17829,17830,17832,17834],{"class":344,"line":1502},[342,17831,1803],{"class":446},[342,17833,1533],{"class":496},[342,17835,1280],{"class":446},[342,17837,17838,17840],{"class":344,"line":1512},[342,17839,294],{"class":450},[342,17841,7819],{"class":446},[13,17843,17844,17845,17848,17849,17851,17852,17855],{},"Vue provides ",[17,17846,17847],{},"scoped slots"," as the general alternative to React JSX render functions, and ",[63,17850,16935],{}," supports this feature to pass data to the reused template. You can use the same ",[63,17853,17854],{},"v-slot"," you would normally use to pass data from a child component back to the parent:",[333,17857,17859],{"className":1246,"code":17858,"language":1248,"meta":338,"style":338},"\u003Cscript setup lang=\"ts\">\nimport { createReusableTemplate } from '@vueuse\u002Fcore'\n\nconst [DefineTemplate, ReuseTemplate] = createReusableTemplate\u003C{ className: string }>()\n\u003C\u002Fscript>\n",[63,17860,17861,17881,17899,17903,17936],{"__ignoreMap":338},[342,17862,17863,17865,17867,17869,17871,17873,17875,17877,17879],{"class":344,"line":345},[342,17864,1023],{"class":446},[342,17866,1262],{"class":496},[342,17868,1265],{"class":830},[342,17870,1268],{"class":830},[342,17872,837],{"class":446},[342,17874,566],{"class":446},[342,17876,1275],{"class":352},[342,17878,566],{"class":446},[342,17880,1280],{"class":446},[342,17882,17883,17885,17887,17889,17891,17893,17895,17897],{"class":344,"line":359},[342,17884,443],{"class":442},[342,17886,447],{"class":446},[342,17888,17002],{"class":450},[342,17890,454],{"class":446},[342,17892,457],{"class":442},[342,17894,4017],{"class":446},[342,17896,5623],{"class":352},[342,17898,5273],{"class":446},[342,17900,17901],{"class":344,"line":469},[342,17902,473],{"emptyLinePlaceholder":472},[342,17904,17905,17907,17909,17911,17913,17915,17917,17919,17921,17924,17927,17929,17931,17934],{"class":344,"line":476},[342,17906,831],{"class":830},[342,17908,3226],{"class":446},[342,17910,17021],{"class":450},[342,17912,1047],{"class":446},[342,17914,17026],{"class":450},[342,17916,1212],{"class":446},[342,17918,1107],{"class":446},[342,17920,17002],{"class":362},[342,17922,17923],{"class":446},"\u003C{",[342,17925,17926],{"class":496}," className",[342,17928,500],{"class":446},[342,17930,1986],{"class":348},[342,17932,17933],{"class":446}," }>",[342,17935,1112],{"class":450},[342,17937,17938,17940,17942],{"class":344,"line":493},[342,17939,1505],{"class":446},[342,17941,1262],{"class":496},[342,17943,1280],{"class":446},[13,17945,17946,17948,17949,17951,17952,17954,17955,500],{},[63,17947,16935],{}," accepts a generic type argument for typing the data passed to the template, similar to specifying a render function signature. You can then bind props to ",[63,17950,17051],{}," and access them in the reusable part inside ",[63,17953,17021],{}," via ",[63,17956,17854],{},[333,17958,17960],{"className":1246,"code":17959,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CDefineTemplate v-slot=\"{ className }\">\n    \u003Cdiv :class=\"['block', className]\" @click=\"handleClick\">\n      Block that should be reused and customized.\n    \u003C\u002Fdiv>\n  \u003C\u002FDefineTemplate>\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      \u003CReuseTemplate v-if=\"!isCompact\" className=\"full\" \u002F>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      \u003CReuseTemplate v-if=\"isCompact\" className=\"compact\" \u002F>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[63,17961,17962,17970,17989,18019,18024,18032,18040,18058,18076,18080,18108,18116,18134,18138,18166,18174,18182],{"__ignoreMap":338},[342,17963,17964,17966,17968],{"class":344,"line":345},[342,17965,1023],{"class":446},[342,17967,1522],{"class":496},[342,17969,1280],{"class":446},[342,17971,17972,17974,17976,17978,17980,17982,17985,17987],{"class":344,"line":359},[342,17973,1530],{"class":446},[342,17975,17021],{"class":496},[342,17977,9948],{"class":830},[342,17979,837],{"class":446},[342,17981,566],{"class":446},[342,17983,17984],{"class":352},"{ className }",[342,17986,566],{"class":446},[342,17988,1280],{"class":446},[342,17990,17991,17993,17995,17998,18000,18002,18005,18007,18009,18011,18013,18015,18017],{"class":344,"line":469},[342,17992,1553],{"class":446},[342,17994,1533],{"class":496},[342,17996,17997],{"class":830}," :class",[342,17999,837],{"class":446},[342,18001,566],{"class":446},[342,18003,18004],{"class":352},"['block', className]",[342,18006,566],{"class":446},[342,18008,9670],{"class":830},[342,18010,837],{"class":446},[342,18012,566],{"class":446},[342,18014,16541],{"class":352},[342,18016,566],{"class":446},[342,18018,1280],{"class":446},[342,18020,18021],{"class":344,"line":476},[342,18022,18023],{"class":450},"      Block that should be reused and customized.\n",[342,18025,18026,18028,18030],{"class":344,"line":493},[342,18027,1793],{"class":446},[342,18029,1533],{"class":496},[342,18031,1280],{"class":446},[342,18033,18034,18036,18038],{"class":344,"line":506},[342,18035,1803],{"class":446},[342,18037,17021],{"class":496},[342,18039,1280],{"class":446},[342,18041,18042,18044,18046,18048,18050,18052,18054,18056],{"class":344,"line":530},[342,18043,1530],{"class":446},[342,18045,1533],{"class":496},[342,18047,1536],{"class":830},[342,18049,837],{"class":446},[342,18051,566],{"class":446},[342,18053,16592],{"class":352},[342,18055,566],{"class":446},[342,18057,1280],{"class":446},[342,18059,18060,18062,18064,18066,18068,18070,18072,18074],{"class":344,"line":540},[342,18061,1553],{"class":446},[342,18063,1533],{"class":496},[342,18065,1536],{"class":830},[342,18067,837],{"class":446},[342,18069,566],{"class":446},[342,18071,9817],{"class":352},[342,18073,566],{"class":446},[342,18075,1280],{"class":446},[342,18077,18078],{"class":344,"line":571},[342,18079,16619],{"class":450},[342,18081,18082,18084,18086,18088,18090,18092,18094,18096,18098,18100,18102,18104,18106],{"class":344,"line":600},[342,18083,1604],{"class":446},[342,18085,17051],{"class":496},[342,18087,2276],{"class":830},[342,18089,837],{"class":446},[342,18091,566],{"class":446},[342,18093,16769],{"class":352},[342,18095,566],{"class":446},[342,18097,17926],{"class":830},[342,18099,837],{"class":446},[342,18101,566],{"class":446},[342,18103,17760],{"class":352},[342,18105,566],{"class":446},[342,18107,7779],{"class":446},[342,18109,18110,18112,18114],{"class":344,"line":629},[342,18111,1793],{"class":446},[342,18113,1533],{"class":496},[342,18115,1280],{"class":446},[342,18117,18118,18120,18122,18124,18126,18128,18130,18132],{"class":344,"line":659},[342,18119,1553],{"class":446},[342,18121,1533],{"class":496},[342,18123,1536],{"class":830},[342,18125,837],{"class":446},[342,18127,566],{"class":446},[342,18129,9855],{"class":352},[342,18131,566],{"class":446},[342,18133,1280],{"class":446},[342,18135,18136],{"class":344,"line":688},[342,18137,16664],{"class":450},[342,18139,18140,18142,18144,18146,18148,18150,18152,18154,18156,18158,18160,18162,18164],{"class":344,"line":694},[342,18141,1604],{"class":446},[342,18143,17051],{"class":496},[342,18145,2276],{"class":830},[342,18147,837],{"class":446},[342,18149,566],{"class":446},[342,18151,16851],{"class":352},[342,18153,566],{"class":446},[342,18155,17926],{"class":830},[342,18157,837],{"class":446},[342,18159,566],{"class":446},[342,18161,17813],{"class":352},[342,18163,566],{"class":446},[342,18165,7779],{"class":446},[342,18167,18168,18170,18172],{"class":344,"line":700},[342,18169,1793],{"class":446},[342,18171,1533],{"class":496},[342,18173,1280],{"class":446},[342,18175,18176,18178,18180],{"class":344,"line":1461},[342,18177,1803],{"class":446},[342,18179,1533],{"class":496},[342,18181,1280],{"class":446},[342,18183,18184,18186,18188],{"class":344,"line":1478},[342,18185,1505],{"class":446},[342,18187,1522],{"class":496},[342,18189,1280],{"class":446},[13,18191,18192,18193,18195,18196,18198,18199,18202,18203,18205,18206,18208,18209,18212],{},"Render functions in JSX can also accept JSX as arguments to be rendered somewhere inside. No worries — ",[63,18194,16935],{}," has you covered here as well, as ",[63,18197,17051],{}," can be passed slots that can be accessed via ",[63,18200,18201],{},"$slots"," on ",[63,18204,17854],{}," and rendered inside ",[63,18207,17021],{}," with ",[17,18210,18211],{},"dynamic components"," syntax:",[333,18214,18216],{"className":1246,"code":18215,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003CDefineTemplate v-slot=\"{ className, $slots }\">\n    \u003Cdiv :class=\"['block', className]\" @click=\"handleClick\">\n      Block that should be reused with custom content.\n      Custom content:\n      \u003C!-- To render the slot -->\n      \u003Ccomponent :is=\"$slots.default\" \u002F>\n    \u003C\u002Fdiv>\n  \u003C\u002FDefineTemplate>\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      \u003CReuseTemplate v-if=\"!isCompact\" className=\"full\">\n        \u003Cdiv>Full content\u003C\u002Fdiv>\n      \u003C\u002FReuseTemplate>\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      \u003CReuseTemplate v-if=\"isCompact\" className=\"compact\">\n        \u003Cdiv>Compact content\u003C\u002Fdiv>\n      \u003C\u002FReuseTemplate>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[63,18217,18218,18226,18245,18273,18278,18283,18288,18307,18315,18323,18341,18359,18363,18391,18408,18416,18424,18442,18446,18474,18491,18499,18507,18515],{"__ignoreMap":338},[342,18219,18220,18222,18224],{"class":344,"line":345},[342,18221,1023],{"class":446},[342,18223,1522],{"class":496},[342,18225,1280],{"class":446},[342,18227,18228,18230,18232,18234,18236,18238,18241,18243],{"class":344,"line":359},[342,18229,1530],{"class":446},[342,18231,17021],{"class":496},[342,18233,9948],{"class":830},[342,18235,837],{"class":446},[342,18237,566],{"class":446},[342,18239,18240],{"class":352},"{ className, $slots }",[342,18242,566],{"class":446},[342,18244,1280],{"class":446},[342,18246,18247,18249,18251,18253,18255,18257,18259,18261,18263,18265,18267,18269,18271],{"class":344,"line":469},[342,18248,1553],{"class":446},[342,18250,1533],{"class":496},[342,18252,17997],{"class":830},[342,18254,837],{"class":446},[342,18256,566],{"class":446},[342,18258,18004],{"class":352},[342,18260,566],{"class":446},[342,18262,9670],{"class":830},[342,18264,837],{"class":446},[342,18266,566],{"class":446},[342,18268,16541],{"class":352},[342,18270,566],{"class":446},[342,18272,1280],{"class":446},[342,18274,18275],{"class":344,"line":476},[342,18276,18277],{"class":450},"      Block that should be reused with custom content.\n",[342,18279,18280],{"class":344,"line":493},[342,18281,18282],{"class":450},"      Custom content:\n",[342,18284,18285],{"class":344,"line":506},[342,18286,18287],{"class":436},"      \u003C!-- To render the slot -->\n",[342,18289,18290,18292,18294,18296,18298,18300,18303,18305],{"class":344,"line":530},[342,18291,1604],{"class":446},[342,18293,10236],{"class":496},[342,18295,11812],{"class":830},[342,18297,837],{"class":446},[342,18299,566],{"class":446},[342,18301,18302],{"class":352},"$slots.default",[342,18304,566],{"class":446},[342,18306,7779],{"class":446},[342,18308,18309,18311,18313],{"class":344,"line":540},[342,18310,1793],{"class":446},[342,18312,1533],{"class":496},[342,18314,1280],{"class":446},[342,18316,18317,18319,18321],{"class":344,"line":571},[342,18318,1803],{"class":446},[342,18320,17021],{"class":496},[342,18322,1280],{"class":446},[342,18324,18325,18327,18329,18331,18333,18335,18337,18339],{"class":344,"line":600},[342,18326,1530],{"class":446},[342,18328,1533],{"class":496},[342,18330,1536],{"class":830},[342,18332,837],{"class":446},[342,18334,566],{"class":446},[342,18336,16592],{"class":352},[342,18338,566],{"class":446},[342,18340,1280],{"class":446},[342,18342,18343,18345,18347,18349,18351,18353,18355,18357],{"class":344,"line":629},[342,18344,1553],{"class":446},[342,18346,1533],{"class":496},[342,18348,1536],{"class":830},[342,18350,837],{"class":446},[342,18352,566],{"class":446},[342,18354,9817],{"class":352},[342,18356,566],{"class":446},[342,18358,1280],{"class":446},[342,18360,18361],{"class":344,"line":659},[342,18362,16619],{"class":450},[342,18364,18365,18367,18369,18371,18373,18375,18377,18379,18381,18383,18385,18387,18389],{"class":344,"line":688},[342,18366,1604],{"class":446},[342,18368,17051],{"class":496},[342,18370,2276],{"class":830},[342,18372,837],{"class":446},[342,18374,566],{"class":446},[342,18376,16769],{"class":352},[342,18378,566],{"class":446},[342,18380,17926],{"class":830},[342,18382,837],{"class":446},[342,18384,566],{"class":446},[342,18386,17760],{"class":352},[342,18388,566],{"class":446},[342,18390,1280],{"class":446},[342,18392,18393,18395,18397,18399,18402,18404,18406],{"class":344,"line":694},[342,18394,1679],{"class":446},[342,18396,1533],{"class":496},[342,18398,1035],{"class":446},[342,18400,18401],{"class":450},"Full content",[342,18403,1505],{"class":446},[342,18405,1533],{"class":496},[342,18407,1280],{"class":446},[342,18409,18410,18412,18414],{"class":344,"line":700},[342,18411,1782],{"class":446},[342,18413,17051],{"class":496},[342,18415,1280],{"class":446},[342,18417,18418,18420,18422],{"class":344,"line":1461},[342,18419,1793],{"class":446},[342,18421,1533],{"class":496},[342,18423,1280],{"class":446},[342,18425,18426,18428,18430,18432,18434,18436,18438,18440],{"class":344,"line":1478},[342,18427,1553],{"class":446},[342,18429,1533],{"class":496},[342,18431,1536],{"class":830},[342,18433,837],{"class":446},[342,18435,566],{"class":446},[342,18437,9855],{"class":352},[342,18439,566],{"class":446},[342,18441,1280],{"class":446},[342,18443,18444],{"class":344,"line":1495},[342,18445,16664],{"class":450},[342,18447,18448,18450,18452,18454,18456,18458,18460,18462,18464,18466,18468,18470,18472],{"class":344,"line":1502},[342,18449,1604],{"class":446},[342,18451,17051],{"class":496},[342,18453,2276],{"class":830},[342,18455,837],{"class":446},[342,18457,566],{"class":446},[342,18459,16851],{"class":352},[342,18461,566],{"class":446},[342,18463,17926],{"class":830},[342,18465,837],{"class":446},[342,18467,566],{"class":446},[342,18469,17813],{"class":352},[342,18471,566],{"class":446},[342,18473,1280],{"class":446},[342,18475,18476,18478,18480,18482,18485,18487,18489],{"class":344,"line":1512},[342,18477,1679],{"class":446},[342,18479,1533],{"class":496},[342,18481,1035],{"class":446},[342,18483,18484],{"class":450},"Compact content",[342,18486,1505],{"class":446},[342,18488,1533],{"class":496},[342,18490,1280],{"class":446},[342,18492,18493,18495,18497],{"class":344,"line":1517},[342,18494,1782],{"class":446},[342,18496,17051],{"class":496},[342,18498,1280],{"class":446},[342,18500,18501,18503,18505],{"class":344,"line":1527},[342,18502,1793],{"class":446},[342,18504,1533],{"class":496},[342,18506,1280],{"class":446},[342,18508,18509,18511,18513],{"class":344,"line":1550},[342,18510,1803],{"class":446},[342,18512,1533],{"class":496},[342,18514,1280],{"class":446},[342,18516,18517,18519,18521],{"class":344,"line":1581},[342,18518,1505],{"class":446},[342,18520,1522],{"class":496},[342,18522,1280],{"class":446},[26,18524,18526],{"id":18525},"thats-it","That's it!",[13,18528,18529,18530,18533],{},"If you're a React dev struggling to embrace Vue because you feel down having to write templates, consider giving it another shot with this addon. Alternatively, if this still isn't enough to make templates plausible for you, you can learn how to ",[4036,18531,18532],{"href":8717},"use JSX with Vue"," to make your experience with Vue more close to home.",[3114,18535],{"label":10273,"to":3117},[3119,18537,18538],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":338,"searchDepth":469,"depth":469,"links":18540},[18541,18542,18543,18544,18545],{"id":16475,"depth":359,"text":16476},{"id":16914,"depth":359,"text":16915},{"id":16928,"depth":359,"text":16929},{"id":17608,"depth":359,"text":17609},{"id":18525,"depth":359,"text":18526},"2024-06-12","Coming to Vue from React? Learn how to reuse template blocks in Vue SFCs with VueUse's createReusableTemplate — the closest thing to JSX render functions.","\u002Fimages\u002Fblog\u002Ffixing-vue-sfc-templates-for-react\u002Fcard.png",{},{"title":8712,"description":18547},"blog\u002Ffixing-vue-sfc-templates-for-react",[3164,8742,16920],"XFUpaPhnz2MdwyGb8NmrKcGM-2JxaObtkvn5XqUIm7c",{"id":18555,"title":18556,"author":7,"authorSlug":8,"body":18557,"date":18639,"description":18640,"extension":3154,"faq":8,"featured":3155,"image":18641,"meta":18642,"navigation":472,"path":18643,"readTime":8,"seo":18644,"stem":18645,"tags":8,"__hash__":18646},"blog\u002Fblog\u002Fepicmax-bilbao-slush.md","Epicmax at Bilbao Slush'D 2025",{"type":10,"value":18558,"toc":18633},[18559,18579,18585,18589,18592,18596,18603,18607,18618,18622,18627],[13,18560,18561,18564,18565,18567,18568,18571,18572,11447,18575,18578],{},[17,18562,18563],{},"Bilbao Slush'D"," is the first edition of the famous global event Slush, tailored to the Southern European context. We are excited to announce that Epicmax will be participating in the upcoming ",[17,18566,18563],{}," conference, taking place on ",[17,18569,18570],{},"February 20th, 2025",", at the ",[17,18573,18574],{},"Bilbao Exhibition Centre (BEC)",[17,18576,18577],{},"Bilbao, Spain",". This event brings together top experts and companies in the tech industry, and we can't wait to showcase our solutions and discuss the latest trends in development.",[13,18580,18581],{},[7602,18582],{"alt":18583,"src":18584},"Bilbao Slush'D 2025 conference banner","\u002Fimages\u002Fblog\u002Fepicmax-bilbao-slush\u002F1739958194092.jpeg",[26,18586,18588],{"id":18587},"about-bilbao-slushd-conference","About Bilbao Slush'D Conference",[13,18590,18591],{},"The conference will gather over 200 international investors, including venture capital firms, venture debt, corporate venture capital, and business angels, as well as more than 150 startups. It's an excellent platform to network, discuss innovations, and explore new business opportunities.",[26,18593,18595],{"id":18594},"what-well-be-showcasing-at-bilbao-slushd","What We'll Be Showcasing at Bilbao Slush'D",[13,18597,18598,18599,18602],{},"At ",[17,18600,18601],{},"Booth 4",", we're excited to share our experience in developing scalable Vue.js solutions and discuss how our UI frameworks help IT companies streamline their development processes. We've prepared some exciting demos and case studies that we hope will inspire and be useful to you.",[26,18604,18606],{"id":18605},"why-you-should-visit-our-booth","Why You Should Visit Our Booth",[41,18608,18609,18612,18615],{},[44,18610,18611],{},"Expert Consultations: Our specialists are ready to answer your questions and share best practices in development.",[44,18613,18614],{},"Networking Opportunities: Connect with industry professionals and discuss potential projects.",[44,18616,18617],{},"Interactive Demos: Experience our products and services in action and see their functionality firsthand.",[26,18619,18621],{"id":18620},"about-us","About Us",[13,18623,18624,18626],{},[17,18625,7497],{}," is a team of professionals dedicated to creating high-quality web development solutions. With a focus on Vue.js and other modern technologies, we strive to help companies succeed in the digital world.",[13,18628,18629,18630,18632],{},"We look forward to seeing you at ",[17,18631,18563],{}," and are confident that our collaboration can lead to exciting opportunities and projects. See you in Bilbao!",{"title":338,"searchDepth":469,"depth":469,"links":18634},[18635,18636,18637,18638],{"id":18587,"depth":359,"text":18588},{"id":18594,"depth":359,"text":18595},{"id":18605,"depth":359,"text":18606},{"id":18620,"depth":359,"text":18621},"2024-04-10","Meet Epicmax at Bilbao Slush'D 2025! Discover cutting-edge Vue.js solutions, UI frameworks, and expert strategies to boost your front-end development. Stop by Booth 4 to connect with our team and explore collaboration opportunities.","\u002Fimages\u002Fblog\u002Fepicmax-bilbao-slush\u002Fcard.jpeg",{},"\u002Fblog\u002Fepicmax-bilbao-slush",{"title":18556,"description":18640},"blog\u002Fepicmax-bilbao-slush","JdOEf0gmtQN380maa0Q_lGJMXdYSkjXdxZJyxYQmNEw",{"id":18648,"title":18649,"author":7,"authorSlug":8,"body":18650,"date":18639,"description":18725,"extension":3154,"faq":8,"featured":3155,"image":18726,"meta":18727,"navigation":472,"path":18728,"readTime":8,"seo":18729,"stem":18730,"tags":8,"__hash__":18731},"blog\u002Fblog\u002Fepicmax-devdays-europe-2025.md","Unlocking the Power of Open Source – Epicmax at DevDays Europe 2025",{"type":10,"value":18651,"toc":18723},[18652,18664,18669,18674,18688,18691,18696,18699,18704,18709],[13,18653,18654,18657,18660,18663],{},[17,18655,18656],{},"DevDays Europe 2025",[17,18658,18659],{},"Vilnius, Lithuania",[17,18661,18662],{},"May 21-23, 2025"," We’re excited to announce that our Co-founder & COO, Anastasiia Zvenigorodskaia, will be speaking at DevDays Europe 2025 – one of the leading software development conferences in Europe, taking place in Vilnius, Lithuania.\nIn her talk, Anastasiia will explore how open-source projects can go beyond community value and become sustainable, profitable business assets.",[13,18665,18666],{},[7602,18667],{"alt":338,"src":18668},"\u002Fimages\u002Fblog\u002Fepicmax-devdays-europe-2025\u002F1740473534291.jpeg",[13,18670,18671],{},[17,18672,18673],{},"What You’ll Learn",[41,18675,18676,18679,18682,18685],{},[44,18677,18678],{},"How to transform open-source contributions into real business value",[44,18680,18681],{},"Proven monetization models: from freemium and premium support to dual licensing and enterprise partnerships",[44,18683,18684],{},"How to generate revenue while maintaining transparency and community trust",[44,18686,18687],{},"Strategies to align business goals with community-driven development",[13,18689,18690],{},"Whether you’re a startup founder, open-source contributor, or tech lead looking to bring structure and sustainability to your project — this talk is for you.",[13,18692,18693],{},[17,18694,18695],{},"Why It Matters",[13,18697,18698],{},"Many open-source projects start as passion-driven ideas, but without a solid strategy, they risk burnout or stagnation. Anastasiia will share her real-world experience of scaling community-driven initiatives into business-ready products — and how to do it without compromising your values.",[13,18700,18701],{},[17,18702,18703],{},"Let’s Meet in Vilnius",[13,18705,18706,18708],{},[17,18707,18656],{}," is a perfect opportunity to connect with developers, leaders, and creators from across the continent.\nReserve your spot, join the conversation, and let’s unlock the true business potential of open source — together.",[13,18710,18711,18714,18715,18714,18719],{},[17,18712,18713],{},"Register and learn more:"," ",[4036,18716,18717],{"href":18717,"rel":18718},"https:\u002F\u002Fdevdays.lt\u002F",[4040],[4036,18720,18722],{"href":18721},"\u002Fblog","All News and Events",{"title":338,"searchDepth":469,"depth":469,"links":18724},[],"Epicmax Co-founder & COO Anastasiia Zvenigorodskaia will speak at DevDays Europe 2025 in Vilnius on “How to Make Open Source Profitable.” Discover strategies to turn open-source projects into sustainable and revenue-generating business assets.","\u002Fimages\u002Fblog\u002Fepicmax-devdays-europe-2025\u002Fcard.jpeg",{},"\u002Fblog\u002Fepicmax-devdays-europe-2025",{"title":18649,"description":18725},"blog\u002Fepicmax-devdays-europe-2025","4AnIOyFbrCkFLaTRPXKMKzflVyxHdz3vkusSpXZ4HAQ",{"id":18733,"title":18734,"author":7,"authorSlug":8,"body":18735,"date":18813,"description":18814,"extension":3154,"faq":8,"featured":3155,"image":18815,"meta":18816,"navigation":472,"path":18817,"readTime":8,"seo":18818,"stem":18819,"tags":8,"__hash__":18820},"blog\u002Fblog\u002Fsalamanca-ole.md","Epicmax at Startup Olé 2024 – Startup Networking in Spain",{"type":10,"value":18736,"toc":18811},[18737,18741,18751,18756,18759,18762,18767,18770,18775,18791,18797,18802],[1556,18738,18740],{"id":18739},"epicmax-at-startup-olé-salamanca","Epicmax at Startup Olé Salamanca 🇪🇸",[13,18742,18743,18744,18746,18747,18750],{},"📅 October 2024\n📍 Salamanca, Spain Last week, part of the ",[17,18745,7497],{}," team took part in",[17,18748,18749],{},"Startup Olé 2024"," in Salamanca — and what a ride it was! 🚀",[13,18752,18753],{},[7602,18754],{"alt":338,"src":18755},"\u002Fimages\u002Fblog\u002Fsalamanca-ole\u002F3.jpg",[13,18757,18758],{},"From the very first minute, the event felt like a celebration of creativity, technology, and collaboration. We were surrounded by inspiring startup founders, investors, mentors, and innovators from all over Europe — and beyond. The energy was amazing, the conversations were deep, and the ideas were endless.",[13,18760,18761],{},"Our COO, Anastasiia Zvenigorodskaia, shared her thoughts after the event:",[13,18763,18764],{},[5161,18765,18766],{},"\"I haven’t seen so many amazing and friendly people gathered in one place 😍. Thanks to everyone who shared valuable insights and patiently listened to my non-stop ideas on how to optimize literally everything =)\"",[13,18768,18769],{},"We were blown away by the incredible projects presented — bold, smart, and full of purpose. And even if not everyone walked away with an award, it’s clear to us: you’re already on the path to success.",[13,18771,18772],{},[7602,18773],{"alt":338,"src":18774},"\u002Fimages\u002Fblog\u002Fsalamanca-ole\u002F4.jpg",[13,18776,18777,18780,18781,18785,18786,18790],{},[17,18778,18779],{},"Why Events Like Startup Olé Matter","\nAt Epicmax, we’re passionate about building tools and frameworks that help teams scale. Whether it’s through open source (hello, ",[4036,18782,10747],{"href":18783,"rel":18784},"https:\u002F\u002Fgithub.com\u002Fepicmaxco\u002Fvuestic-ui",[4040]," 👋), ",[4036,18787,18789],{"href":7217,"rel":18788},[4040],"consulting services",", or sharing insights, we believe in empowering people to do more with less chaos.\nEvents like Startup Olé remind us how much potential there is when tech meets community. It's not just about funding or pitching — it's about listening, exchanging, and building real connections.",[13,18792,18793,18796],{},[17,18794,18795],{},"Salamanca — Windy, Rainy, and Unforgettable","\nFor us, this was also a first visit to Salamanca, and it instantly became one of our favorite cities in Spain. The historic streets, cozy record shops, and unbelievably good food… it’s a place we’ll definitely return to (hopefully with more sunshine next time ☀️🌧️).",[13,18798,18799],{},[7602,18800],{"alt":338,"src":18801},"\u002Fimages\u002Fblog\u002Fsalamanca-ole\u002F5.jpg",[13,18803,18804,18805,18808,18809],{},"We’re leaving this event full of ideas, new friends, and renewed energy. Thanks again to ",[17,18806,18807],{},"Startup Olé Accelerator"," for organizing such a powerful gathering. See you soon — at the next conference! ✨ ",[4036,18810,18722],{"href":18721},{"title":338,"searchDepth":469,"depth":469,"links":18812},[],"2024-04-07","Epicmax joined Startup Olé 2024 in Salamanca, Spain – connecting with startups, sharing growth ideas, and exploring innovation, open source, and tech networking.","\u002Fimages\u002Fblog\u002Fsalamanca-ole\u002Fcard.jpg",{},"\u002Fblog\u002Fsalamanca-ole",{"title":18734,"description":18814},"blog\u002Fsalamanca-ole","h0WlWp-441dsTKf5IYuTtLoihtgBi8OZx8oGlaTdnds",{"id":18822,"title":18823,"author":7,"authorSlug":8,"body":18824,"date":18813,"description":18926,"extension":3154,"faq":8,"featured":3155,"image":18927,"meta":18928,"navigation":472,"path":18929,"readTime":8,"seo":18930,"stem":18931,"tags":8,"__hash__":18932},"blog\u002Fblog\u002Fvuejs-conf-2024.md","Epicmax at Vue.js.de Conf 2024 Germany – Vue.js & UI Library Talk",{"type":10,"value":18825,"toc":18922},[18826,18832,18839,18845,18848,18852,18855,18869,18872,18876,18879,18882,18885,18888,18902,18905,18914,18920],[13,18827,18828,18829,18831],{},"📅 October 8–9, 2024",[4375,18830],{},"\n📍 Vue.js.de Conf, Germany",[13,18833,18834,18835,18838],{},"We’re excited to announce that Yauheni Prakopchyk, CTO at Epicmax and lead developer of the open-source Vuestic UI component framework, will be speaking at ",[17,18836,18837],{},"Vue.js.de Conf 2024"," – one of the leading Vue.js conferences in Europe!",[13,18840,18841],{},[7602,18842],{"alt":18843,"src":18844},"Vue.js.de Conf 2024 Germany — Epicmax CTO Yauheni Prakopchyk speaker announcement","\u002Fimages\u002Fblog\u002Fvuejs-conf-2024\u002Fvuejs_talks_7_1080_x.jpg",[13,18846,18847],{},"If you're passionate about Vue.js development, building scalable front-end architecture, and UI component libraries, this is a talk you won't want to miss.",[26,18849,18851],{"id":18850},"talk-cooking-up-a-ui-component-library","🎤 Talk: Cooking Up a UI Component Library",[13,18853,18854],{},"In his session, Yauheni will walk through the process of creating a custom Vue.js UI component library from scratch. He’ll dive deep into:",[41,18856,18857,18860,18863,18866],{},[44,18858,18859],{},"When and why your company should build its own Vue.js component system",[44,18861,18862],{},"Common mistakes in front-end library development",[44,18864,18865],{},"Best practices for planning, testing, accessibility, and documentation",[44,18867,18868],{},"Lessons from developing Vuestic UI, an open-source Vue 3 component framework trusted by thousands of developers",[13,18870,18871],{},"Whether you’re a startup scaling your design system or an enterprise building internal tools, this session will provide actionable insights into Vue.js UI architecture and component-driven development.",[26,18873,18875],{"id":18874},"about-the-speaker","👨‍💻 About the Speaker",[13,18877,18878],{},"Yauheni Prakopchyk, CTO @ Epicmax.",[13,18880,18881],{},"Yauheni is a seasoned front-end engineer with 10+ years of experience in full-stack development. After transitioning from Laravel to Vue.js, he became a core contributor and visionary behind Vuestic UI – a powerful, open-source Vue.js UI library.",[13,18883,18884],{},"As CTO at Epicmax, Yauheni leads the engineering team, contributes to open source, and mentors companies on scaling their Vue.js applications and component libraries. He has spoken at international Vue conferences, including VueConf Toronto 2022 and VueJs Amsterdam 2023.",[13,18886,18887],{},"If you're attending Vue.js.de Conf 2024 in Germany and want to chat about:",[41,18889,18890,18893,18896,18899],{},[44,18891,18892],{},"Building open-source tools with Vue.js",[44,18894,18895],{},"Creating efficient, accessible UI component systems",[44,18897,18898],{},"Scaling front-end architecture with Vue",[44,18900,18901],{},"Collaborating on Vuestic UI or Epicmax services",[13,18903,18904],{},"… we’d love to meet you in person!",[13,18906,18907,18908,18913],{},"📅 ",[4036,18909,18912],{"href":18910,"rel":18911},"https:\u002F\u002Fcalendar.app.google\u002FnqGDauqE7kCkxExX7",[4040],"Book a meeting with our team"," – let’s talk Vue.js, open source, and building better tools together.",[13,18915,18916],{},[7602,18917],{"alt":18918,"src":18919},"Vue.js.de Conf 2024 venue and audience","\u002Fimages\u002Fblog\u002Fvuejs-conf-2024\u002F1727958978221.jpeg",[3114,18921],{"label":18722,"to":18721},{"title":338,"searchDepth":469,"depth":469,"links":18923},[18924,18925],{"id":18850,"depth":359,"text":18851},{"id":18874,"depth":359,"text":18875},"Join Epicmax CTO at Vue.js.de Conf 2024 to learn how to build scalable Vue 3 UI component libraries with best practices in architecture, testing, accessibility, and open source.","\u002Fimages\u002Fblog\u002Fvuejs-conf-2024\u002Fcard.jpg",{},"\u002Fblog\u002Fvuejs-conf-2024",{"title":18823,"description":18926},"blog\u002Fvuejs-conf-2024","gAU6NXhNI9ScIyoSquYChTgo9x-7VqziL_0YhIzM8xg",{"id":18934,"title":18935,"author":7,"authorSlug":8,"body":18936,"date":19219,"description":19220,"extension":3154,"faq":8,"featured":3155,"image":19221,"meta":19222,"navigation":472,"path":19223,"readTime":8,"seo":19224,"stem":19225,"tags":8,"__hash__":19226},"blog\u002Fblog\u002Fekool.md","eKool Case Study: K12 EdTech Platform Built with Vuetify and Vue.js",{"type":10,"value":18937,"toc":19203},[18938,18942,18953,18962,18968,18971,18974,18978,19002,19006,19009,19026,19032,19036,19040,19051,19055,19063,19067,19078,19082,19090,19094,19106,19110,19136,19140,19143,19160,19163,19167,19175,19179,19182,19193,19196,19201],[26,18939,18941],{"id":18940},"ekool-a-national-k12-education-platform","eKool: A National K12 Education Platform",[13,18943,18944,18945,18948,18949,18952],{},"Looking to build a scalable ",[17,18946,18947],{},"online school platform"," or modern ",[17,18950,18951],{},"K12 EdTech solution","? Discover how Epicmax used Vue.js and Vuetify to help eKool—Estonia’s national education system—scale across 90% of the country’s schools.",[13,18954,18955,18956,18961],{},"👉 ",[4036,18957,18960],{"href":18958,"rel":18959},"https:\u002F\u002Fepicmax.co\u002Fekool",[4040],"Check out the full eKool case study"," and explore what Epicmax can do for educational platform.",[13,18963,18964],{},[7602,18965],{"alt":18966,"src":18967},"eKool K12 education platform — Vue.js dashboard for students, teachers, and parents","\u002Fimages\u002Fblog\u002Fekool\u002F67444fbf19301343be44.png",[13,18969,18970],{},"eKool is a digital learning platform that powers communication and collaboration between students, teachers, school administrators, parents, and government authorities.",[13,18972,18973],{},"Designed for K12 education, it’s one of the most widely adopted online learning platforms in Europe. With over 90% of Estonian schools using it, eKool supports assignment tracking, grading, communication, and real-time updates for nearly 30% of Estonia’s population.",[26,18975,18977],{"id":18976},"why-vuejs-is-the-best-choice-for-online-learning-platforms","Why Vue.js Is the Best Choice for Online Learning Platforms",[13,18979,18980,18981,18983,18984,18986,18987,18989,18990,18992,18993,18995,18996,18998,18999,19001],{},"When building a scalable K12 educational platform or online school, choosing the right frontend framework is critical. Here’s why Vue.js is a top choice for EdTech:",[4375,18982],{},"\n⚡ Fast load times for students and teachers across devices",[4375,18985],{},"\n🧩 Component-based architecture for reusable educational features",[4375,18988],{},"\n📚 Support for real-time dashboards, notifications, calendars, and grading systems",[4375,18991],{},"\n🧠 Intuitive syntax, perfect for mixed-skill dev teams",[4375,18994],{},"\n🛠️ Ecosystem of tools: Vue 3, Vuetify, Pinia, Vuex, Vite",[4375,18997],{},"\n🔄 Long-term maintainability for public-sector apps",[4375,19000],{},"\n👨🏫 Trusted by schools, universities, and online learning platforms globally",[26,19003,19005],{"id":19004},"project-challenges","Project Challenges",[13,19007,19008],{},"eKool’s microservices-based architecture posed several challenges:",[41,19010,19011,19014,19017,19020,19023],{},[44,19012,19013],{},"Lack of design and structure consistency across services",[44,19015,19016],{},"Need for a custom UI component library with Vuetify",[44,19018,19019],{},"Migration from Vue 2 to Vue 3",[44,19021,19022],{},"Legacy technologies blocking performance",[44,19024,19025],{},"Need for clear, centralized developer documentation",[13,19027,19028,19031],{},[17,19029,19030],{},"Our goal:"," modernize and unify eKool’s frontend infrastructure while keeping the user experience intuitive and efficient for students, teachers, and admins.",[26,19033,19035],{"id":19034},"our-solution-for-a-scalable-edtech-platform","Our Solution for a Scalable EdTech Platform",[328,19037,19039],{"id":19038},"custom-ui-component-library-with-vuetify","🧩 Custom UI Component Library with Vuetify",[41,19041,19042,19045,19048],{},[44,19043,19044],{},"Designed to wrap Vuetify with school-specific branding and behavior",[44,19046,19047],{},"Ensured reusability across multiple apps",[44,19049,19050],{},"Followed best UX practices for online learning platforms",[328,19052,19054],{"id":19053},"documentation-built-in-storybook","📘 Documentation Built in Storybook",[41,19056,19057,19060],{},[44,19058,19059],{},"Made for fast onboarding of EdTech development teams",[44,19061,19062],{},"Included live examples, usage guidelines, and developer notes",[328,19064,19066],{"id":19065},"️-vue-3-migration","⚙️ Vue 3 Migration",[41,19068,19069,19072,19075],{},[44,19070,19071],{},"Migrated core components to Vue 3",[44,19073,19074],{},"Applied Composition API and modern patterns",[44,19076,19077],{},"Removed outdated dependencies and improved app speed",[328,19079,19081],{"id":19080},"boilerplate-for-new-microservices","🚀 Boilerplate for New Microservices",[41,19083,19084,19087],{},[44,19085,19086],{},"Ready-to-use template with routing, layout, and component integration",[44,19088,19089],{},"Ideal for launching new features like online gradebooks, report generators, or messaging tools",[26,19091,19093],{"id":19092},"key-results","Key Results",[13,19095,19096,19097,19099,19100,19102,19103,19105],{},"✅ Unified and scalable UI across the entire K12 platform",[4375,19098],{},"\n✅ Improved development speed for new education modules",[4375,19101],{},"\n✅ Future-ready frontend with Vue 3, Vuetify, and Pinia",[4375,19104],{},"\n✅ Clean, maintainable codebase built for long-term growth",[26,19107,19109],{"id":19108},"tech-stack-for-online-school-development","Tech Stack for Online School Development",[41,19111,19112,19118,19124,19130],{},[44,19113,19114,19117],{},[17,19115,19116],{},"Frontend:"," Vue.js 2 → Vue 3, Vuetify, Pinia, Composition API",[44,19119,19120,19123],{},[17,19121,19122],{},"Tooling:"," Storybook, ESLint, Vite",[44,19125,19126,19129],{},[17,19127,19128],{},"Architecture:"," Microservices with backend integrations",[44,19131,19132,19135],{},[17,19133,19134],{},"Design Focus:"," Accessibility, mobile support, multilingual UI",[26,19137,19139],{"id":19138},"why-epicmax-for-vuejs-edtech-development","Why Epicmax for Vue.js EdTech Development?",[13,19141,19142],{},"Epicmax is a Vue.js-focused agency trusted by startups, government platforms, and global enterprises. If you’re building a K12 EdTech product, online school, or educational dashboard, we can help you:",[41,19144,19145,19148,19151,19154,19157],{},[44,19146,19147],{},"Audit your existing system",[44,19149,19150],{},"Migrate to Vue 3 and modern tools",[44,19152,19153],{},"Build a frontend from scratch",[44,19155,19156],{},"Create a design system for your platform",[44,19158,19159],{},"Launch fast and scale safely",[13,19161,19162],{},"We blend engineering with product thinking — and we understand how to build platforms that support millions of users in education.",[26,19164,19166],{"id":19165},"got-an-edtech-project-in-mind","Got an EdTech Project in Mind?",[13,19168,19169,19170,19174],{},"Let’s bring it to life. Contact us at ",[4036,19171,19173],{"href":19172},"mailto:hello@epicmax.co","hello@epicmax.co"," or via WhatsApp, and let’s discuss how we can help you build the next big thing in online learning.",[26,19176,19178],{"id":19177},"about-epicmax","About Epicmax",[13,19180,19181],{},"Epicmax is a core contributor to the Vue.js ecosystem, known for open-source tools like:",[41,19183,19184,19187,19190],{},[44,19185,19186],{},"Vuestic UI – UI framework for Vue 3",[44,19188,19189],{},"Vuestic Admin – Admin dashboard for Vue.js",[44,19191,19192],{},"Epic Spinners – Lightweight Vue animation library",[13,19194,19195],{},"We help you build better, faster, and smarter—especially when education is on the line.",[13,19197,18955,19198,18961],{},[4036,19199,18960],{"href":18958,"rel":19200},[4040],[3114,19202],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":19204},[19205,19206,19207,19208,19214,19215,19216,19217,19218],{"id":18940,"depth":359,"text":18941},{"id":18976,"depth":359,"text":18977},{"id":19004,"depth":359,"text":19005},{"id":19034,"depth":359,"text":19035,"children":19209},[19210,19211,19212,19213],{"id":19038,"depth":469,"text":19039},{"id":19053,"depth":469,"text":19054},{"id":19065,"depth":469,"text":19066},{"id":19080,"depth":469,"text":19081},{"id":19092,"depth":359,"text":19093},{"id":19108,"depth":359,"text":19109},{"id":19138,"depth":359,"text":19139},{"id":19165,"depth":359,"text":19166},{"id":19177,"depth":359,"text":19178},"2024-03-27","Discover how Epicmax used Vue.js and Vuetify to build eKool — a K12 EdTech platform for online schools. Scalable architecture, UI library & Vue 3 migration.","\u002Fimages\u002Fblog\u002Fekool\u002Fcard.png",{},"\u002Fblog\u002Fekool",{"title":18935,"description":19220},"blog\u002Fekool","-mhLkXc0yzWmEAIhSjawFxw9wi0u0iqzlwbMjlgLVW0",{"id":19228,"title":19229,"author":7,"authorSlug":8,"body":19230,"date":19346,"description":19347,"extension":3154,"faq":8,"featured":3155,"image":19348,"meta":19349,"navigation":472,"path":19350,"readTime":8,"seo":19351,"stem":19352,"tags":8,"__hash__":19353},"blog\u002Fblog\u002Fepicmax-partners-with-vuejobs.md","Epicmax Partners with VueJobs to Enhance the Vue.js Ecosystem",{"type":10,"value":19231,"toc":19340},[19232,19235,19239,19242,19253,19256,19260,19263,19295,19299,19319,19325,19329,19332,19335,19338],[13,19233,19234],{},"VueJobs is the leading job portal for Vue.js developers. We are thrilled to announce that Epicmax has officially partnered with VueJobs, marking an exciting milestone in our journey to provide top-tier services and support to the Vue.js community.",[26,19236,19238],{"id":19237},"what-this-partnership-means-for-you","What This Partnership Means for You",[13,19240,19241],{},"As an Epicmax client, you can now take advantage of exclusive benefits through VueJobs. This includes a special discount on our services, designed to enhance your project's success and ensure top-quality outcomes.",[13,19243,19244,19245,19252],{},"Specifically, you can now get 10% off our professional ",[17,19246,19247],{},[4036,19248,19251],{"href":19249,"rel":19250},"https:\u002F\u002Fepicmax.co\u002Fcode-audit",[4040],"Vue.js code audit services"," by using the promo code VUEJOBS.",[13,19254,19255],{},"Our goal is to make it easier for businesses to connect with skilled Vue.js developers and elevate the quality of their codebases through our expert services.",[26,19257,19259],{"id":19258},"elevate-your-code-quality-with-epicmax","Elevate Your Code Quality with Epicmax",[13,19261,19262],{},"At Epicmax, we understand the importance of clean, efficient, and secure code. That's why we offer comprehensive Vue.js code audits to ensure your code meets the highest standards. Our code audit services cover every aspect of your project, including:",[41,19264,19265,19268,19271,19274,19277,19280,19283,19286,19289,19292],{},[44,19266,19267],{},"Technical Debt: Identifying and reducing suboptimal code solutions.",[44,19269,19270],{},"Code Quality & Standards: Ensuring adherence to best practices.",[44,19272,19273],{},"Code Structure: Assessing design and organization for maintainability.",[44,19275,19276],{},"Deployment & Build: Streamlining build processes and deployment pipelines.",[44,19278,19279],{},"Security: Detecting and addressing vulnerabilities.",[44,19281,19282],{},"Testing: Reviewing and improving testing methods.",[44,19284,19285],{},"Performance: Optimizing speed and resource usage.",[44,19287,19288],{},"Error Handling: Implementing robust error management systems.",[44,19290,19291],{},"External Libraries & Plugins: Evaluating the necessity and effectiveness of third-party tools.",[44,19293,19294],{},"Documentation: Ensuring clear and thorough documentation for future maintenance.",[26,19296,19298],{"id":19297},"who-gains-from-our-code-audit","Who Gains from Our Code Audit?",[41,19300,19301,19304,19307,19310,19313,19316],{},[44,19302,19303],{},"Startups Seeking Investment: Perfect your pitch and captivate investors with a polished, investor-ready codebase.",[44,19305,19306],{},"CTOs Focused on Excellence: Assure top-notch quality in your tech stack with our comprehensive code audit.",[44,19308,19309],{},"Managers Focused on Quality: Enhance your product's quality and user satisfaction through detailed code insights.",[44,19311,19312],{},"Business Owners Ensuring Project Health: Stay informed about your project's frontend health without deep-diving into code.",[44,19314,19315],{},"Developers in the Pre-release Stage: Confirm your code's readiness and robustness before hitting the launch button.",[44,19317,19318],{},"Business Owners Pre-Investment: Validate the frontend quality of your potential investment to ensure it's a sound choice.",[13,19320,19321],{},[7602,19322],{"alt":19323,"src":19324},"Epicmax and VueJobs partnership announcement","\u002Fimages\u002Fblog\u002Fepicmax-partners-with-vuejobs\u002F66d5bab4d4ac84a36d32.jpeg",[26,19326,19328],{"id":19327},"why-choose-epicmax","Why Choose Epicmax?",[13,19330,19331],{},"With over 60 global projects under our belt, Epicmax combines deep Vue.js expertise with a commitment to innovation and transparency. Our experience in contributing to open-source projects and our dedication to delivering tailored solutions make us a trusted partner for businesses looking to enhance their Vue.js applications.",[13,19333,19334],{},"Whether you're hiring developers through VueJobs or seeking to improve your code quality, Epicmax is here to support your growth and success in the Vue.js ecosystem.",[13,19336,19337],{},"Ready to take your project to the next level? Contact us today to learn more about our services and how we can help you achieve your goals.",[3114,19339],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":19341},[19342,19343,19344,19345],{"id":19237,"depth":359,"text":19238},{"id":19258,"depth":359,"text":19259},{"id":19297,"depth":359,"text":19298},{"id":19327,"depth":359,"text":19328},"2023-03-27","We are thrilled to announce that Epicmax has officially partnered with VueJobs, the leading job portal for Vue.js developers. This collaboration marks an exciting milestone in our journey to provide top-tier services and support to the Vue.js community.","\u002Fimages\u002Fblog\u002Fepicmax-partners-with-vuejobs\u002Fcard.png",{},"\u002Fblog\u002Fepicmax-partners-with-vuejobs",{"title":19229,"description":19347},"blog\u002Fepicmax-partners-with-vuejobs","0MRXWzDwZx5VGjUDUq5iTa9tbZeSO4txNY1ZCY1xE24",{"id":19355,"title":19356,"author":7,"authorSlug":8,"body":19357,"date":19346,"description":19472,"extension":3154,"faq":8,"featured":3155,"image":19473,"meta":19474,"navigation":472,"path":19475,"readTime":8,"seo":19476,"stem":19477,"tags":8,"__hash__":19478},"blog\u002Fblog\u002Fnetlify-over-railway.md","Comparing Hosting Providers: Why Epicmax Chose Netlify Over Railway",{"type":10,"value":19358,"toc":19468},[19359,19368,19382,19386,19392,19403,19406,19412,19420,19425,19430,19433,19438,19443,19449,19451,19454,19461,19466],[13,19360,19361,19362,19367],{},"At Epicmax, we've recently decided to move our hosting provider from Railway to ",[4036,19363,19366],{"href":19364,"rel":19365},"https:\u002F\u002Fwww.netlify.com\u002F?utm_source=epicmax&utm_campaign=epicmax_blog&utm_medium=epicmax_website&utm_content=epicmax_blogpost",[4040],"Netlify"," as we transition to Netlify's Open Source team plan, which includes, at the time of writing:",[41,19369,19370,19373,19376,19379],{},[44,19371,19372],{},"1TB bandwidth per month",[44,19374,19375],{},"25,000 build minutes per month",[44,19377,19378],{},"2 million edge functions invocations per month",[44,19380,19381],{},"Up to 3 concurrent builds",[26,19383,19385],{"id":19384},"what-are-the-other-benefits-of-netlify-compared-to-railway","What are the other benefits of Netlify compared to Railway?",[13,19387,19388,19391],{},[17,19389,19390],{},"1. Specialization in Jamstack architectures"," (JS, APIs, and Markup), whereas Railway is more suited for container-based stacks.",[13,19393,19394,19397,19398,158],{},[17,19395,19396],{},"2. Comprehensive features",": Forms, Edge functions, Blobs, Image CDN, Cache Control, and analytics ",[4036,19399,19402],{"href":19400,"rel":19401},"https:\u002F\u002Fdocs.netlify.com\u002Fplatform\u002Fprimitives\u002F",[4040],"out of the box",[13,19404,19405],{},"For instance, this is how real use metrics would look like:",[13,19407,19408],{},[7602,19409],{"alt":19410,"src":19411},"Netlify usage metrics dashboard","\u002Fimages\u002Fblog\u002Fnetlify-over-railway\u002F66fffd9346bbb7b46b76.png",[13,19413,19414,19415,2952],{},"Forms look very promising (haven't tried it yet, here's the ",[4036,19416,19419],{"href":19417,"rel":19418},"https:\u002F\u002Fdocs.netlify.com\u002Fforms\u002Fsetup\u002F",[4040],"documentation",[13,19421,19422],{},[7602,19423],{"alt":19424,"src":19411},"Netlify forms setup interface",[13,19426,19427],{},[17,19428,19429],{},"3. Supportive ecosystem and community.",[13,19431,19432],{},"E.g., Netlify has an impressive list of 3rd party integrations:",[13,19434,19435],{},[7602,19436],{"alt":19437,"src":19411},"Netlify third-party integrations directory",[13,19439,19440],{},[17,19441,19442],{},"4. Powerful and well documented CLI tools",[13,19444,19445,19448],{},[17,19446,19447],{},"5. Flexible tuning for your branches and deploy contexts."," Now we can build a preview for any commit, check it out via unique URL and then deploy for production for just a matter of several minutes.",[26,19450,3106],{"id":3105},[13,19452,19453],{},"Switching from Railway to Netlify has allowed us to take advantage of a more specialized platform for our Jamstack-based projects. With features like built-in forms, edge functions, image CDN, and a highly supportive ecosystem, Netlify offers a comprehensive solution tailored to our needs. Its robust CLI tools and flexible deployment options give us the ability to streamline our workflows and focus on delivering quality projects faster. As we continue to grow and evolve, Netlify's scalability and open-source support make it the perfect partner for our future endeavors.",[13,19455,19456,19457,19460],{},"If you're considering a hosting provider optimized for modern web architectures, we highly recommend giving ",[4036,19458,19366],{"href":19364,"rel":19459},[4040]," a try.",[13,19462,19463],{},[5161,19464,19465],{},"Author: Dmitry Raichev, Senior Frontend Developer",[3114,19467],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":19469},[19470,19471],{"id":19384,"depth":359,"text":19385},{"id":3105,"depth":359,"text":3106},"In this article, we explain why Epicmax decided to switch from Railway to Netlify as our hosting provider. We explore the key advantages of Netlify’s Open Source team plan, including features like Jamstack architecture, powerful CLI tools, built-in forms, edge functions, and an extensive list of third-party integrations. Discover how Netlify's comprehensive features better align with our needs and why it stands out as the preferred choice over Railway.","\u002Fimages\u002Fblog\u002Fnetlify-over-railway\u002Fcard.png",{},"\u002Fblog\u002Fnetlify-over-railway",{"title":19356,"description":19472},"blog\u002Fnetlify-over-railway","2Gq1xLNNcu7gv3IS2xNb9BqRk5AnHKBhO41YdlGghjQ",{"id":19480,"title":19481,"author":7,"authorSlug":8,"body":19482,"date":19346,"description":19564,"extension":3154,"faq":8,"featured":3155,"image":19565,"meta":19566,"navigation":472,"path":19567,"readTime":8,"seo":19568,"stem":19569,"tags":8,"__hash__":19570},"blog\u002Fblog\u002Fprimevue.md","Epicmax and PrimeVue: A Partnership to Enhance Frontend Development",{"type":10,"value":19483,"toc":19557},[19484,19487,19493,19496,19500,19503,19507,19510,19514,19517,19520,19538,19542,19545,19549,19552,19555],[13,19485,19486],{},"PrimeVue is a rich set of open source UI Components for Vue. At Epicmax, we pride ourselves on delivering top-notch Vue.js solutions that push the boundaries of frontend development. Today, we're excited to announce a new chapter in our journey—a strategic partnership with PrimeVue, one of the leading UI component libraries for Vue.js.",[13,19488,19489],{},[7602,19490],{"alt":19491,"src":19492},"Epicmax and PrimeVue partnership announcement","\u002Fimages\u002Fblog\u002Fprimevue\u002F66eabd9e7a91f51354b6.jpeg",[13,19494,19495],{},"This collaboration reflects our shared commitment to providing businesses with high-quality, scalable, and performant applications. Together, we aim to support developers and enterprises in optimizing their products, ensuring they meet the highest standards of performance, security, and user experience.",[26,19497,19499],{"id":19498},"why-primevue","Why PrimeVue?",[13,19501,19502],{},"PrimeVue is renowned for its extensive suite of UI components, allowing developers to quickly and efficiently create stunning, feature-rich applications. With a strong focus on ease of use, flexibility, and modern design principles, PrimeVue has become a go-to resource for Vue.js developers. Our partnership with PrimeVue allows us to extend our capabilities and offer more comprehensive services to businesses using Vue.js technologies.",[26,19504,19506],{"id":19505},"epicmax-a-vuejs-expert","Epicmax: A Vue.js Expert",[13,19508,19509],{},"At Epicmax, we are dedicated to delivering top-tier development and consulting services for businesses relying on Vue.js. Our expertise spans across different industries, ensuring that we can tackle complex challenges and create solutions tailored to each client's needs. With over 57 completed projects and a deep understanding of frontend technologies, Epicmax stands out as a leader in the Vue.js ecosystem.",[26,19511,19513],{"id":19512},"what-we-offer","What We Offer",[13,19515,19516],{},"Through this partnership, Epicmax will deliver a wide range of services to complement PrimeVue's offerings. Our goal is to help businesses maximize the potential of their projects by ensuring best practices are followed and performance is optimized.",[13,19518,19519],{},"Here's what we offer as part of our collaboration with PrimeVue:",[41,19521,19522,19525,19528,19535],{},[44,19523,19524],{},"Custom Vue.js Development: Whether you're starting from scratch or enhancing an existing project, our team delivers custom solutions tailored to your business needs. From building complex applications to creating responsive UIs, Epicmax ensures that your product is scalable and maintainable.",[44,19526,19527],{},"Frontend Code Audits: Our code audit service is designed to uncover hidden issues and inefficiencies in your codebase. We thoroughly review your frontend code to ensure it's optimized for performance, security, and scalability. With actionable recommendations, we'll help you get your code in shape for the future.",[44,19529,19530,19531,19534],{},"Consulting Services: Need expert guidance? Our ",[4036,19532,8651],{"href":7217,"rel":19533},[4040]," offer the flexibility to assist you with anything from quick problem-solving to long-term strategic advice. We can help you fine-tune your frontend architecture, improve performance, and stay on track for success.",[44,19536,19537],{},"Team Extension: For businesses looking to scale quickly, our team extension services provide experienced Vue.js developers ready to join your project. Whether you need short-term help or long-term collaboration, we offer skilled professionals who can seamlessly integrate into your team.",[26,19539,19541],{"id":19540},"a-shared-vision-for-open-source","A Shared Vision for Open Source",[13,19543,19544],{},"Both PrimeVue and Epicmax believe in the power of open source. Our partnership aims to not only deliver quality solutions to businesses but also to contribute to the broader Vue.js community. Together, we're excited to work on projects that push the limits of what can be achieved with Vue.js, while also supporting the ongoing growth of open-source technologies.",[26,19546,19548],{"id":19547},"looking-ahead","Looking Ahead",[13,19550,19551],{},"As we embark on this partnership, we're thrilled about the opportunities ahead. By combining PrimeVue's powerful UI components with Epicmax's expertise in Vue.js development and consulting, we are confident that we'll deliver exceptional value to our clients and the broader community.",[13,19553,19554],{},"Stay tuned for exciting updates, special offers, and new services that will help you take your Vue.js projects to the next level. Together, we're building the future of frontend development—one line of code at a time.",[3114,19556],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":19558},[19559,19560,19561,19562,19563],{"id":19498,"depth":359,"text":19499},{"id":19505,"depth":359,"text":19506},{"id":19512,"depth":359,"text":19513},{"id":19540,"depth":359,"text":19541},{"id":19547,"depth":359,"text":19548},"At Epicmax, we pride ourselves on delivering top-notch Vue.js solutions that push the boundaries of frontend development. Today, we’re excited to announce a new chapter in our journey—a strategic partnership with PrimeVue, one of the leading UI component libraries for Vue.js.","\u002Fimages\u002Fblog\u002Fprimevue\u002Fcard.png",{},"\u002Fblog\u002Fprimevue",{"title":19481,"description":19564},"blog\u002Fprimevue","CrM81z5bK16EQonQ_vmer6qo_b8-KJV0SWYX-t007H4",{"id":19572,"title":8718,"author":7,"authorSlug":8,"body":19573,"date":19346,"description":19577,"extension":3154,"faq":8,"featured":3155,"image":20825,"meta":20826,"navigation":472,"path":8717,"readTime":8,"seo":20827,"stem":20828,"tags":8,"__hash__":20829},"blog\u002Fblog\u002Fusing-jsx-with-vue.md",{"type":10,"value":19574,"toc":20821},[19575,19578,19587,19597,19600,19604,19607,19615,19847,19860,19864,19876,19893,19899,20028,20034,20040,20043,20187,20193,20335,20341,20503,20512,20640,20650,20653,20661,20755,20818],[13,19576,19577],{},"With Vue templates being the recommended way to build components by the framework standard, Vue also supports writing component markup using JSX or TSX with Typescript.",[13,19579,19580,19581,19586],{},"Despite common conception, JSX is not exclusive to React, although it was initially developed for and popularized by React as it’s way for writing markup within JavaScript. And even though Vue has it’s own alternative template syntax, ",[4036,19582,19585],{"href":19583,"rel":19584},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Frender-function.html#functional-components",[4040],"official docs"," acknowledge it’s limitations and the benefits that come from having the full programmatic power of JavaScript at your disposal when generation the markup and recognize the need for it for solving complex scenarios.",[13,19588,19589,19590,19596],{},"Teams maintaining large legacy Vue apps often benefit from professional ",[17,19591,19592],{},[4036,19593,19595],{"href":5956,"rel":19594},[4040],"Vue.js migration services"," to adopt JSX strategically, ensuring maintainability and consistency across the codebase.",[13,19598,19599],{},"Let’s have a look on what are the options with using it in Vue.",[26,19601,19603],{"id":19602},"how-jsx-is-possible-in-vue","How JSX is possible in Vue",[13,19605,19606],{},"Vue templates are syntactic sugar for you to right a more readable and DX-friendly way to create virtual DOM nodes (vnodes how Vue calls them) in an HTML-like markup.",[13,19608,19609,19610,158],{},"You can check in the compiled templates that what it translates into under the hood are render functions with calls to function that creates vnodes that is h() function. You can read more on relationship between templates and render function in ",[4036,19611,19614],{"href":19612,"rel":19613},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Frendering-mechanism.html#templates-vs-render-functions",[4040],"Vue docs",[333,19616,19618],{"className":4501,"code":19617,"language":4503,"meta":338,"style":338},"const vnode = (\n  h(\n    'div',\n    {\n      id: 'foo',\n      class: 'bar',\n      \u002F\u002F here may go other props, possibly complex ones\n    },\n    [\n      \u002F* children *\u002F\n    ]\n  )\n);\n\nreturn (\n  \u003Cdiv class=\"container\">\n    \u003Cdiv class=\"header\">\n      Header content\n      {!isCompact && vnode}\n    \u003C\u002Fdiv>\n    \u003Cdiv class=\"footer\">\n      Footer content\n      {isCompact && vnode}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n);\n",[63,19619,19620,19631,19638,19649,19654,19670,19685,19690,19694,19699,19704,19709,19714,19720,19724,19730,19748,19766,19770,19783,19791,19809,19813,19825,19833,19841],{"__ignoreMap":338},[342,19621,19622,19624,19627,19629],{"class":344,"line":345},[342,19623,831],{"class":830},[342,19625,19626],{"class":450}," vnode ",[342,19628,837],{"class":446},[342,19630,16510],{"class":450},[342,19632,19633,19636],{"class":344,"line":359},[342,19634,19635],{"class":362},"  h",[342,19637,14314],{"class":450},[342,19639,19640,19643,19645,19647],{"class":344,"line":469},[342,19641,19642],{"class":446},"    '",[342,19644,1533],{"class":352},[342,19646,3229],{"class":446},[342,19648,527],{"class":446},[342,19650,19651],{"class":344,"line":476},[342,19652,19653],{"class":446},"    {\n",[342,19655,19656,19659,19661,19663,19666,19668],{"class":344,"line":493},[342,19657,19658],{"class":496},"      id",[342,19660,500],{"class":446},[342,19662,4017],{"class":446},[342,19664,19665],{"class":352},"foo",[342,19667,3229],{"class":446},[342,19669,527],{"class":446},[342,19671,19672,19675,19677,19679,19681,19683],{"class":344,"line":506},[342,19673,19674],{"class":496},"      class",[342,19676,500],{"class":446},[342,19678,4017],{"class":446},[342,19680,5740],{"class":352},[342,19682,3229],{"class":446},[342,19684,527],{"class":446},[342,19686,19687],{"class":344,"line":530},[342,19688,19689],{"class":436},"      \u002F\u002F here may go other props, possibly complex ones\n",[342,19691,19692],{"class":344,"line":540},[342,19693,691],{"class":446},[342,19695,19696],{"class":344,"line":571},[342,19697,19698],{"class":450},"    [\n",[342,19700,19701],{"class":344,"line":600},[342,19702,19703],{"class":436},"      \u002F* children *\u002F\n",[342,19705,19706],{"class":344,"line":629},[342,19707,19708],{"class":450},"    ]\n",[342,19710,19711],{"class":344,"line":659},[342,19712,19713],{"class":450},"  )\n",[342,19715,19716,19718],{"class":344,"line":688},[342,19717,294],{"class":450},[342,19719,7819],{"class":446},[342,19721,19722],{"class":344,"line":694},[342,19723,473],{"emptyLinePlaceholder":472},[342,19725,19726,19728],{"class":344,"line":700},[342,19727,2698],{"class":442},[342,19729,16510],{"class":450},[342,19731,19732,19734,19736,19738,19740,19742,19744,19746],{"class":344,"line":1461},[342,19733,1530],{"class":446},[342,19735,1533],{"class":496},[342,19737,1536],{"class":830},[342,19739,837],{"class":446},[342,19741,566],{"class":446},[342,19743,16592],{"class":352},[342,19745,566],{"class":446},[342,19747,1280],{"class":446},[342,19749,19750,19752,19754,19756,19758,19760,19762,19764],{"class":344,"line":1478},[342,19751,1553],{"class":446},[342,19753,1533],{"class":496},[342,19755,1536],{"class":830},[342,19757,837],{"class":446},[342,19759,566],{"class":446},[342,19761,9817],{"class":352},[342,19763,566],{"class":446},[342,19765,1280],{"class":446},[342,19767,19768],{"class":344,"line":1495},[342,19769,16619],{"class":450},[342,19771,19772,19774,19776,19778,19781],{"class":344,"line":1502},[342,19773,16624],{"class":446},[342,19775,16627],{"class":450},[342,19777,4757],{"class":446},[342,19779,19780],{"class":450}," vnode",[342,19782,956],{"class":446},[342,19784,19785,19787,19789],{"class":344,"line":1512},[342,19786,1793],{"class":446},[342,19788,1533],{"class":496},[342,19790,1280],{"class":446},[342,19792,19793,19795,19797,19799,19801,19803,19805,19807],{"class":344,"line":1517},[342,19794,1553],{"class":446},[342,19796,1533],{"class":496},[342,19798,1536],{"class":830},[342,19800,837],{"class":446},[342,19802,566],{"class":446},[342,19804,9855],{"class":352},[342,19806,566],{"class":446},[342,19808,1280],{"class":446},[342,19810,19811],{"class":344,"line":1527},[342,19812,16664],{"class":450},[342,19814,19815,19817,19819,19821,19823],{"class":344,"line":1550},[342,19816,6559],{"class":446},[342,19818,16627],{"class":450},[342,19820,4757],{"class":446},[342,19822,19780],{"class":450},[342,19824,956],{"class":446},[342,19826,19827,19829,19831],{"class":344,"line":1581},[342,19828,1793],{"class":446},[342,19830,1533],{"class":496},[342,19832,1280],{"class":446},[342,19834,19835,19837,19839],{"class":344,"line":1601},[342,19836,1803],{"class":446},[342,19838,1533],{"class":496},[342,19840,1280],{"class":446},[342,19842,19843,19845],{"class":344,"line":1610},[342,19844,294],{"class":450},[342,19846,7819],{"class":446},[13,19848,19849,19850,158,19857,19859],{},"Vue also provides APIs that allow us to skip the template compilation step and directly author render functions via exporting h() function that you can manually call. This is described in docs on ",[17,19851,19852],{},[4036,19853,19856],{"href":19854,"rel":19855},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Frender-function.html#basic-usage",[4040],"Render Functions & JSX",[4375,19858],{},"\nJSX here comes into play here as an alternative syntactic sugar to define render functions, but contrary to Vue templates that are static JSX is written as part of your code directly so can be manipulated with it.",[26,19861,19863],{"id":19862},"setup","Setup",[13,19865,19866,19867,19872,19873,19875],{},"create-vue and Vue CLI both have options for scaffolding projects with pre-configured JSX support. In order to manually enable an existing Vite-based Vue project with JSX you should perform the following configuration. (If you have different environment, see ",[4036,19868,19871],{"href":19869,"rel":19870},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fextras\u002Frender-function.html#jsx-tsx",[4040],"Vue docs on JSX\u002FTSX"," for configuring options)",[4375,19874],{},"\nInstall Vite plugin with command like:",[333,19877,19879],{"className":335,"code":19878,"language":337,"meta":338,"style":338},"npm install @vitejs\u002Fplugin-vue-jsx -D\n",[63,19880,19881],{"__ignoreMap":338},[342,19882,19883,19885,19887,19890],{"class":344,"line":345},[342,19884,393],{"class":348},[342,19886,778],{"class":352},[342,19888,19889],{"class":352}," @vitejs\u002Fplugin-vue-jsx",[342,19891,19892],{"class":352}," -D\n",[13,19894,19895,19896],{},"Next, add plugin to the ",[17,19897,19898],{},"vite.config.ts:",[333,19900,19902],{"className":4501,"code":19901,"language":4503,"meta":338,"style":338},"\u002F\u002F vite.config.js\n\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs\u002Fplugin-vue'\nimport vueJsx from '@vitejs\u002Fplugin-vue-jsx'\n\nexport default defineConfig({\n  plugins: [\n    vue(),\n    vueJsx(),\n  ],\n  \u002F\u002F other configurations\n  ...\n});\n",[63,19903,19904,19909,19913,19931,19945,19961,19965,19977,19985,19994,20003,20010,20015,20020],{"__ignoreMap":338},[342,19905,19906],{"class":344,"line":345},[342,19907,19908],{"class":436},"\u002F\u002F vite.config.js\n",[342,19910,19911],{"class":344,"line":359},[342,19912,473],{"emptyLinePlaceholder":472},[342,19914,19915,19917,19919,19921,19923,19925,19927,19929],{"class":344,"line":469},[342,19916,443],{"class":442},[342,19918,447],{"class":446},[342,19920,451],{"class":450},[342,19922,454],{"class":446},[342,19924,457],{"class":442},[342,19926,4017],{"class":446},[342,19928,6274],{"class":352},[342,19930,5273],{"class":446},[342,19932,19933,19935,19937,19939,19941,19943],{"class":344,"line":476},[342,19934,443],{"class":442},[342,19936,6283],{"class":450},[342,19938,815],{"class":442},[342,19940,4017],{"class":446},[342,19942,6290],{"class":352},[342,19944,5273],{"class":446},[342,19946,19947,19949,19952,19954,19956,19959],{"class":344,"line":493},[342,19948,443],{"class":442},[342,19950,19951],{"class":450}," vueJsx ",[342,19953,815],{"class":442},[342,19955,4017],{"class":446},[342,19957,19958],{"class":352},"@vitejs\u002Fplugin-vue-jsx",[342,19960,5273],{"class":446},[342,19962,19963],{"class":344,"line":506},[342,19964,473],{"emptyLinePlaceholder":472},[342,19966,19967,19969,19971,19973,19975],{"class":344,"line":530},[342,19968,479],{"class":442},[342,19970,482],{"class":442},[342,19972,451],{"class":362},[342,19974,487],{"class":450},[342,19976,490],{"class":446},[342,19978,19979,19981,19983],{"class":344,"line":540},[342,19980,6313],{"class":496},[342,19982,500],{"class":446},[342,19984,6554],{"class":450},[342,19986,19987,19990,19992],{"class":344,"line":571},[342,19988,19989],{"class":362},"    vue",[342,19991,941],{"class":450},[342,19993,527],{"class":446},[342,19995,19996,19999,20001],{"class":344,"line":600},[342,19997,19998],{"class":362},"    vueJsx",[342,20000,941],{"class":450},[342,20002,527],{"class":446},[342,20004,20005,20008],{"class":344,"line":629},[342,20006,20007],{"class":450},"  ]",[342,20009,527],{"class":446},[342,20011,20012],{"class":344,"line":659},[342,20013,20014],{"class":436},"  \u002F\u002F other configurations\n",[342,20016,20017],{"class":344,"line":688},[342,20018,20019],{"class":446},"  ...\n",[342,20021,20022,20024,20026],{"class":344,"line":694},[342,20023,703],{"class":446},[342,20025,294],{"class":450},[342,20027,7819],{"class":446},[13,20029,20030,20031],{},"If you are using Typescript, make changes to ",[17,20032,20033],{},"tsconfig.json :",[333,20035,20038],{"className":20036,"code":20037,"language":374},[372],"\u002F\u002F tsconfig.json\n\n{\n  \"compilerOptions\": {\n    \"jsx\": \"preserve\",\n    \"jsxImportSource\": \"vue\",\n    \u002F\u002F other compiler options\n    ...\n  }\n}\n",[63,20039,20037],{"__ignoreMap":338},[13,20041,20042],{},"Vue docs are not so exhaustive in the section covering the topic and just tell that in Composition API a render function can be returned from setup() hook to be treated as component’s render output. Then they only showcase usage with h() function for explicitly creating vnodes:",[333,20044,20046],{"className":4501,"code":20045,"language":4503,"meta":338,"style":338},"import { ref, h } from 'vue'\n\nexport default {\n  props: {\n    \u002F\u002F define props here\n    ...\n  },\n  setup(props) {\n    const count = ref(1)\n\n    \u002F\u002F return the render function\n    return () => h('div', props.msg + count.value)\n  }\n}\n",[63,20047,20048,20070,20074,20082,20090,20095,20100,20104,20116,20132,20136,20141,20179,20183],{"__ignoreMap":338},[342,20049,20050,20052,20054,20056,20058,20060,20062,20064,20066,20068],{"class":344,"line":345},[342,20051,443],{"class":442},[342,20053,447],{"class":446},[342,20055,2132],{"class":450},[342,20057,1047],{"class":446},[342,20059,15224],{"class":450},[342,20061,454],{"class":446},[342,20063,457],{"class":442},[342,20065,4017],{"class":446},[342,20067,1248],{"class":352},[342,20069,5273],{"class":446},[342,20071,20072],{"class":344,"line":359},[342,20073,473],{"emptyLinePlaceholder":472},[342,20075,20076,20078,20080],{"class":344,"line":469},[342,20077,479],{"class":442},[342,20079,482],{"class":442},[342,20081,503],{"class":446},[342,20083,20084,20086,20088],{"class":344,"line":476},[342,20085,5291],{"class":496},[342,20087,500],{"class":446},[342,20089,503],{"class":446},[342,20091,20092],{"class":344,"line":493},[342,20093,20094],{"class":436},"    \u002F\u002F define props here\n",[342,20096,20097],{"class":344,"line":506},[342,20098,20099],{"class":446},"    ...\n",[342,20101,20102],{"class":344,"line":530},[342,20103,697],{"class":446},[342,20105,20106,20108,20110,20112,20114],{"class":344,"line":540},[342,20107,5642],{"class":496},[342,20109,487],{"class":446},[342,20111,10196],{"class":2819},[342,20113,294],{"class":446},[342,20115,503],{"class":446},[342,20117,20118,20120,20122,20124,20126,20128,20130],{"class":344,"line":571},[342,20119,1158],{"class":830},[342,20121,5002],{"class":450},[342,20123,1107],{"class":446},[342,20125,2132],{"class":362},[342,20127,487],{"class":496},[342,20129,5132],{"class":1208},[342,20131,706],{"class":496},[342,20133,20134],{"class":344,"line":600},[342,20135,473],{"emptyLinePlaceholder":472},[342,20137,20138],{"class":344,"line":629},[342,20139,20140],{"class":436},"    \u002F\u002F return the render function\n",[342,20142,20143,20145,20147,20149,20151,20153,20155,20157,20159,20161,20163,20165,20168,20171,20173,20175,20177],{"class":344,"line":659},[342,20144,2791],{"class":442},[342,20146,1008],{"class":446},[342,20148,1011],{"class":830},[342,20150,15224],{"class":362},[342,20152,487],{"class":496},[342,20154,3229],{"class":446},[342,20156,1533],{"class":352},[342,20158,3229],{"class":446},[342,20160,1047],{"class":446},[342,20162,13025],{"class":450},[342,20164,158],{"class":446},[342,20166,20167],{"class":450},"msg",[342,20169,20170],{"class":446}," +",[342,20172,5002],{"class":450},[342,20174,158],{"class":446},[342,20176,1148],{"class":450},[342,20178,706],{"class":496},[342,20180,20181],{"class":344,"line":688},[342,20182,1229],{"class":446},[342,20184,20185],{"class":344,"line":694},[342,20186,956],{"class":446},[13,20188,20189,20190,20192],{},"The docs then imply that the render function can be written with JSX instead but don’t provide a complete example on this so let’s explore the options more closely.",[4375,20191],{},"\nThe equivalent of this with JSX would be:",[333,20194,20196],{"className":4501,"code":20195,"language":4503,"meta":338,"style":338},"\u002F\u002F Component.jsx\n\nimport { ref } from \"vue\"\n\nexport default {\n  setup(props) {\n    const count = ref(1)\n\n    \u002F\u002F render function with JSX\n    return () => (\n      \u003Cdiv>\n        {props.msg + count.value}\n      \u003C\u002Fdiv>\n    )\n  }\n}\n",[63,20197,20198,20202,20206,20224,20228,20236,20248,20264,20268,20273,20283,20291,20314,20322,20327,20331],{"__ignoreMap":338},[342,20199,20200],{"class":344,"line":345},[342,20201,8847],{"class":436},[342,20203,20204],{"class":344,"line":359},[342,20205,473],{"emptyLinePlaceholder":472},[342,20207,20208,20210,20212,20214,20216,20218,20220,20222],{"class":344,"line":469},[342,20209,443],{"class":442},[342,20211,447],{"class":446},[342,20213,2132],{"class":450},[342,20215,454],{"class":446},[342,20217,457],{"class":442},[342,20219,460],{"class":446},[342,20221,1248],{"class":352},[342,20223,466],{"class":446},[342,20225,20226],{"class":344,"line":476},[342,20227,473],{"emptyLinePlaceholder":472},[342,20229,20230,20232,20234],{"class":344,"line":493},[342,20231,479],{"class":442},[342,20233,482],{"class":442},[342,20235,503],{"class":446},[342,20237,20238,20240,20242,20244,20246],{"class":344,"line":506},[342,20239,5642],{"class":496},[342,20241,487],{"class":446},[342,20243,10196],{"class":2819},[342,20245,294],{"class":446},[342,20247,503],{"class":446},[342,20249,20250,20252,20254,20256,20258,20260,20262],{"class":344,"line":530},[342,20251,1158],{"class":830},[342,20253,5002],{"class":450},[342,20255,1107],{"class":446},[342,20257,2132],{"class":362},[342,20259,487],{"class":496},[342,20261,5132],{"class":1208},[342,20263,706],{"class":496},[342,20265,20266],{"class":344,"line":540},[342,20267,473],{"emptyLinePlaceholder":472},[342,20269,20270],{"class":344,"line":571},[342,20271,20272],{"class":436},"    \u002F\u002F render function with JSX\n",[342,20274,20275,20277,20279,20281],{"class":344,"line":600},[342,20276,2791],{"class":442},[342,20278,1008],{"class":446},[342,20280,1011],{"class":830},[342,20282,16510],{"class":496},[342,20284,20285,20287,20289],{"class":344,"line":629},[342,20286,1604],{"class":446},[342,20288,1533],{"class":496},[342,20290,1280],{"class":446},[342,20292,20293,20296,20298,20300,20303,20306,20308,20310,20312],{"class":344,"line":659},[342,20294,20295],{"class":446},"        {",[342,20297,10196],{"class":450},[342,20299,158],{"class":446},[342,20301,20302],{"class":450},"msg ",[342,20304,20305],{"class":446},"+",[342,20307,5002],{"class":450},[342,20309,158],{"class":446},[342,20311,1148],{"class":450},[342,20313,956],{"class":446},[342,20315,20316,20318,20320],{"class":344,"line":688},[342,20317,1782],{"class":446},[342,20319,1533],{"class":496},[342,20321,1280],{"class":446},[342,20323,20324],{"class":344,"line":694},[342,20325,20326],{"class":496},"    )\n",[342,20328,20329],{"class":344,"line":700},[342,20330,1229],{"class":446},[342,20332,20333],{"class":344,"line":1461},[342,20334,956],{"class":446},[13,20336,20337,20338,20340],{},"The file extension should be .jsx here to enable it.",[4375,20339],{},"\nOr the same in Typescript in .tsx and wrapped with defineComponent:",[333,20342,20344],{"className":4501,"code":20343,"language":4503,"meta":338,"style":338},"\u002F\u002F Component.tsx\n\nimport { defineComponent, provide, inject, Ref, ref } from \"vue\"\n\nexport default defineComponent({\n  setup(props) {\n    const count = ref(1)\n\n    \u002F\u002F render function with JSX\n    return () => (\n      \u003Cdiv>\n        {props.msg + count.value}\n      \u003C\u002Fdiv>\n    )\n  }\n})\n",[63,20345,20346,20351,20355,20391,20395,20407,20419,20435,20439,20443,20453,20461,20481,20489,20493,20497],{"__ignoreMap":338},[342,20347,20348],{"class":344,"line":345},[342,20349,20350],{"class":436},"\u002F\u002F Component.tsx\n",[342,20352,20353],{"class":344,"line":359},[342,20354,473],{"emptyLinePlaceholder":472},[342,20356,20357,20359,20361,20363,20365,20368,20370,20372,20374,20377,20379,20381,20383,20385,20387,20389],{"class":344,"line":469},[342,20358,443],{"class":442},[342,20360,447],{"class":446},[342,20362,5257],{"class":450},[342,20364,1047],{"class":446},[342,20366,20367],{"class":450}," provide",[342,20369,1047],{"class":446},[342,20371,10130],{"class":450},[342,20373,1047],{"class":446},[342,20375,20376],{"class":450}," Ref",[342,20378,1047],{"class":446},[342,20380,2132],{"class":450},[342,20382,454],{"class":446},[342,20384,457],{"class":442},[342,20386,460],{"class":446},[342,20388,1248],{"class":352},[342,20390,466],{"class":446},[342,20392,20393],{"class":344,"line":476},[342,20394,473],{"emptyLinePlaceholder":472},[342,20396,20397,20399,20401,20403,20405],{"class":344,"line":493},[342,20398,479],{"class":442},[342,20400,482],{"class":442},[342,20402,5257],{"class":362},[342,20404,487],{"class":450},[342,20406,490],{"class":446},[342,20408,20409,20411,20413,20415,20417],{"class":344,"line":506},[342,20410,5642],{"class":496},[342,20412,487],{"class":446},[342,20414,10196],{"class":2819},[342,20416,294],{"class":446},[342,20418,503],{"class":446},[342,20420,20421,20423,20425,20427,20429,20431,20433],{"class":344,"line":530},[342,20422,1158],{"class":830},[342,20424,5002],{"class":450},[342,20426,1107],{"class":446},[342,20428,2132],{"class":362},[342,20430,487],{"class":496},[342,20432,5132],{"class":1208},[342,20434,706],{"class":496},[342,20436,20437],{"class":344,"line":540},[342,20438,473],{"emptyLinePlaceholder":472},[342,20440,20441],{"class":344,"line":571},[342,20442,20272],{"class":436},[342,20444,20445,20447,20449,20451],{"class":344,"line":600},[342,20446,2791],{"class":442},[342,20448,1008],{"class":446},[342,20450,1011],{"class":830},[342,20452,16510],{"class":496},[342,20454,20455,20457,20459],{"class":344,"line":629},[342,20456,1604],{"class":446},[342,20458,1533],{"class":496},[342,20460,1280],{"class":446},[342,20462,20463,20465,20467,20469,20471,20473,20475,20477,20479],{"class":344,"line":659},[342,20464,20295],{"class":446},[342,20466,10196],{"class":450},[342,20468,158],{"class":446},[342,20470,20302],{"class":450},[342,20472,20305],{"class":446},[342,20474,5002],{"class":450},[342,20476,158],{"class":446},[342,20478,1148],{"class":450},[342,20480,956],{"class":446},[342,20482,20483,20485,20487],{"class":344,"line":688},[342,20484,1782],{"class":446},[342,20486,1533],{"class":496},[342,20488,1280],{"class":446},[342,20490,20491],{"class":344,"line":694},[342,20492,20326],{"class":496},[342,20494,20495],{"class":344,"line":700},[342,20496,1229],{"class":446},[342,20498,20499,20501],{"class":344,"line":1461},[342,20500,703],{"class":446},[342,20502,706],{"class":450},[13,20504,20505,20506,20508,20509],{},"The former is what most articles show as examples and this may suggest that in order to use JSX in Vue one should ditch SFC and instead declare Options API style component mixing it up with setup() hook if you want to stick to the Composition API. But this is not the case.",[4375,20507],{},"\nYou can use to JSX or TSX inside Vue SFC with specifying lang=\"jsx\" or lang=\"tsx\" on ",[1262,20510,20511],{}," tag:",[333,20513,20515],{"className":4501,"code":20514,"language":4503,"meta":338,"style":338},"\u002F\u002F Component.vue\n\n\u003Cscript lang=\"tsx\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  setup(props) {\n    return () => (\n      \u003Cdiv>\n        Hello, World\n      \u003C\u002Fdiv>\n    )\n  }\n})\n\u003C\u002Fscript>\n",[63,20516,20517,20522,20526,20545,20560,20564,20571,20580,20590,20598,20608,20618,20622,20626,20632],{"__ignoreMap":338},[342,20518,20519],{"class":344,"line":345},[342,20520,20521],{"class":436},"\u002F\u002F Component.vue\n",[342,20523,20524],{"class":344,"line":359},[342,20525,473],{"emptyLinePlaceholder":472},[342,20527,20528,20530,20532,20534,20536,20538,20541,20543],{"class":344,"line":469},[342,20529,1023],{"class":446},[342,20531,1262],{"class":496},[342,20533,1268],{"class":830},[342,20535,837],{"class":446},[342,20537,566],{"class":446},[342,20539,20540],{"class":352},"tsx",[342,20542,566],{"class":446},[342,20544,1280],{"class":446},[342,20546,20547,20550,20552,20555,20557],{"class":344,"line":476},[342,20548,20549],{"class":450},"import ",[342,20551,2086],{"class":446},[342,20553,20554],{"class":450}," defineComponent ",[342,20556,703],{"class":446},[342,20558,20559],{"class":450}," from 'vue'\n",[342,20561,20562],{"class":344,"line":493},[342,20563,473],{"emptyLinePlaceholder":472},[342,20565,20566,20569],{"class":344,"line":506},[342,20567,20568],{"class":450},"export default defineComponent(",[342,20570,490],{"class":446},[342,20572,20573,20575,20578],{"class":344,"line":530},[342,20574,5642],{"class":362},[342,20576,20577],{"class":450},"(props) ",[342,20579,490],{"class":446},[342,20581,20582,20584,20586,20588],{"class":344,"line":540},[342,20583,2791],{"class":496},[342,20585,1008],{"class":446},[342,20587,1011],{"class":830},[342,20589,16510],{"class":450},[342,20591,20592,20594,20596],{"class":344,"line":571},[342,20593,1604],{"class":446},[342,20595,1533],{"class":348},[342,20597,1280],{"class":446},[342,20599,20600,20603,20605],{"class":344,"line":600},[342,20601,20602],{"class":348},"        Hello",[342,20604,1047],{"class":446},[342,20606,20607],{"class":348}," World\n",[342,20609,20610,20612,20614,20616],{"class":344,"line":629},[342,20611,1604],{"class":446},[342,20613,6578],{"class":450},[342,20615,1533],{"class":348},[342,20617,1280],{"class":446},[342,20619,20620],{"class":344,"line":659},[342,20621,20326],{"class":450},[342,20623,20624],{"class":344,"line":688},[342,20625,1229],{"class":446},[342,20627,20628,20630],{"class":344,"line":694},[342,20629,703],{"class":446},[342,20631,706],{"class":450},[342,20633,20634,20636,20638],{"class":344,"line":700},[342,20635,1505],{"class":446},[342,20637,1262],{"class":496},[342,20639,1280],{"class":446},[13,20641,20642,20643,20646,20647],{},"You can even use JSX in ",[1262,20644,20645],{"setup":338}," with lang=\"tsx\" or lang=\"tsx\". In this scenario you can declare ** Functional Components","** in ",[1262,20648,20649],{"setup":338}," and use them in \u003Ctemplate> to render part of the markup**.**",[13,20651,20652],{},"Functional components are a lightweight form of component that must be pure functions and can’t have state or side-effect (pretty much like Function components in React before React hooks).",[13,20654,20655,20656,20660],{},"You can declare a single ",[4036,20657,20659],{"href":19583,"rel":20658},[4040],"** Functional Components","** to produce your entire SFC markup like we did in the previous examples with component definition, you just need to call it not as a function but instantiate as a component:",[333,20662,20664],{"className":1246,"code":20663,"language":1248,"meta":338,"style":338},"\u002F\u002F Component.vue\n\n\u003Cscript lang=\"tsx\" setup>\nconst RenderComponent = () => (\n  \u003Cdiv>\n    Hello, World\n  \u003C\u002Fdiv>\n);\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CRenderComponent \u002F>\n\u003C\u002Ftemplate>\n",[63,20665,20666,20670,20674,20694,20699,20704,20709,20714,20718,20726,20730,20738,20747],{"__ignoreMap":338},[342,20667,20668],{"class":344,"line":345},[342,20669,20521],{"class":450},[342,20671,20672],{"class":344,"line":359},[342,20673,473],{"emptyLinePlaceholder":472},[342,20675,20676,20678,20680,20682,20684,20686,20688,20690,20692],{"class":344,"line":469},[342,20677,1023],{"class":446},[342,20679,1262],{"class":496},[342,20681,1268],{"class":830},[342,20683,837],{"class":446},[342,20685,566],{"class":446},[342,20687,20540],{"class":352},[342,20689,566],{"class":446},[342,20691,1265],{"class":830},[342,20693,1280],{"class":446},[342,20695,20696],{"class":344,"line":476},[342,20697,20698],{"class":450},"const RenderComponent = () => (\n",[342,20700,20701],{"class":344,"line":493},[342,20702,20703],{"class":450},"  \u003Cdiv>\n",[342,20705,20706],{"class":344,"line":506},[342,20707,20708],{"class":450},"    Hello, World\n",[342,20710,20711],{"class":344,"line":530},[342,20712,20713],{"class":450},"  \u003C\u002Fdiv>\n",[342,20715,20716],{"class":344,"line":540},[342,20717,8194],{"class":450},[342,20719,20720,20722,20724],{"class":344,"line":571},[342,20721,1505],{"class":446},[342,20723,1262],{"class":496},[342,20725,1280],{"class":446},[342,20727,20728],{"class":344,"line":600},[342,20729,473],{"emptyLinePlaceholder":472},[342,20731,20732,20734,20736],{"class":344,"line":629},[342,20733,1023],{"class":446},[342,20735,1522],{"class":496},[342,20737,1280],{"class":446},[342,20739,20740,20742,20745],{"class":344,"line":659},[342,20741,1530],{"class":446},[342,20743,20744],{"class":496},"RenderComponent",[342,20746,7779],{"class":446},[342,20748,20749,20751,20753],{"class":344,"line":688},[342,20750,1505],{"class":446},[342,20752,1522],{"class":496},[342,20754,1280],{"class":446},[13,20756,20757,20758],{},"But perhaps the most potent is that you can still use ",[1522,20759,20760,20761,20765,20766],{}," for most of your template like you normally would in Vue and occasionally embed a ",[4036,20762,20764],{"href":19583,"rel":20763},[4040],"** Functional Component","** to produce some parts of the markup that can’t be easily expressed with the template syntax.",[13,20767,20768,20769,16957,20774,20776,20777,20780,20781],{},"One of the notable limitations is inability to define a template part and reuse it across template, for example when some block needs to appear in different places in the markup depending on some condition (although there is now an alternative pure-template solution for this problem that is explored in ",[4036,20770,20773],{"href":20771,"rel":20772},"https:\u002F\u002Fwww.notion.so\u002FFixing-Vue-SFC-templates-for-React-developers-e449f064f64a4b6c9d3d303e107a71dc?pvs=21",[4040],"another article",[4375,20775],{},"\nThis part can be extracted into a ",[4036,20778,20764],{"href":19583,"rel":20779},[4040],"** and reused across ",[1522,20782,20783,20784,20791,20800,20806,20811,20816],{}," :\n",[333,20785,20789],{"className":20786,"code":20788,"language":1248,"meta":338},[20787],"language-vue","\u002F\u002F Component.vue\n\n\u003Cscript lang=\"tsx\" setup>\nimport { ref } from 'vue';\n\nconst ReusedPart = () => (\n  \u003Cdiv>\n    Hello, World\n  \u003C\u002Fdiv>\n);\n\nconst someCondition = ref(true);\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CReusedPart v-if=\"someCondition\" \u002F>\n  \u003C\u002Fdiv>\n  \u003Cdiv>\n    \u003CReusedPart v-if=\"!someCondition\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[63,20790,20788],{"__ignoreMap":338},[13,20792,20793,20799],{},[17,20794,20795],{},[4036,20796,20798],{"href":19583,"rel":20797},[4040],"Functional Component"," can be passed props, just like you would pass them to normal components:",[333,20801,20804],{"className":20802,"code":20803,"language":1248,"meta":338},[20787],"\u002F\u002F Component.vue\n\n\u003Cscript lang=\"tsx\" setup>\ninterface Props {\n  label: string;\n}\n\nconst SubComponentA = (props: Props) => (\n  \u003Cdiv>\n    \u003Clabel>{props.label}\u003C\u002Flabel>\n    \u003Cinput type=\"number\" \u002F>\n  \u003C\u002Fdiv>\n);\n\nconst SubComponentB = (props: Props) => (\n  \u003Cdiv>\n    \u003Clabel>{props.label}\u003C\u002Flabel>\n    \u003Ctextarea \u002F>\n  \u003C\u002Fdiv>\n);\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CSubComponentA label=\"Label 1\" \u002F>\n  \u003CSubComponentB label=\"Label 2\" \u002F>\n  \u003CSubComponentA label=\"Label 3\" \u002F>\n  \u003CSubComponentB label=\"Label 4\" \u002F>\n\u003C\u002Ftemplate>\n",[63,20805,20803],{"__ignoreMap":338},[13,20807,20808],{},[17,20809,20810],{},"Hope that this was a usefull tour on how to incorporate JSX in your Vue app to help you write your templates more easily.",[13,20812,20813],{},[5161,20814,20815],{},"Autor: Alexandra Petrova, Lead Frontend developer",[3114,20817],{"label":3116,"to":3117},[3119,20819,20820],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":338,"searchDepth":469,"depth":469,"links":20822},[20823,20824],{"id":19602,"depth":359,"text":19603},{"id":19862,"depth":359,"text":19863},"\u002Fimages\u002Fblog\u002Fusing-jsx-with-vue\u002Fcard.png",{},{"title":8718,"description":19577},"blog\u002Fusing-jsx-with-vue","3CbtAbKE3WYQN1Rno-bh8ZLGFzCRILTCvXCdNM6PXZU",{"id":20831,"title":20832,"author":7,"authorSlug":8,"body":20833,"date":21051,"description":21052,"extension":3154,"faq":8,"featured":3155,"image":21053,"meta":21054,"navigation":472,"path":21055,"readTime":8,"seo":21056,"stem":21057,"tags":8,"__hash__":21058},"blog\u002Fblog\u002Fvuestic-ui-1-10.md","Vuestic UI 1.10 Release: New Features and Optimizations",{"type":10,"value":20834,"toc":21039},[20835,20841,20844,20848,20852,20855,20861,20864,20944,20947,20951,20954,20957,20961,20964,20968,20972,20975,20983,20986,20991,20995,21002,21005,21016,21020,21023,21026,21034,21036],[13,20836,20837,20838],{},"Today, we've released a new version of Vuestic UI. You can find the repository here: ",[4036,20839,18783],{"href":18783,"rel":20840},[4040],[13,20842,20843],{},"⭐️⭐️⭐️ If you're using Vuestic, please give us a star and share the news on your social media so more people can learn about Vuestic!",[26,20845,20847],{"id":20846},"new-features-in-vuestic-110","New Features in Vuestic 1.10",[328,20849,20851],{"id":20850},"input-mask-customizable-flexible","Input Mask: Customizable & Flexible",[13,20853,20854],{},"With Vuestic 1.10, we've introduced a new composable for building custom input masks. The useInputMask composable is a versatile solution that works with any HTML input element or vue component, including VaInput, VaDateInput, and more. The mask is based on regex syntax, allowing you to easily define the format you need.",[13,20856,20857],{},[7602,20858],{"alt":20859,"src":20860},"Vuestic UI 1.10 input mask example","\u002Fimages\u002Fblog\u002Fvuestic-ui-1-10\u002F66cc72c80f6b41b86ea9.png",[13,20862,20863],{},"Here's an example:",[333,20865,20867],{"className":4501,"code":20866,"language":4503,"meta":338,"style":338},"const phone = createRegexMask(\u002F(+\\d{1,3})? \\d\\d\\d-\\d\\d-\\d\\d\u002F)\n\u002F\u002F This mask will accept two phone formats. In traditional method it is:\n\u002F\u002F +### ###-##-## or ###-##-## where # is a number\n\u002F\u002F Notice that country code can be #, ## or ### and it is not required because of ?\n\nconst myPhoneInput = ref()\nuseInputMask(phone, myPhoneInput)\n",[63,20868,20869,20899,20904,20909,20914,20918,20931],{"__ignoreMap":338},[342,20870,20871,20873,20876,20878,20881,20883,20886,20889,20892,20895,20897],{"class":344,"line":345},[342,20872,831],{"class":830},[342,20874,20875],{"class":450}," phone ",[342,20877,837],{"class":446},[342,20879,20880],{"class":362}," createRegexMask",[342,20882,487],{"class":450},[342,20884,20885],{"class":446},"\u002F(+",[342,20887,20888],{"class":352},"\\d",[342,20890,20891],{"class":446},"{1,3})?",[342,20893,20894],{"class":352}," \\d\\d\\d-\\d\\d-\\d\\d",[342,20896,6578],{"class":446},[342,20898,706],{"class":450},[342,20900,20901],{"class":344,"line":359},[342,20902,20903],{"class":436},"\u002F\u002F This mask will accept two phone formats. In traditional method it is:\n",[342,20905,20906],{"class":344,"line":469},[342,20907,20908],{"class":436},"\u002F\u002F +### ###-##-## or ###-##-## where # is a number\n",[342,20910,20911],{"class":344,"line":476},[342,20912,20913],{"class":436},"\u002F\u002F Notice that country code can be #, ## or ### and it is not required because of ?\n",[342,20915,20916],{"class":344,"line":493},[342,20917,473],{"emptyLinePlaceholder":472},[342,20919,20920,20922,20925,20927,20929],{"class":344,"line":506},[342,20921,831],{"class":830},[342,20923,20924],{"class":450}," myPhoneInput ",[342,20926,837],{"class":446},[342,20928,2132],{"class":362},[342,20930,1112],{"class":450},[342,20932,20933,20936,20939,20941],{"class":344,"line":530},[342,20934,20935],{"class":362},"useInputMask",[342,20937,20938],{"class":450},"(phone",[342,20940,1047],{"class":446},[342,20942,20943],{"class":450}," myPhoneInput)\n",[13,20945,20946],{},"We've also included built-in mask presets, such as createDateMask and createNumeralMask, which you can explore further in our updated documentation.",[328,20948,20950],{"id":20949},"sticky-table-headers-better-ux-for-large-tables","Sticky Table Headers: Better UX for Large Tables",[13,20952,20953],{},"Handling large data tables just got easier with the introduction of sticky table headers. The useStickyTableHeader composable creates a copy of the thead and keeps it visible as a fixed element when you scroll through long tables. This feature works with any HTML table or vue component, including VaDataTable.",[13,20955,20956],{},"All you need to do is pass your table element to useStickyTableHeader, and you're good to go!",[328,20958,20960],{"id":20959},"sticky-scrollbars-improve-visibility-and-ux","Sticky Scrollbars: Improve Visibility and UX",[13,20962,20963],{},"When working with large scroll containers or tables, it's easy to lose important columns or sections as users scroll. Vuestic 1.10 introduces the \"sticky scrollbar\" feature, which ensures that scrollbars remain visible even when they're outside the window bounds. This improvement helps users stay oriented and improves overall user experience.",[26,20965,20967],{"id":20966},"performance-optimization","Performance & Optimization",[328,20969,20971],{"id":20970},"bundle-size-reductions","Bundle Size Reductions",[13,20973,20974],{},"Performance matters, and we've made significant strides in reducing the bundle size in Vuestic 1.10. By reducing external dependencies, we've achieved impressive reductions:",[41,20976,20977,20980],{},[44,20978,20979],{},"Core Build + Button: Reduced by 50% (from 173kb to 86kb).",[44,20981,20982],{},"Full Build: Reduced by 14% (from 1084kb to 946kb).",[13,20984,20985],{},"This means your applications will load faster and run more efficiently, using only the features you need.",[13,20987,20988],{},[7602,20989],{"alt":20990,"src":20860},"Vuestic UI 1.10 bundle size comparison",[26,20992,20994],{"id":20993},"introducing-the-vuestic-compiler-performance-developer-experience","Introducing the Vuestic Compiler: Performance & Developer Experience",[13,20996,20997,20998,21001],{},"As we continue to add more features and composables to Vuestic, we recognize the need to optimize the development process and prevent runtime overhead. That's why we're introducing the ",[17,20999,21000],{},"Vuestic compiler",". This new tool is designed to enhance performance during build time, reducing complexity without sacrificing the developer experience.",[13,21003,21004],{},"Here's additional features you can play with right now:",[41,21006,21007,21010,21013],{},[44,21008,21009],{},"Automatic Configuration: The compiler detects your vuestic.config.ts and sets up createVuestic in your main.ts. (Manual configuration is also supported, of course.)",[44,21011,21012],{},"CSS Layer Integration: If you're using Tailwind or UnoCSS, the compiler ensures that Vuestic styles are less important than your CSS framework. This solves a common issue where multiple stylesheets clash in production, causing layout inconsistencies.",[44,21014,21015],{},"Visual DevTools: Vuestic now includes new devtools that allow you to visually edit your application without needing to write a single line of code.",[26,21017,21019],{"id":21018},"looking-ahead-more-compiler-optimizations","Looking Ahead: More Compiler Optimizations",[13,21021,21022],{},"In the future, we plan to move more components into the Vuestic compiler, further optimizing performance without negatively impacting the developer experience. Stay tuned for updates as we continue refining and improving Vuestic UI!",[13,21024,21025],{},"With these new features and optimizations, Vuestic 1.10 offers better flexibility, performance, and usability, whether you're working with large tables, complex input fields, or modern CSS frameworks. We're excited to see what you build with these new tools and can't wait to continue improving your developer experience.",[13,21027,21028,21029,21033],{},"For more details, check out our updated ",[4036,21030,19419],{"href":21031,"rel":21032},"https:\u002F\u002Fvuestic.dev\u002F",[4040]," and get started with Vuestic 1.10 today!",[3114,21035],{"label":3116,"to":3117},[3119,21037,21038],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":338,"searchDepth":469,"depth":469,"links":21040},[21041,21046,21049,21050],{"id":20846,"depth":359,"text":20847,"children":21042},[21043,21044,21045],{"id":20850,"depth":469,"text":20851},{"id":20949,"depth":469,"text":20950},{"id":20959,"depth":469,"text":20960},{"id":20966,"depth":359,"text":20967,"children":21047},[21048],{"id":20970,"depth":469,"text":20971},{"id":20993,"depth":359,"text":20994},{"id":21018,"depth":359,"text":21019},"2023-03-26","We're excited to announce the release of Vuestic UI 1.10! This update brings a range of new features, optimizations, and improvements to enhance your development workflow and deliver better performance for your applications. In this blog post, we'll dive into the key highlights of this release, including input masks, sticky table headers, sticky scrollbars, bundle size reductions, and the introduction of the Vuestic compiler. Let's take a closer look at each of these exciting updates.","\u002Fimages\u002Fblog\u002Fvuestic-ui-1-10\u002Fcard.png",{},"\u002Fblog\u002Fvuestic-ui-1-10",{"title":20832,"description":21052},"blog\u002Fvuestic-ui-1-10","PecAxeurw1hYIj9Dq9iber8ab3sRJ9OuRcrepm3BQ8I",{"id":21060,"title":21061,"author":7,"authorSlug":8,"body":21062,"date":21125,"description":21126,"extension":3154,"faq":8,"featured":3155,"image":21127,"meta":21128,"navigation":472,"path":21129,"readTime":8,"seo":21130,"stem":21131,"tags":8,"__hash__":21132},"blog\u002Fblog\u002Fopen-source-business-2024.md","Starting a Business from Open Source in 2024: How to Make Open Source Profitable",{"type":10,"value":21063,"toc":21122},[21064,21072,21075,21078,21081,21099,21102,21105,21109,21112,21115,21120],[13,21065,21066,21067,21071],{},"We at Epicmax have extensive experience with open-source development, and today I want to look back on how it all started and explore whether starting a company today with an open-source project is a good idea. Epicmax launched its first open-source project, ",[4036,21068,11469],{"href":21069,"rel":21070},"https:\u002F\u002Fadmin.vuestic.dev\u002F?=epicmax-blog-post-15-05",[4040]," 7 years ago, and it quickly became the 4th most popular on GitHub in just 23 hours. This success was a key moment for the company.",[13,21073,21074],{},"You all know that finding new customers is hard for small outsourcing businesses, especially when you need to find your FIRST client. At this hard moment, some companies start with sending numerous proposals on Upwork or LinkedIn, hoping to find good clients.",[13,21076,21077],{},"Others simply get any job they can, trying to secure any contract at all, and then quickly burnout and lose their positioning. Some even hire a sales team to make many cold calls and send emails every day, but turning these efforts into real customers is challenging when you don't have cases and reputation. Nobody wants to be the first and invest own time and money to provide you with a case and experience.",[13,21079,21080],{},"In 2017, Epicmax team decided to try a different way.\nIt was risky and challenging, but also exciting - they started by building a non-commercial Admin template and putting it on GitHub.",[13,21082,21083,21084,21089,21090,21094,21095,21098],{},"It’s been over 7 years now. Vuestic Admin has received more than 10 000 stars on ",[4036,21085,21088],{"href":21086,"rel":21087},"https:\u002F\u002Fgithub.com\u002Fepicmaxco\u002Fvuestic-admin",[4040],"GitHub"," ⭐⭐⭐\nIt led to more successful open-source products and significant growth for the company. After realizing that they could leverage own previous experience to create something cool, they developed ",[4036,21091,10747],{"href":21092,"rel":21093},"https:\u002F\u002Fui.vuestic.dev\u002F?=epicmax-blog-post-15-05",[4040]," - an open-source Vue.js UI Framework. It currently has only 3 300 stars on ",[4036,21096,21088],{"href":18783,"rel":21097},[4040],", but let's see what happens next.",[13,21100,21101],{},"We have great and beautiful competitors in the world of Vue.js Frameworks, but we still have something special under the hood that I am sure you will absolutely love!",[13,21103,21104],{},"Having several open-source products now helps us to connect and collaborate with interesting projects, and to join the best conferences in the field (they're happy when our CTO gives a talk or when we come just for fun and discussions).",[26,21106,21108],{"id":21107},"so-what-about-our-sales-process-now","So, what about our sales process now?",[13,21110,21111],{},"We still have to look for new clients and try new ways to reach people who need our services, but some of them come to us with exciting offers after discovering our open-source work.",[13,21113,21114],{},"And for me, as a part of Epicmax, it's a great honor to know that we're not all about money.\nWe have something more, and I am thankful that I've met a lot of amazing people working at Epicmax. And one more benefit for me - I always have a place where I can put my ideas. If I were to start a new business now, I would definitely begin with an open-source project, but for 2025 it must not be called Vuestic UI, but Vuestic AI. 🤣",[13,21116,21117],{},[5161,21118,21119],{},"Anastasiia Zvenigorodskaia, COO and Community manager",[3114,21121],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":21123},[21124],{"id":21107,"depth":359,"text":21108},"2022-02-26","Can an open-source project become a successful business in 2024? Discover how Epicmax grew from Vuestic Admin to Vuestic UI, a Vue.js open-source framework, and how open-source development helps attract clients, build reputation, and drive growth.","\u002Fimages\u002Fblog\u002Fopen-source-business-2024\u002Fcard.png",{},"\u002Fblog\u002Fopen-source-business-2024",{"title":21061,"description":21126},"blog\u002Fopen-source-business-2024","Sxqm_Ao6nljxGTUuvE35H7f_yTcdp-16QqrCSu8rW9A",{"id":21134,"title":21135,"author":7,"authorSlug":8,"body":21136,"date":21192,"description":21193,"extension":3154,"faq":8,"featured":3155,"image":21194,"meta":21195,"navigation":472,"path":21196,"readTime":8,"seo":21197,"stem":21198,"tags":8,"__hash__":21199},"blog\u002Fblog\u002Fvuejsamsterdam.md","VueJsAmsterdam 2024 | Storybook & Playwright Testing Insights from Epicmax",{"type":10,"value":21137,"toc":21187},[21138,21141,21144,21156,21160,21164,21167,21173,21176,21182,21185],[13,21139,21140],{},"Our CTO, Yauheni Prakopchyk, took the stage at Europe's leading Vue.js conference, VueJsAmsterdam, with an insightful talk titled 'Cost-Effective Testing with Storybook and Playwright in Vue Applications'.",[13,21142,21143],{},"Here's a sneak peek into what his presentation covered:",[13,21145,21146,21147,21149,21150,21152,21153,21155],{},"🙈 The Story of Crafting My Own “Storybook”",[4375,21148],{},"\n🤓 Story-Driven Development Example",[4375,21151],{},"\n📈 Maximizing Storybook Value to Cost ratio",[4375,21154],{},"\n🫰 Bonus Points: Team Interaction and Aligning with Business Goals",[26,21157,21159],{"id":21158},"key-takeaways","Key Takeaways",[328,21161,21163],{"id":21162},"mastering-efficient-story-usage","Mastering Efficient Story Usage",[13,21165,21166],{},"Feeling the urge to test your Vue applications? Storybook might just be the cost-effective solution you're seeking. Start by setting up Storybook with interaction and visual tests, then gradually add a story for each component. Prioritize complex modular components, followed by simpler ones and CSS utils. Reserve stories for pages and widgets only if absolutely necessary.",[13,21168,21169],{},[7602,21170],{"alt":21171,"src":21172},"Yauheni Prakopchyk presenting on cost-effective Vue.js testing at VueJsAmsterdam 2024","\u002Fimages\u002Fblog\u002Fvuejsamsterdam\u002F662b9e34700704698776.jpg",[13,21174,21175],{},"Curious if this testing approach suits your project?",[13,21177,21178],{},[4036,21179,21181],{"href":3117,"rel":21180},[4040],"Schedule a consultation with our CTO.",[13,21183,21184],{},"A big shoutout to the organizers for orchestrating such an exceptional event! These conferences aren't merely about showcasing skills; they're opportunities to forge new connections and have a blast. We're already counting down the days until next year's edition!",[3114,21186],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":21188},[21189],{"id":21158,"depth":359,"text":21159,"children":21190},[21191],{"id":21162,"depth":469,"text":21163},"2022-02-25","Epicmax CTO Yauheni Prakopchyk shared insights on cost-effective testing with Storybook & Playwright at VueJsAmsterdam 2024. Learn how to streamline Vue app testing, improve team collaboration, and align development with business goals. Explore key takeaways and book a consultation!","\u002Fimages\u002Fblog\u002Fvuejsamsterdam\u002Fcard.jpg",{},"\u002Fblog\u002Fvuejsamsterdam",{"title":21135,"description":21193},"blog\u002Fvuejsamsterdam","vWYbDVTAXR37Ir8_VVpxPCv0Y6cJ_De8xwad71J2YDY",{"id":21201,"title":21202,"author":7,"authorSlug":8,"body":21203,"date":21192,"description":21350,"extension":3154,"faq":8,"featured":3155,"image":21351,"meta":21352,"navigation":472,"path":21353,"readTime":8,"seo":21354,"stem":21355,"tags":8,"__hash__":21356},"blog\u002Fblog\u002Fvuestic-admin-3-0.md","Vuestic Admin 3.0 | Vue 3 Admin Dashboard Built with Vite, Pinia & Tailwind CSS",{"type":10,"value":21204,"toc":21342},[21205,21208,21217,21221,21224,21230,21234,21242,21245,21259,21263,21266,21292,21296,21299,21303,21306,21317,21321,21324,21332,21335,21340],[13,21206,21207],{},"Free Admin Template utilizing Vue 3, Vite, Pinia, and Tailwind CSS. Tailored to speed up development, Vuestic Admin integrates the latest web technologies for a seamless experience.",[13,21209,21210,21211,21216],{},"Hi there! We've just rolled out ",[4036,21212,21215],{"href":21213,"rel":21214},"https:\u002F\u002Fgithub.com\u002Fepicmaxco\u002Fvuestic-admin?ref=epicmax-blog-post",[4040],"Vuestic Admin 3.0",", built with Vue 3, Vite, Pinia, and Tailwind CSS, aiming to make your coding life a bit easier.",[26,21218,21220],{"id":21219},"its-more-than-just-pretty-pages","It's More Than Just Pretty Pages!",[13,21222,21223],{},"We decided not to stop at just making templates. Instead, we're giving you pages that you'll actually use - like a dashboard you can tweak, a settings page that makes sense, and even a login page that's ready to go. We thought about what you really need for your projects and put it all in Vuestic Admin.",[13,21225,21226],{},[7602,21227],{"alt":21228,"src":21229},"Vuestic Admin 3.0 dashboard preview","\u002Fimages\u002Fblog\u002Fvuestic-admin-3-0\u002Fvuestic_admin__2025-.png",[26,21231,21233],{"id":21232},"built-with-vuestic-ui-by-a-community-for-the-community","Built With Vuestic UI, By a Community for the Community",[13,21235,21236,21237,21241],{},"We use UI components from ",[4036,21238,10747],{"href":21239,"rel":21240},"https:\u002F\u002Fui.vuestic.dev\u002F?ref=epicmax-blog-post",[4040],", which helps with updates and ensures consistent design.",[13,21243,21244],{},"Vuestic Admin is more than just tools and codes; it's about the people behind and around it. Whether you're just starting out or you've been coding for years, there's something here for you:",[41,21246,21247,21250,21253,21256],{},[44,21248,21249],{},"For Beginners: Get your hands dirty with actual code and learn with us.",[44,21251,21252],{},"For the Curious: Dive into new experiments and maybe even join us at some tech events.",[44,21254,21255],{},"For Open Source Fans: Got an idea? Let's collaborate and see where we can take it.",[44,21257,21258],{},"For Business Minds: Need a custom solution or a code checkup? We're here for that too ;)",[26,21260,21262],{"id":21261},"core-features","Core Features",[13,21264,21265],{},"Here's what you'll find in Vuestic Admin 3.0:",[41,21267,21268,21271,21274,21277,21280,21283,21286,21289],{},[44,21269,21270],{},"Quick to Start: Vue 3 and Vite make setup a breeze.",[44,21272,21273],{},"Looks Good on Any Screen: Responsive design is a given.",[44,21275,21276],{},"Dark Mode: Because it's cool (and easier on the eyes).",[44,21278,21279],{},"Customize It: Make it fit your style and needs.",[44,21281,21282],{},"For Everyone: We kept accessibility in mind.",[44,21284,21285],{},"Speak Any Language: i18n ready for your global audience.",[44,21287,21288],{},"Learn Something New: Great for picking up Vue.js.",[44,21290,21291],{},"Got Questions? We're here to help.",[26,21293,21295],{"id":21294},"a-little-background","A Little Background",[13,21297,21298],{},"Vuestic started small in 2017 as a side project because we love Vue.js and wanted to give back to the community. Fast forward to now, and it's grown thanks to the team and users like you. Vuestic Admin 3.0, built on the Vuestic UI framework, is our latest effort to make web development smoother and more fun.",[26,21300,21302],{"id":21301},"the-team","The Team",[13,21304,21305],{},"The team behind Vuestic is in ❤️ with Vue.js and Open Source. We're just a bunch of folks who love coding and Vue.js. We believe in making tools that we'd want to use too.",[13,21307,21308,21309,21312,21313,158],{},"Vuestic was created and backed by ",[4036,21310,7497],{"href":7495,"rel":21311},[4040],", and is supported through all the years. You can request a consultation or order web development services by Epicmax ",[4036,21314,21316],{"href":4382,"rel":21315},[4040],"here",[26,21318,21320],{"id":21319},"check-it-out","Check It Out",[13,21322,21323],{},"What do you think? What do you need? Your feedback is super important — it helps us make Vuestic even better.",[13,21325,21326,21331],{},[4036,21327,21330],{"href":21328,"rel":21329},"https:\u002F\u002Fdiscord.com\u002Finvite\u002Fu7fQdqQt8c",[4040],"Join the community",", share your thoughts, or just have a look around.",[13,21333,21334],{},"Need something more? We're always ready to talk.\nLet's make something cool together.",[13,21336,21337],{},[17,21338,21339],{},"Welcome to Vuestic Admin!",[3114,21341],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":21343},[21344,21345,21346,21347,21348,21349],{"id":21219,"depth":359,"text":21220},{"id":21232,"depth":359,"text":21233},{"id":21261,"depth":359,"text":21262},{"id":21294,"depth":359,"text":21295},{"id":21301,"depth":359,"text":21302},{"id":21319,"depth":359,"text":21320},"Discover Vuestic Admin 3.0—an open-source Vue 3 admin dashboard powered by Vite, Pinia, and Tailwind CSS. Fully responsive, customizable, and packed with essential features like dark mode, i18n, and accessibility. Built with Vuestic UI by the Epicmax team for developers and businesses alike!","\u002Fimages\u002Fblog\u002Fvuestic-admin-3-0\u002Fcard.jpg",{},"\u002Fblog\u002Fvuestic-admin-3-0",{"title":21202,"description":21350},"blog\u002Fvuestic-admin-3-0","hpwrIuqqL2tMbN6fRdVAku7k6miR7YhUbnyGIgg65tY",{"id":21358,"title":21359,"author":7,"authorSlug":8,"body":21360,"date":21192,"description":21609,"extension":3154,"faq":8,"featured":3155,"image":21610,"meta":21611,"navigation":472,"path":21612,"readTime":8,"seo":21613,"stem":21614,"tags":8,"__hash__":21615},"blog\u002Fblog\u002Fvuestic-ui.md","Vuestic UI - The Best Open Source Vue 3 UI Framework in 2024",{"type":10,"value":21361,"toc":21600},[21362,21365,21373,21379,21383,21389,21395,21401,21407,21413,21419,21422,21426,21429,21443,21446,21458,21461,21469,21477,21483,21487,21490,21493,21499,21503,21506,21509,21512,21518,21521,21527,21530,21536,21543,21546,21552,21558,21562,21565,21568,21571,21575,21578,21586,21589,21595,21597],[13,21363,21364],{},"At Epicmax, we develop Vuestic UI with a focus on creating components that are user-friendly for developers, adaptable for designers, robust for testing environments, accessible for all users, and visually appealing for potential clients. Vuestic UI is an Open Source Vue 3 based UI framework. Featuring beautifully designed components, it offers extensive features for building high-quality, performance-optimized applications rapidly.",[13,21366,21367,21368,21372],{},"With the support of our active ",[4036,21369,21371],{"href":21328,"rel":21370},[4040],"Discord community",", we at Epicmax consistently gather feedback to refine and enhance Vuestic UI. Leveraging our collective experience, we address common front-end development challenges, enabling developers to focus entirely on building exceptional applications. This discussion explores our strategies to create the dream UI framework for your projects.",[13,21374,21375],{},[7602,21376],{"alt":21377,"src":21378},"Vuestic UI Vue 3 component library overview","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9dceb3751d994171.png",[26,21380,21382],{"id":21381},"why-choose-vuestic-ui-in-2024","Why Choose Vuestic UI in 2024?",[13,21384,21385,21388],{},[17,21386,21387],{},"1. Developer-Friendly:"," Our components are developed with care to be intuitive and easy to integrate, speeding up both development and deployment.",[13,21390,21391,21394],{},[17,21392,21393],{},"2. Active Development and Support:"," Our team is constantly improving Vuestic UI, and we're here to help you with any challenges you might encounter.",[13,21396,21397,21400],{},[17,21398,21399],{},"3. Robust Testing:"," We ensure Vuestic UI components are reliable and stable, no matter what you throw at them.",[13,21402,21403,21406],{},[17,21404,21405],{},"4. Accessibility Commitment:"," Accessibility is a priority for us; we strive to make Vuestic UI usable for everyone, enhancing user experience across all demographics.",[13,21408,21409,21412],{},[17,21410,21411],{},"5. Community Driven:"," We listen to our users. Your feedback directly influences future updates and features, making sure Vuestic UI evolves in the right direction.",[13,21414,21415,21418],{},[17,21416,21417],{},"6. Open Source Spirit:"," We believe in the power of open source. Vuestic UI is yours to tweak, improve, and adapt to your needs.",[3114,21420],{"label":21421,"to":10745},"Learn more",[26,21423,21425],{"id":21424},"efficient-installation-process","Efficient Installation Process",[13,21427,21428],{},"Getting started with Vuestic UI is straightforward. Simply run the following command in your terminal to begin:",[333,21430,21432],{"className":335,"code":21431,"language":337,"meta":338,"style":338},"npm create vuestic@latest\n",[63,21433,21434],{"__ignoreMap":338},[342,21435,21436,21438,21440],{"class":344,"line":345},[342,21437,393],{"class":348},[342,21439,5936],{"class":352},[342,21441,21442],{"class":352}," vuestic@latest\n",[13,21444,21445],{},"You will be presented with a few templates and features.",[13,21447,21448,21449,21453,21454,158],{},"We recommend ",[4036,21450,21452],{"href":4114,"rel":21451},[4040],"Nuxt.js development"," for large applications because Nuxt's auto imports and tree-shaking features help keep codebases cleaner and more efficient. Vuestic UI is fully tree-shakable, meaning components you don't use will not be bundled in the final build. You can read more about tree-shaking ",[4036,21455,21316],{"href":21456,"rel":21457},"https:\u002F\u002Fui.vuestic.dev\u002Fgetting-started\u002Ftree-shaking",[4040],[13,21459,21460],{},"Looking to set up something faster? Use our Vuestic Admin template with preconfigured pages like dashboard, users, project, payments, etc.",[13,21462,21463,21464,158],{},"Want to include Vuestic in your existing project? Follow ",[4036,21465,21468],{"href":21466,"rel":21467},"https:\u002F\u002Fui.vuestic.dev\u002Fgetting-started\u002Finstallation#manual-installation",[4040],"this guide",[13,21470,21471,21472,158],{},"Interested in trying it out online? Visit our ",[4036,21473,21476],{"href":21474,"rel":21475},"https:\u002F\u002Fui.vuestic.dev\u002Fplay",[4040],"playground",[13,21478,21479],{},[7602,21480],{"alt":21481,"src":21482},"Vuestic UI installation walkthrough","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663ca3cd1d81210cb76b.jpg",[26,21484,21486],{"id":21485},"advanced-configuration-for-customization","Advanced Configuration for Customization",[13,21488,21489],{},"Vuestic UI goes beyond basic customization, offering multiple theme presets beyond the standard light and dark modes. Our intelligent design automatically selects the best text colors for backgrounds, ensuring optimal readability and contrast.",[13,21491,21492],{},"Say goodbye to poor contrast. We have a limited number of colors by design, making it easier for you to modify them. You can also extend the default color palette with any colors of your choice. Additionally, we provide a tool in case you prefer to use Tailwind colors.",[13,21494,21495],{},[7602,21496],{"alt":21497,"src":21498},"Vuestic UI theme customization preview","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9e7a1634a2862ac9.gif",[328,21500,21502],{"id":21501},"interested-in-changing-icons","Interested in changing icons?",[13,21504,21505],{},"How about using two different icon fonts? Interested in using SVG icons instead of a font? What about Iconify icons? That's easy with our Icons Config, which supports multiple icon configurations and aliases.",[13,21507,21508],{},"We make it easy to globally change component props, slots, and CSS variables. In addition to global configuration, you can also save presets and reuse them across the application when needed. All configurations support TypeScript types.",[13,21510,21511],{},"Here is an example of alert customization:",[13,21513,21514],{},[7602,21515],{"alt":21516,"src":21517},"Vuestic UI alert customization code example","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9e9471d19184564c.png",[13,21519,21520],{},"Here we make all alerts closeable by default and set icons, text and color for success and error presets. We can use it later like this:",[13,21522,21523],{},[7602,21524],{"alt":21525,"src":21526},"Vuestic UI alert usage code example","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9ea3b3751d994172.png",[13,21528,21529],{},"And this is the final result:",[13,21531,21532],{},[7602,21533],{"alt":21534,"src":21535},"Vuestic UI customized alert components","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9eb1869cd5334108.png",[13,21537,21538,21539,158],{},"Dive deeper into extensive customization ",[4036,21540,21316],{"href":21541,"rel":21542},"https:\u002F\u002Fui.vuestic.dev\u002Fgetting-started\u002Fconfiguration-guide",[4040],[13,21544,21545],{},"In practice, you can create a frontend for a popular app in just one day. All with keyboard navigation, correct text contrast, and component presets.",[13,21547,21548],{},[7602,21549],{"alt":21550,"src":21551},"Vuestic UI rapid frontend prototype example","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9ebe76dfceefc304.png",[13,21553,21554],{},[7602,21555],{"alt":21556,"src":21557},"Vuestic UI prototype interface preview","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002F663c9ec58db072d552e7.png",[26,21559,21561],{"id":21560},"simplified-development-experience","Simplified Development Experience",[13,21563,21564],{},"To make your life easier as a developer, we made components simple: no more Material Design and strange borders that are hard to override! When working with large UI libraries, there are always style issues.",[13,21566,21567],{},"We use BEM and prefix all of our classes, so you can expect no conflicts with any library you use or with your existing code. We care about IntelliSense in your IDE and use TypeScript everywhere: in configs, component props, emits, and slots. Sometimes you need helpers like defineVaDataTableColumns to have IDE auto-suggestions. Easy to work with forms using the VaForm component.",[13,21569,21570],{},"We provide a lot of components that you can use in your forms. If you need something extra, you can create your own component using the VaFormField component! The documentation is full of examples and guides. If you struggle with any use case, you can easily ask a question in our Discord community.",[26,21572,21574],{"id":21573},"active-contribution-opportunities","Active Contribution Opportunities",[13,21576,21577],{},"Have you found something we can improve?",[13,21579,21580,21581,21585],{},"Don't hesitate to join our ",[4036,21582,21371],{"href":21583,"rel":21584},"https:\u002F\u002Fdiscord.gg\u002Fu7fQdqQt8c",[4040]," and discuss it! Are you interested in the code? Usually, diving into UI library source code is hard. We make frequent updates, usually every 1-2 weeks, or if you have an important feature that we have thoroughly tested before release ❤️",[13,21587,21588],{},"If you're integrating Vuestic into your project, we'd love to hear from you! Your feedback helps us better understand your needs and improve our framework. Please share your experiences and insights with us.",[13,21590,21591,21592,21594],{},"Additionally, if you have ideas for collaboration or are interested in forming a partnership, don't hesitate to reach out at ",[4036,21593,19173],{"href":19172},". We are excited about exploring potential synergies and opportunities together.",[3114,21596],{"label":3116,"to":3117},[3119,21598,21599],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":338,"searchDepth":469,"depth":469,"links":21601},[21602,21603,21604,21607,21608],{"id":21381,"depth":359,"text":21382},{"id":21424,"depth":359,"text":21425},{"id":21485,"depth":359,"text":21486,"children":21605},[21606],{"id":21501,"depth":469,"text":21502},{"id":21560,"depth":359,"text":21561},{"id":21573,"depth":359,"text":21574},"Vuestic UI is a modern open-source Vue.js UI framework with beautifully designed components, built for performance and accessibility. Developed by Epicmax, it offers a feature-rich, developer-friendly, and customizable solution for building high-quality Vue 3 applications effortlessly.","\u002Fimages\u002Fblog\u002Fvuestic-ui\u002Fcard.png",{},"\u002Fblog\u002Fvuestic-ui",{"title":21359,"description":21609},"blog\u002Fvuestic-ui","fiBqUQFdvc6N5P3ej1xIjPmC_kmECpSLZrByQbDC84Y",{"id":21617,"title":21618,"author":7,"authorSlug":8,"body":21619,"date":21685,"description":21686,"extension":3154,"faq":8,"featured":3155,"image":21687,"meta":21688,"navigation":472,"path":21689,"readTime":8,"seo":21690,"stem":21691,"tags":8,"__hash__":21692},"blog\u002Fblog\u002Fag-grid.md","Epicmax & AG Grid - seamless Vue 3 Datagrid integration with Vuestic UI",{"type":10,"value":21620,"toc":21683},[21621,21630,21633,21638,21643,21646,21651,21656,21664,21675,21681],[13,21622,21623,21624,21629],{},"Vuestic UI, the Vue 3 UI framework from Epicmax, have collaborated with AG Grid, the leading datagrid provider, to empower users with feature-rich table functionality. ",[4036,21625,21628],{"href":21626,"rel":21627},"https:\u002F\u002Fwww.ag-grid.com\u002F",[4040],"AG Grid"," is available as an extension via Vuestic UI, meaning users can add AG Grid tables to their projects with no extra cost or difficulty. The company also provides a ready-made style theme for AG Grid's library of tables, which integrates with Vuestic global config, keeping AG Grid in sync with other components on the page.",[13,21631,21632],{},"Niall Crosby, CEO, CTO and Founder of AG Grid said:",[13,21634,21635],{},[5161,21636,21637],{},"“This collaboration with Vuestic ensures users of the platform have access to the most feature-rich and clean datagrid tables in the industry. This will empower their Vue projects, and encourages them to develop creative, disruptive and flexible user interfaces.",[13,21639,21640],{},[5161,21641,21642],{},"It has also been a pleasure working alongside the team at Epicmax, and we will continue to update the AG Grid framework and extension for even more seamless collaboration in the future.”",[13,21644,21645],{},"Yauheni Prakopchyk, CTO of Epicmax added:",[13,21647,21648],{},[5161,21649,21650],{},"“While selecting a UI framework, users are looking for different flavors of tables – from simplest one to robust and complicated. AG Grid covers the most sophisticated needs, and Vuestic UI holds your hand for simpler cases. Sounds like a perfect match to me.",[13,21652,21653],{},[5161,21654,21655],{},"To support this awesome collaboration we plan to cover upcoming updates of AG Grid as well as improve integration with Vuestic UI theming system.”",[13,21657,21658,21663],{},[4036,21659,21662],{"href":21660,"rel":21661},"https:\u002F\u002Fbmmagazine.co.uk\u002Fbusiness\u002Fvuestic-ui-teams-up-with-ag-grid-to-empower-users-with-datagrid-capabilities\u002F",[4040],"Read more"," on the UK's largest business magazine for entrepreneur, start-up & enterprise.",[13,21665,21666,21667,21671,21672],{},"To start using AG Grid with Vuestic UI see the guide: ",[4036,21668,21669],{"href":21669,"rel":21670},"https:\u002F\u002Fvuestic.dev\u002Fen\u002Fextensions\u002Fag-grid",[4040],"\nIf you have any ideas for a partnership with us - say hi to ",[4036,21673,19173],{"href":21674},"mailto:hello@epicmax.co?subject=Message%20from%20epicmax.co",[13,21676,21677],{},[7602,21678],{"alt":21679,"src":21680},"Vuestic UI and AG Grid partnership banner","\u002Fimages\u002Fblog\u002Fag-grid\u002Fvuestic_ui_-_ag_grid.png",[3114,21682],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":21684},[],"2022-02-24","Epicmax partners with AG Grid to bring powerful, feature-rich Vue 3 datagrids to Vuestic UI. Enjoy seamless integration, custom styling, and enhanced table functionality—all at no extra cost. Discover how this collaboration empowers Vue.js developers with the best UI and data table solutions.","\u002Fimages\u002Fblog\u002Fag-grid\u002Fcard.jpg",{},"\u002Fblog\u002Fag-grid",{"title":21618,"description":21686},"blog\u002Fag-grid","XeJNTYpJdxwv2GSY1TZDBhkdxal9M87e2rl6R4opwIU",{"id":21694,"title":21695,"author":7,"authorSlug":8,"body":21696,"date":21685,"description":21737,"extension":3154,"faq":8,"featured":3155,"image":21738,"meta":21739,"navigation":472,"path":21740,"readTime":8,"seo":21741,"stem":21742,"tags":8,"__hash__":21743},"blog\u002Fblog\u002Fvueconf-toronto.md","Epicmax at VueConf Toronto - Meet Our CTO & Explore Vue.js Innovations",{"type":10,"value":21697,"toc":21735},[21698,21701,21704,21718,21721,21726,21731],[13,21699,21700],{},"VueConf Toronto is the #1 Vue.js conference in Toronto. We’re excited to announce that Yauheni Prakopchyk, CTO of Epicmax, will be speaking at VueConf Toronto, one of the world’s top Vue.js conferences! The event takes place from November 1-3, 2022, bringing together Vue.js experts, developers, and enthusiasts to discuss the latest advancements in the ecosystem.",[13,21702,21703],{},"🚀 Why Attend VueConf Toronto?",[41,21705,21706,21709,21712,21715],{},[44,21707,21708],{},"Gain insights from leading Vue.js experts",[44,21710,21711],{},"Explore the latest trends in frontend development",[44,21713,21714],{},"Connect with the global Vue.js community",[44,21716,21717],{},"Learn from real-world case studies and hands-on sessions",[13,21719,21720],{},"🎤 Meet Our CTO on Stage\nYauheni will share valuable technical knowledge, industry trends, and best practices in Vue.js development. If you’re attending VueConf, don’t miss the chance to learn from one of the top Vue experts in Europe!",[13,21722,21723,21724],{},"📩 Interested in working with us? Say hi at ",[4036,21725,19173],{"href":19172},[13,21727,21728],{},[7602,21729],{"alt":338,"src":21730},"\u002Fimages\u002Fblog\u002Fvueconf-toronto\u002F662a0c83f64ed9bae11f.jpg",[13,21732,21733],{},[4036,21734,18722],{"href":18721},{"title":338,"searchDepth":469,"depth":469,"links":21736},[],"Epicmax CTO Yauheni Prakopchyk is speaking at VueConf Toronto, one of the top Vue.js conferences worldwide! Join us from November 1-3, 2022, to explore cutting-edge Vue.js trends, network with industry experts, and discover career opportunities at Epicmax.","\u002Fimages\u002Fblog\u002Fvueconf-toronto\u002Fcard.jpg",{},"\u002Fblog\u002Fvueconf-toronto",{"title":21695,"description":21737},"blog\u002Fvueconf-toronto","PBaFwFV2I0rDFITKveWN8EU0qOSwLX9QMB6If5o0b1U",{"id":21745,"title":21746,"author":7,"authorSlug":8,"body":21747,"date":21685,"description":21876,"extension":3154,"faq":8,"featured":3155,"image":21877,"meta":21878,"navigation":472,"path":21879,"readTime":8,"seo":21880,"stem":21881,"tags":8,"__hash__":21882},"blog\u002Fblog\u002Fvuestic-ui-epicmax.md","Epicmax’s Journey | From Vue.js Enthusiasts to Open-Source Innovators",{"type":10,"value":21748,"toc":21872},[21749,21756,21764,21771,21777,21780,21788,21792,21821,21825,21829,21856,21859,21864,21870],[13,21750,21751,21752,21755],{},"We're a passionate team of front-end developers united by our love for coding and open source. Founded in 2017, Epicmax quickly became known for our deep dive into open source with a sharp focus on Vue.js, marking our expertise in the domain. Discover the journey of Epicmax, from the birth of an idea in 2017 to the creation of ",[4036,21753,10747],{"href":10745,"rel":21754},[4040],", a powerful open-source framework for modern web applications, born out of passion for technology and a dedication to innovation.",[13,21757,21758,21759,21763],{},"Our journey at Epicmax began with a deep-rooted love for technology and a passion for problem-solving. This unyielding drive to innovate culminated in 2017 with the founding of our company and the birth of our first project: ",[4036,21760,11469],{"href":21761,"rel":21762},"https:\u002F\u002Fadmin.vuestic.dev\u002F",[4040],". This free, open-source Vue.js admin template was not merely a project, but a testament to our technical prowess. The idea was simple - we wanted to showcase our capabilities to prospective clients, and what better way than to create an open-source project that encapsulates our skills and dedication?",[13,21765,21766,21767,21770],{},"As we brainstormed ideas for a product that would bring value to the tech community, the concept of admin templates emerged as a promising choice. Our collective curiosity was piqued by Vue.js, a growing JavaScript framework, and we were eager to delve into its intricacies. The result of this exploration was ",[4036,21768,11469],{"href":21761,"rel":21769},[4040],", a project that not only showcased our talents but also resonated well with the tech community. This success marked the start of our continued commitment to maintaining and improving this project.",[13,21772,21773],{},[7602,21774],{"alt":21775,"src":21776},"Epicmax team working on Vuestic Admin","\u002Fimages\u002Fblog\u002Fvuestic-ui-epicmax\u002F6464ad07f295c0007c87.png",[13,21778,21779],{},"Over time, we came to the realization that we could leverage our experience with Vuestic Admin to create something even more impactful - a comprehensive components framework of our own. This led to the evolution of Vuestic Admin into what is now known as Vuestic UI.",[13,21781,21782,21783,21787],{},"Launched in 2021, ",[4036,21784,10747],{"href":21785,"rel":21786},"https:\u002F\u002Fgithub.com\u002Fepicmaxco\u002Fvuestic-ui\u002F",[4040]," is a robust framework for modern web applications. It embodies the fruits of our labor, the lessons learned, and the insights gained over years of dedication to our craft. Today, we take pride in sharing the advantages that Vuestic UI offers.",[26,21789,21791],{"id":21790},"explore-vuestic-ui","Explore Vuestic UI",[41,21793,21794,21801,21807,21814],{},[44,21795,21796,21797],{},"Read more about Vuestic UI: ",[4036,21798,21800],{"href":10745,"rel":21799},[4040],"ui.vuestic.dev",[44,21802,21803,21804],{},"Give us a star on ",[4036,21805,21088],{"href":21785,"rel":21806},[4040],[44,21808,21809],{},[4036,21810,21813],{"href":21811,"rel":21812},"https:\u002F\u002Fui.vuestic.dev\u002Fcontribution\u002Fguide",[4040],"Contribution Guide",[44,21815,21816],{},[4036,21817,21820],{"href":21818,"rel":21819},"https:\u002F\u002Fui.vuestic.dev\u002Fsupport\u002Fconsulting",[4040],"Support & Consulting",[3114,21822],{"label":21823,"to":21824},"Get Started","https:\u002F\u002Fui.vuestic.dev\u002Fgetting-started\u002Finstallation",[26,21826,21828],{"id":21827},"explore-vuestic-admin","Explore Vuestic Admin",[41,21830,21831,21838,21843,21850],{},[44,21832,21833,21834],{},"Read more about Vuestic Admin: ",[4036,21835,21837],{"href":21761,"rel":21836},[4040],"admin.vuestic.dev",[44,21839,21803,21840],{},[4036,21841,21088],{"href":21086,"rel":21842},[4040],[44,21844,21845],{},[4036,21846,21849],{"href":21847,"rel":21848},"https:\u002F\u002Fadmin-demo.vuestic.dev\u002Fdashboard",[4040],"Live Demo",[44,21851,21852],{},[4036,21853,21855],{"href":21541,"rel":21854},[4040],"Learn How to Configure",[13,21857,21858],{},"Stay tuned as we continue to evolve, innovate, and contribute to the tech community with our open-source projects.",[13,21860,21861],{},[17,21862,21863],{},"At Epicmax, our journey is just beginning, and we are excited about the future we are building together.",[13,21865,21866],{},[7602,21867],{"alt":21868,"src":21869},"Epicmax open-source community celebration","\u002Fimages\u002Fblog\u002Fvuestic-ui-epicmax\u002F6464ad23868661375f62.png",[3114,21871],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":21873},[21874,21875],{"id":21790,"depth":359,"text":21791},{"id":21827,"depth":359,"text":21828},"Discover Epicmax’s evolution from a passionate frontend team to a leading Vue.js open-source powerhouse. Learn how Vuestic Admin grew into Vuestic UI, a modern Vue 3 framework for web applications. Join our journey, explore our projects, and contribute to the future of open source!","\u002Fimages\u002Fblog\u002Fvuestic-ui-epicmax\u002Fcard.jpg",{},"\u002Fblog\u002Fvuestic-ui-epicmax",{"title":21746,"description":21876},"blog\u002Fvuestic-ui-epicmax","HklDKlAHE3VJ2Zr0ynXQAgEsUNcjjcmriyM9Og4ASE0",{"id":21884,"title":21885,"author":7,"authorSlug":8,"body":21886,"date":21685,"description":21962,"extension":3154,"faq":8,"featured":3155,"image":21963,"meta":21964,"navigation":472,"path":21965,"readTime":8,"seo":21966,"stem":21967,"tags":8,"__hash__":21968},"blog\u002Fblog\u002Fvuestic-ui-v1-5.md","Vuestic UI Update | New Features, Dark Theme & Accessibility in Vue 3 Framework",{"type":10,"value":21887,"toc":21955},[21888,21891,21896,21900,21904,21908,21911,21915,21929,21932,21939,21942,21947,21953],[13,21889,21890],{},"We have good news! Vuestic UI v1.5 is released! 🚀",[13,21892,21893,21895],{},[17,21894,10747],{}," is an open-source Vue 3-based UI framework that provides easily configurable, ready-to-use frontend components that speed up the development of responsive, fast-loading web interfaces. Epicmax initially released it in May 2021.",[26,21897,21899],{"id":21898},"whats-new","💥 What's New?",[328,21901,21903],{"id":21902},"dark-theme","🌚 Dark Theme",[328,21905,21907],{"id":21906},"accessibility-feature-added-this-is-a-big-one","👁 Accessibility feature added — this is a big one.",[13,21909,21910],{},"Vuestic became accessible across the board. All components are designed so that it is convenient for all people to use.",[26,21912,21914],{"id":21913},"new-components","🐣 New components",[41,21916,21917,21920,21923,21926],{},[44,21918,21919],{},"Tree view",[44,21921,21922],{},"Split view",[44,21924,21925],{},"Dropdown",[44,21927,21928],{},"Virtual scroll",[13,21930,21931],{},"In addition to these changes is a lot of other fixes and updates.",[13,21933,21934,21935],{},"View on GitHub: ",[4036,21936,18783],{"href":21937,"rel":21938},"https:\u002F\u002Flnkd.in\u002Fdb-CzHW",[4040],[13,21940,21941],{},"If you have any questions come to our Discord channel. We like to discuss any ideas 💥💥💥",[13,21943,21944],{},[4036,21945,21328],{"href":21328,"rel":21946},[4040],[13,21948,21949],{},[7602,21950],{"alt":21951,"src":21952},"Vuestic UI v1.5 release banner","\u002Fimages\u002Fblog\u002Fvuestic-ui-v1-5\u002F662a4f1b76a551b1d15d.jpg",[3114,21954],{"label":3116,"to":3117},{"title":338,"searchDepth":469,"depth":469,"links":21956},[21957,21961],{"id":21898,"depth":359,"text":21899,"children":21958},[21959,21960],{"id":21902,"depth":469,"text":21903},{"id":21906,"depth":469,"text":21907},{"id":21913,"depth":359,"text":21914},"Discover the latest Vuestic UI updates! Now with a Dark Theme, enhanced accessibility, new Vue 3 components like Tree View, Split View, Dropdown, and Virtual Scroll. Build fast, responsive web apps with Vuestic UI. Explore on GitHub & join our Discord!","\u002Fimages\u002Fblog\u002Fvuestic-ui-v1-5\u002Fcard.jpg",{},"\u002Fblog\u002Fvuestic-ui-v1-5",{"title":21885,"description":21962},"blog\u002Fvuestic-ui-v1-5","0OWfbqctNiNDJmTnXIpq-qFvvUzUNm8u6BbNma4vxtw",{"id":21970,"title":21971,"author":7,"authorSlug":8,"body":21972,"date":21685,"description":24998,"extension":3154,"faq":8,"featured":3155,"image":24999,"meta":25000,"navigation":472,"path":25001,"readTime":8,"seo":25002,"stem":25003,"tags":8,"__hash__":25004},"blog\u002Fblog\u002Fvuestic-ui-with-ag-grid.md","Vuestic UI & AG Grid Tutorial - Build Advanced Vue 3 Datatables with Epicmax",{"type":10,"value":21973,"toc":24989},[21974,21977,21983,21986,21989,21998,22005,22014,22019,22022,22064,22069,22073,22126,22129,22134,22154,22308,22312,22680,22861,22866,23052,23107,23112,23116,23344,23398,23403,23484,23490,23494,23643,23648,23652,24103,24108,24112,24459,24465,24966,24972,24977,24982,24984,24986],[13,21975,21976],{},"Vuestic UI, the Vue 3 UI framework from Epicmax, have collaborated with AG Grid, the leading datagrid provider, to empower users with feature-rich table functionality.",[13,21978,21979,21982],{},[4036,21980,21628],{"href":21626,"rel":21981},[4040]," is available as an extension via Vuestic UI, meaning users can add AG Grid tables to their projects with no extra cost or difficulty. The company also provides a ready-made style theme for AG Grid’s library of tables, which integrates with Vuestic global config, keeping AG Grid in sync with other components on the page.",[13,21984,21985],{},"Vuestic selected AG Grid as an excellent solution for complex data handling needs. Features like fixed columns and grouping are too expensive to implement, so it’s more beneficial to use a trusted tool instead of developing something independently. The experience in non open source projects was positive as well.",[13,21987,21988],{},"AG Grid works with all JavaScript frameworks, including React, Angular, and Vue, including Vue 2 and Vue 3. AG Grid saves on development time, providing the perfect mix of out-of-the-box functionality and customization.",[13,21990,21991,21992,21997],{},"For teams upgrading legacy Vue 2 projects, combining AG Grid with Vuestic UI pairs well with professional ",[17,21993,21994],{},[4036,21995,19595],{"href":5956,"rel":21996},[4040],", helping ensure a smooth transition to modern Vue 3 architecture.",[13,21999,22000,22001,158],{},"You can find the repo here and the code sandbox ",[4036,22002,21316],{"href":22003,"rel":22004},"https:\u002F\u002Fcodesandbox.io\u002Fp\u002Fgithub\u002Fepicmaxco\u002Fvuestic-ag-grid-app\u002Fdraft\u002Famazing-violet?file=%2Fsrc%2Fcomponents%2FUsersPage.vue",[4040],[13,22006,22007,22008,22010,22011,22013],{},"Requirements",[4375,22009],{},"\nThe application we will build is a CRUD page of users - something an admin would use to manage users. You can find something similar in almost any admin dashboard.",[4375,22012],{},"\nHere’s a wireframe for the application:",[13,22015,22016],{},[7602,22017],{"alt":338,"src":22018},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f8e184aa2197bc7e.png",[13,22020,22021],{},"Can’t work on an app without a plan, so here’s one:",[41,22023,22026,22034,22040,22046,22052,22058],{"className":22024},[22025],"contains-task-list",[44,22027,22030,22033],{"className":22028},[22029],"task-list-item",[14908,22031],{"disabled":472,"type":22032},"checkbox"," Scaffold new project",[44,22035,22037,22039],{"className":22036},[22029],[14908,22038],{"disabled":472,"type":22032}," Make a simple table with image, name, email",[44,22041,22043,22045],{"className":22042},[22029],[14908,22044],{"disabled":472,"type":22032}," Add active column",[44,22047,22049,22051],{"className":22048},[22029],[14908,22050],{"disabled":472,"type":22032}," Implement sorting for all columns",[44,22053,22055,22057],{"className":22054},[22029],[14908,22056],{"disabled":472,"type":22032}," Implement full-text filtering",[44,22059,22061,22063],{"className":22060},[22029],[14908,22062],{"disabled":472,"type":22032}," Add create, edit and delete functionality",[13,22065,22066],{},[7602,22067],{"alt":338,"src":22068},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f90cccd0c4a50fc3.gif",[26,22070,22072],{"id":22071},"scaffold-new-project","Scaffold new project",[333,22074,22076],{"className":335,"code":22075,"language":337,"meta":338,"style":338},"npm create vite ag-grid-project -- --template vue-ts \ncd ag-grid-project \nnpm install \nnpm run dev\n",[63,22077,22078,22102,22110,22118],{"__ignoreMap":338},[342,22079,22080,22082,22084,22087,22090,22093,22096,22099],{"class":344,"line":345},[342,22081,393],{"class":348},[342,22083,5936],{"class":352},[342,22085,22086],{"class":352}," vite",[342,22088,22089],{"class":352}," ag-grid-project",[342,22091,22092],{"class":352}," --",[342,22094,22095],{"class":352}," --template",[342,22097,22098],{"class":352}," vue-ts",[342,22100,22101],{"class":450}," \n",[342,22103,22104,22106,22108],{"class":344,"line":359},[342,22105,363],{"class":362},[342,22107,22089],{"class":352},[342,22109,22101],{"class":450},[342,22111,22112,22114,22116],{"class":344,"line":469},[342,22113,393],{"class":348},[342,22115,778],{"class":352},[342,22117,22101],{"class":450},[342,22119,22120,22122,22124],{"class":344,"line":476},[342,22121,393],{"class":348},[342,22123,396],{"class":352},[342,22125,399],{"class":352},[13,22127,22128],{},"If you open the page in browser - you should see default vite page:",[13,22130,22131],{},[7602,22132],{"alt":338,"src":22133},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f948bfc2e72f2376.png",[333,22135,22137],{"className":335,"code":22136,"language":337,"meta":338,"style":338},"npm install vuestic-ui @vuestic\u002Fag-grid-theme sass\n",[63,22138,22139],{"__ignoreMap":338},[342,22140,22141,22143,22145,22148,22151],{"class":344,"line":345},[342,22142,393],{"class":348},[342,22144,778],{"class":352},[342,22146,22147],{"class":352}," vuestic-ui",[342,22149,22150],{"class":352}," @vuestic\u002Fag-grid-theme",[342,22152,22153],{"class":352}," sass\n",[333,22155,22157],{"className":4501,"code":22156,"language":4503,"meta":338,"style":338},"import { createApp } from \"vue\";\nimport App from \".\u002FApp.vue\";\nimport { createVuesticEssential } from \"vuestic-ui\"; \u002F\u002F \u003C--\nimport 'vuestic-ui\u002Fdist\u002Fvuestic-ui.css' \u002F\u002F \u003C--\n\nconst app = createApp(App);\n\napp.use(createVuesticEssential()); \u002F\u002F \u003C--\n\napp.mount(\"#app\");\n",[63,22158,22159,22180,22198,22223,22236,22240,22256,22260,22282,22286],{"__ignoreMap":338},[342,22160,22161,22163,22165,22168,22170,22172,22174,22176,22178],{"class":344,"line":345},[342,22162,443],{"class":442},[342,22164,447],{"class":446},[342,22166,22167],{"class":450}," createApp",[342,22169,454],{"class":446},[342,22171,457],{"class":442},[342,22173,460],{"class":446},[342,22175,1248],{"class":352},[342,22177,566],{"class":446},[342,22179,7819],{"class":446},[342,22181,22182,22184,22187,22189,22191,22194,22196],{"class":344,"line":359},[342,22183,443],{"class":442},[342,22185,22186],{"class":450}," App ",[342,22188,815],{"class":442},[342,22190,460],{"class":446},[342,22192,22193],{"class":352},".\u002FApp.vue",[342,22195,566],{"class":446},[342,22197,7819],{"class":446},[342,22199,22200,22202,22204,22207,22209,22211,22213,22215,22217,22220],{"class":344,"line":469},[342,22201,443],{"class":442},[342,22203,447],{"class":446},[342,22205,22206],{"class":450}," createVuesticEssential",[342,22208,454],{"class":446},[342,22210,457],{"class":442},[342,22212,460],{"class":446},[342,22214,11914],{"class":352},[342,22216,566],{"class":446},[342,22218,22219],{"class":446},";",[342,22221,22222],{"class":436}," \u002F\u002F \u003C--\n",[342,22224,22225,22227,22229,22232,22234],{"class":344,"line":476},[342,22226,443],{"class":442},[342,22228,4017],{"class":446},[342,22230,22231],{"class":352},"vuestic-ui\u002Fdist\u002Fvuestic-ui.css",[342,22233,3229],{"class":446},[342,22235,22222],{"class":436},[342,22237,22238],{"class":344,"line":493},[342,22239,473],{"emptyLinePlaceholder":472},[342,22241,22242,22244,22247,22249,22251,22254],{"class":344,"line":506},[342,22243,831],{"class":830},[342,22245,22246],{"class":450}," app ",[342,22248,837],{"class":446},[342,22250,22167],{"class":362},[342,22252,22253],{"class":450},"(App)",[342,22255,7819],{"class":446},[342,22257,22258],{"class":344,"line":530},[342,22259,473],{"emptyLinePlaceholder":472},[342,22261,22262,22265,22267,22270,22272,22275,22278,22280],{"class":344,"line":540},[342,22263,22264],{"class":450},"app",[342,22266,158],{"class":446},[342,22268,22269],{"class":362},"use",[342,22271,487],{"class":450},[342,22273,22274],{"class":362},"createVuesticEssential",[342,22276,22277],{"class":450},"())",[342,22279,22219],{"class":446},[342,22281,22222],{"class":436},[342,22283,22284],{"class":344,"line":571},[342,22285,473],{"emptyLinePlaceholder":472},[342,22287,22288,22290,22292,22295,22297,22299,22302,22304,22306],{"class":344,"line":600},[342,22289,22264],{"class":450},[342,22291,158],{"class":446},[342,22293,22294],{"class":362},"mount",[342,22296,487],{"class":450},[342,22298,566],{"class":446},[342,22300,22301],{"class":352},"#app",[342,22303,566],{"class":446},[342,22305,294],{"class":450},[342,22307,7819],{"class":446},[26,22309,22311],{"id":22310},"make-a-simple-table-with-image-name-email","Make a simple table with \"image\", \"name\", \"email\"",[333,22313,22315],{"className":1246,"code":22314,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cdiv style=\"height: 300px; width: 100%;\">\n    \u003Cag-grid-vue\n      class=\"ag-theme-vuestic\"\n      style=\"width: 100%; height: 100%;\"\n      :columnDefs=\"columnDefs\"\n      :rowData=\"reactiveUsers\"\n      :modules=\"modules\"\n    \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { AgGridVue } from '@ag-grid-community\u002Fvue3'\nimport {\n  ClientSideRowModelModule,\n} from '@ag-grid-community\u002Fclient-side-row-model'\nimport { users } from '..\u002Fdata\u002Fusers'\nimport { reactive } from 'vue'\n\nconst modules = [ClientSideRowModelModule]\n\nconst reactiveUsers = reactive(users)\n\nconst columnDefs = [\n  { field: 'name' },\n  { field: 'email' },\n]\n\u003C\u002Fscript>\n\n\u003Cstyle lang=\"scss\">\n@import \"@vuestic\u002Fag-grid-theme\u002Findex.scss\";\n\u003C\u002Fstyle>\n",[63,22316,22317,22325,22344,22351,22364,22378,22392,22406,22420,22425,22433,22441,22445,22465,22485,22491,22498,22511,22531,22549,22553,22565,22569,22583,22587,22598,22616,22632,22636,22644,22648,22667,22672],{"__ignoreMap":338},[342,22318,22319,22321,22323],{"class":344,"line":345},[342,22320,1023],{"class":446},[342,22322,1522],{"class":496},[342,22324,1280],{"class":446},[342,22326,22327,22329,22331,22333,22335,22337,22340,22342],{"class":344,"line":359},[342,22328,1530],{"class":446},[342,22330,1533],{"class":496},[342,22332,7882],{"class":830},[342,22334,837],{"class":446},[342,22336,566],{"class":446},[342,22338,22339],{"class":352},"height: 300px; width: 100%;",[342,22341,566],{"class":446},[342,22343,1280],{"class":446},[342,22345,22346,22348],{"class":344,"line":469},[342,22347,1553],{"class":446},[342,22349,22350],{"class":496},"ag-grid-vue\n",[342,22352,22353,22355,22357,22359,22362],{"class":344,"line":476},[342,22354,19674],{"class":830},[342,22356,837],{"class":446},[342,22358,566],{"class":446},[342,22360,22361],{"class":352},"ag-theme-vuestic",[342,22363,466],{"class":446},[342,22365,22366,22369,22371,22373,22376],{"class":344,"line":493},[342,22367,22368],{"class":830},"      style",[342,22370,837],{"class":446},[342,22372,566],{"class":446},[342,22374,22375],{"class":352},"width: 100%; height: 100%;",[342,22377,466],{"class":446},[342,22379,22380,22383,22385,22387,22390],{"class":344,"line":506},[342,22381,22382],{"class":830},"      :columnDefs",[342,22384,837],{"class":446},[342,22386,566],{"class":446},[342,22388,22389],{"class":352},"columnDefs",[342,22391,466],{"class":446},[342,22393,22394,22397,22399,22401,22404],{"class":344,"line":530},[342,22395,22396],{"class":830},"      :rowData",[342,22398,837],{"class":446},[342,22400,566],{"class":446},[342,22402,22403],{"class":352},"reactiveUsers",[342,22405,466],{"class":446},[342,22407,22408,22411,22413,22415,22418],{"class":344,"line":540},[342,22409,22410],{"class":830},"      :modules",[342,22412,837],{"class":446},[342,22414,566],{"class":446},[342,22416,22417],{"class":352},"modules",[342,22419,466],{"class":446},[342,22421,22422],{"class":344,"line":571},[342,22423,22424],{"class":446},"    \u002F>\n",[342,22426,22427,22429,22431],{"class":344,"line":600},[342,22428,1803],{"class":446},[342,22430,1533],{"class":496},[342,22432,1280],{"class":446},[342,22434,22435,22437,22439],{"class":344,"line":629},[342,22436,1505],{"class":446},[342,22438,1522],{"class":496},[342,22440,1280],{"class":446},[342,22442,22443],{"class":344,"line":659},[342,22444,473],{"emptyLinePlaceholder":472},[342,22446,22447,22449,22451,22453,22455,22457,22459,22461,22463],{"class":344,"line":688},[342,22448,1023],{"class":446},[342,22450,1262],{"class":496},[342,22452,1265],{"class":830},[342,22454,1268],{"class":830},[342,22456,837],{"class":446},[342,22458,566],{"class":446},[342,22460,1275],{"class":352},[342,22462,566],{"class":446},[342,22464,1280],{"class":446},[342,22466,22467,22469,22471,22474,22476,22478,22480,22483],{"class":344,"line":694},[342,22468,443],{"class":442},[342,22470,447],{"class":446},[342,22472,22473],{"class":450}," AgGridVue",[342,22475,454],{"class":446},[342,22477,457],{"class":442},[342,22479,4017],{"class":446},[342,22481,22482],{"class":352},"@ag-grid-community\u002Fvue3",[342,22484,5273],{"class":446},[342,22486,22487,22489],{"class":344,"line":700},[342,22488,443],{"class":442},[342,22490,503],{"class":446},[342,22492,22493,22496],{"class":344,"line":1461},[342,22494,22495],{"class":450},"  ClientSideRowModelModule",[342,22497,527],{"class":446},[342,22499,22500,22502,22504,22506,22509],{"class":344,"line":1478},[342,22501,703],{"class":446},[342,22503,457],{"class":442},[342,22505,4017],{"class":446},[342,22507,22508],{"class":352},"@ag-grid-community\u002Fclient-side-row-model",[342,22510,5273],{"class":446},[342,22512,22513,22515,22517,22520,22522,22524,22526,22529],{"class":344,"line":1495},[342,22514,443],{"class":442},[342,22516,447],{"class":446},[342,22518,22519],{"class":450}," users",[342,22521,454],{"class":446},[342,22523,457],{"class":442},[342,22525,4017],{"class":446},[342,22527,22528],{"class":352},"..\u002Fdata\u002Fusers",[342,22530,5273],{"class":446},[342,22532,22533,22535,22537,22539,22541,22543,22545,22547],{"class":344,"line":1502},[342,22534,443],{"class":442},[342,22536,447],{"class":446},[342,22538,5392],{"class":450},[342,22540,454],{"class":446},[342,22542,457],{"class":442},[342,22544,4017],{"class":446},[342,22546,1248],{"class":352},[342,22548,5273],{"class":446},[342,22550,22551],{"class":344,"line":1512},[342,22552,473],{"emptyLinePlaceholder":472},[342,22554,22555,22557,22560,22562],{"class":344,"line":1517},[342,22556,831],{"class":830},[342,22558,22559],{"class":450}," modules ",[342,22561,837],{"class":446},[342,22563,22564],{"class":450}," [ClientSideRowModelModule]\n",[342,22566,22567],{"class":344,"line":1527},[342,22568,473],{"emptyLinePlaceholder":472},[342,22570,22571,22573,22576,22578,22580],{"class":344,"line":1550},[342,22572,831],{"class":830},[342,22574,22575],{"class":450}," reactiveUsers ",[342,22577,837],{"class":446},[342,22579,5392],{"class":362},[342,22581,22582],{"class":450},"(users)\n",[342,22584,22585],{"class":344,"line":1581},[342,22586,473],{"emptyLinePlaceholder":472},[342,22588,22589,22591,22594,22596],{"class":344,"line":1601},[342,22590,831],{"class":830},[342,22592,22593],{"class":450}," columnDefs ",[342,22595,837],{"class":446},[342,22597,6554],{"class":450},[342,22599,22600,22603,22606,22608,22610,22612,22614],{"class":344,"line":1610},[342,22601,22602],{"class":446},"  {",[342,22604,22605],{"class":496}," field",[342,22607,500],{"class":446},[342,22609,4017],{"class":446},[342,22611,4582],{"class":352},[342,22613,3229],{"class":446},[342,22615,4025],{"class":446},[342,22617,22618,22620,22622,22624,22626,22628,22630],{"class":344,"line":1625},[342,22619,22602],{"class":446},[342,22621,22605],{"class":496},[342,22623,500],{"class":446},[342,22625,4017],{"class":446},[342,22627,4590],{"class":352},[342,22629,3229],{"class":446},[342,22631,4025],{"class":446},[342,22633,22634],{"class":344,"line":1640},[342,22635,3699],{"class":450},[342,22637,22638,22640,22642],{"class":344,"line":1655},[342,22639,1505],{"class":446},[342,22641,1262],{"class":496},[342,22643,1280],{"class":446},[342,22645,22646],{"class":344,"line":1670},[342,22647,473],{"emptyLinePlaceholder":472},[342,22649,22650,22652,22654,22656,22658,22660,22663,22665],{"class":344,"line":1676},[342,22651,1023],{"class":446},[342,22653,3119],{"class":496},[342,22655,1268],{"class":830},[342,22657,837],{"class":446},[342,22659,566],{"class":446},[342,22661,22662],{"class":352},"scss",[342,22664,566],{"class":446},[342,22666,1280],{"class":446},[342,22668,22669],{"class":344,"line":1685},[342,22670,22671],{"class":450},"@import \"@vuestic\u002Fag-grid-theme\u002Findex.scss\";\n",[342,22673,22674,22676,22678],{"class":344,"line":1700},[342,22675,1505],{"class":446},[342,22677,3119],{"class":496},[342,22679,1280],{"class":446},[333,22681,22683],{"className":12060,"code":22682,"language":1275,"meta":338,"style":338},"type User = {\n  id: number,\n  image: string,\n  name: string,\n  email: string,\n  active: boolean,\n}\n\nexport const users: User[] = [\n  {\n    id: 1,\n    image: 'https:\u002F\u002Fpicsum.photos\u002Fid\u002F1\u002F50\u002F50',\n    name: 'Ashley Mcdaniel',\n    email: 'ashleymcdaniel@nebulean.com',\n    active: true,\n  },\n...\n",[63,22684,22685,22696,22708,22718,22728,22739,22751,22755,22759,22778,22783,22794,22810,22825,22841,22852,22856],{"__ignoreMap":338},[342,22686,22687,22689,22692,22694],{"class":344,"line":345},[342,22688,15146],{"class":830},[342,22690,22691],{"class":348}," User",[342,22693,1107],{"class":446},[342,22695,503],{"class":446},[342,22697,22698,22701,22703,22706],{"class":344,"line":359},[342,22699,22700],{"class":496},"  id",[342,22702,500],{"class":446},[342,22704,22705],{"class":348}," number",[342,22707,527],{"class":446},[342,22709,22710,22712,22714,22716],{"class":344,"line":469},[342,22711,3598],{"class":496},[342,22713,500],{"class":446},[342,22715,1986],{"class":348},[342,22717,527],{"class":446},[342,22719,22720,22722,22724,22726],{"class":344,"line":476},[342,22721,11937],{"class":496},[342,22723,500],{"class":446},[342,22725,1986],{"class":348},[342,22727,527],{"class":446},[342,22729,22730,22733,22735,22737],{"class":344,"line":493},[342,22731,22732],{"class":496},"  email",[342,22734,500],{"class":446},[342,22736,1986],{"class":348},[342,22738,527],{"class":446},[342,22740,22741,22744,22746,22749],{"class":344,"line":506},[342,22742,22743],{"class":496},"  active",[342,22745,500],{"class":446},[342,22747,22748],{"class":348}," boolean",[342,22750,527],{"class":446},[342,22752,22753],{"class":344,"line":530},[342,22754,956],{"class":446},[342,22756,22757],{"class":344,"line":540},[342,22758,473],{"emptyLinePlaceholder":472},[342,22760,22761,22763,22765,22767,22769,22771,22774,22776],{"class":344,"line":571},[342,22762,479],{"class":442},[342,22764,1000],{"class":830},[342,22766,22519],{"class":450},[342,22768,500],{"class":446},[342,22770,22691],{"class":348},[342,22772,22773],{"class":450},"[] ",[342,22775,837],{"class":446},[342,22777,6554],{"class":450},[342,22779,22780],{"class":344,"line":600},[342,22781,22782],{"class":446},"  {\n",[342,22784,22785,22788,22790,22792],{"class":344,"line":629},[342,22786,22787],{"class":496},"    id",[342,22789,500],{"class":446},[342,22791,2834],{"class":1208},[342,22793,527],{"class":446},[342,22795,22796,22799,22801,22803,22806,22808],{"class":344,"line":659},[342,22797,22798],{"class":496},"    image",[342,22800,500],{"class":446},[342,22802,4017],{"class":446},[342,22804,22805],{"class":352},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F1\u002F50\u002F50",[342,22807,3229],{"class":446},[342,22809,527],{"class":446},[342,22811,22812,22814,22816,22818,22821,22823],{"class":344,"line":688},[342,22813,14023],{"class":496},[342,22815,500],{"class":446},[342,22817,4017],{"class":446},[342,22819,22820],{"class":352},"Ashley Mcdaniel",[342,22822,3229],{"class":446},[342,22824,527],{"class":446},[342,22826,22827,22830,22832,22834,22837,22839],{"class":344,"line":694},[342,22828,22829],{"class":496},"    email",[342,22831,500],{"class":446},[342,22833,4017],{"class":446},[342,22835,22836],{"class":352},"ashleymcdaniel@nebulean.com",[342,22838,3229],{"class":446},[342,22840,527],{"class":446},[342,22842,22843,22846,22848,22850],{"class":344,"line":700},[342,22844,22845],{"class":496},"    active",[342,22847,500],{"class":446},[342,22849,6343],{"class":3603},[342,22851,527],{"class":446},[342,22853,22854],{"class":344,"line":1461},[342,22855,697],{"class":446},[342,22857,22858],{"class":344,"line":1478},[342,22859,22860],{"class":446},"...\n",[13,22862,22863],{},[7602,22864],{"alt":338,"src":22865},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f988feeda38b9ee8.png",[333,22867,22869],{"className":1246,"code":22868,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cva-avatar :src=\"url\" :size=\"44\" \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { VaAvatar } from 'vuestic-ui'\nimport { computed } from 'vue'\n\nconst props = defineProps\u003C{params: {value: string}}>()\n\nconst url = computed(() => props.params.value)\n\u003C\u002Fscript>\n",[63,22870,22871,22879,22910,22918,22922,22942,22961,22979,22983,23012,23016,23044],{"__ignoreMap":338},[342,22872,22873,22875,22877],{"class":344,"line":345},[342,22874,1023],{"class":446},[342,22876,1522],{"class":496},[342,22878,1280],{"class":446},[342,22880,22881,22883,22886,22888,22890,22892,22894,22896,22899,22901,22903,22906,22908],{"class":344,"line":359},[342,22882,1530],{"class":446},[342,22884,22885],{"class":496},"va-avatar",[342,22887,7731],{"class":830},[342,22889,837],{"class":446},[342,22891,566],{"class":446},[342,22893,9015],{"class":352},[342,22895,566],{"class":446},[342,22897,22898],{"class":830}," :size",[342,22900,837],{"class":446},[342,22902,566],{"class":446},[342,22904,22905],{"class":352},"44",[342,22907,566],{"class":446},[342,22909,7779],{"class":446},[342,22911,22912,22914,22916],{"class":344,"line":469},[342,22913,1505],{"class":446},[342,22915,1522],{"class":496},[342,22917,1280],{"class":446},[342,22919,22920],{"class":344,"line":476},[342,22921,473],{"emptyLinePlaceholder":472},[342,22923,22924,22926,22928,22930,22932,22934,22936,22938,22940],{"class":344,"line":493},[342,22925,1023],{"class":446},[342,22927,1262],{"class":496},[342,22929,1265],{"class":830},[342,22931,1268],{"class":830},[342,22933,837],{"class":446},[342,22935,566],{"class":446},[342,22937,1275],{"class":352},[342,22939,566],{"class":446},[342,22941,1280],{"class":446},[342,22943,22944,22946,22948,22951,22953,22955,22957,22959],{"class":344,"line":506},[342,22945,443],{"class":442},[342,22947,447],{"class":446},[342,22949,22950],{"class":450}," VaAvatar",[342,22952,454],{"class":446},[342,22954,457],{"class":442},[342,22956,4017],{"class":446},[342,22958,11914],{"class":352},[342,22960,5273],{"class":446},[342,22962,22963,22965,22967,22969,22971,22973,22975,22977],{"class":344,"line":530},[342,22964,443],{"class":442},[342,22966,447],{"class":446},[342,22968,1423],{"class":450},[342,22970,454],{"class":446},[342,22972,457],{"class":442},[342,22974,4017],{"class":446},[342,22976,1248],{"class":352},[342,22978,5273],{"class":446},[342,22980,22981],{"class":344,"line":540},[342,22982,473],{"emptyLinePlaceholder":472},[342,22984,22985,22987,22989,22991,22993,22995,22997,22999,23001,23003,23005,23007,23010],{"class":344,"line":571},[342,22986,831],{"class":830},[342,22988,8926],{"class":450},[342,22990,837],{"class":446},[342,22992,8931],{"class":362},[342,22994,17923],{"class":446},[342,22996,1927],{"class":496},[342,22998,500],{"class":446},[342,23000,447],{"class":446},[342,23002,1148],{"class":496},[342,23004,500],{"class":446},[342,23006,1986],{"class":348},[342,23008,23009],{"class":446},"}}>",[342,23011,1112],{"class":450},[342,23013,23014],{"class":344,"line":600},[342,23015,473],{"emptyLinePlaceholder":472},[342,23017,23018,23020,23023,23025,23027,23029,23031,23033,23035,23037,23039,23041],{"class":344,"line":629},[342,23019,831],{"class":830},[342,23021,23022],{"class":450}," url ",[342,23024,837],{"class":446},[342,23026,1423],{"class":362},[342,23028,487],{"class":450},[342,23030,941],{"class":446},[342,23032,1011],{"class":830},[342,23034,13025],{"class":450},[342,23036,158],{"class":446},[342,23038,1927],{"class":450},[342,23040,158],{"class":446},[342,23042,23043],{"class":450},"value)\n",[342,23045,23046,23048,23050],{"class":344,"line":659},[342,23047,1505],{"class":446},[342,23049,1262],{"class":496},[342,23051,1280],{"class":446},[333,23053,23055],{"className":4501,"code":23054,"language":4503,"meta":338,"style":338},"const columnDefs = [\n  { field: 'image', cellRenderer: UserImage, width: 65 },\n  ...\n",[63,23056,23057,23067,23103],{"__ignoreMap":338},[342,23058,23059,23061,23063,23065],{"class":344,"line":345},[342,23060,831],{"class":830},[342,23062,22593],{"class":450},[342,23064,837],{"class":446},[342,23066,6554],{"class":450},[342,23068,23069,23071,23073,23075,23077,23080,23082,23084,23087,23089,23092,23094,23096,23098,23101],{"class":344,"line":359},[342,23070,22602],{"class":446},[342,23072,22605],{"class":496},[342,23074,500],{"class":446},[342,23076,4017],{"class":446},[342,23078,23079],{"class":352},"image",[342,23081,3229],{"class":446},[342,23083,1047],{"class":446},[342,23085,23086],{"class":496}," cellRenderer",[342,23088,500],{"class":446},[342,23090,23091],{"class":450}," UserImage",[342,23093,1047],{"class":446},[342,23095,7669],{"class":496},[342,23097,500],{"class":446},[342,23099,23100],{"class":1208}," 65",[342,23102,4025],{"class":446},[342,23104,23105],{"class":344,"line":469},[342,23106,20019],{"class":446},[13,23108,23109],{},[7602,23110],{"alt":338,"src":23111},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f9b5dd9f1cb7a53b.png",[26,23113,23115],{"id":23114},"add-active-column","Add 'active' column",[333,23117,23119],{"className":1246,"code":23118,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cva-switch class=\"user-active\" v-model=\"user.active\" size=\"small\" \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { VaSwitch } from 'vuestic-ui'\nimport { computed, reactive } from 'vue'\nimport { User } from '..\u002Fdata\u002Fusers'\n\nconst props = defineProps\u003C{params: {data: User}}>()\n\n\u002F\u002F AG Grid strips reactivity for some reason, so we have to force it back.\nconst user = computed(() => reactive(props.params.data))\n\u003C\u002Fscript>\n",[63,23120,23121,23129,23173,23181,23185,23205,23224,23246,23264,23268,23296,23300,23305,23336],{"__ignoreMap":338},[342,23122,23123,23125,23127],{"class":344,"line":345},[342,23124,1023],{"class":446},[342,23126,1522],{"class":496},[342,23128,1280],{"class":446},[342,23130,23131,23133,23136,23138,23140,23142,23145,23147,23150,23152,23154,23157,23159,23162,23164,23166,23169,23171],{"class":344,"line":359},[342,23132,1530],{"class":446},[342,23134,23135],{"class":496},"va-switch",[342,23137,1536],{"class":830},[342,23139,837],{"class":446},[342,23141,566],{"class":446},[342,23143,23144],{"class":352},"user-active",[342,23146,566],{"class":446},[342,23148,23149],{"class":830}," v-model",[342,23151,837],{"class":446},[342,23153,566],{"class":446},[342,23155,23156],{"class":352},"user.active",[342,23158,566],{"class":446},[342,23160,23161],{"class":830}," size",[342,23163,837],{"class":446},[342,23165,566],{"class":446},[342,23167,23168],{"class":352},"small",[342,23170,566],{"class":446},[342,23172,7779],{"class":446},[342,23174,23175,23177,23179],{"class":344,"line":469},[342,23176,1505],{"class":446},[342,23178,1522],{"class":496},[342,23180,1280],{"class":446},[342,23182,23183],{"class":344,"line":476},[342,23184,473],{"emptyLinePlaceholder":472},[342,23186,23187,23189,23191,23193,23195,23197,23199,23201,23203],{"class":344,"line":493},[342,23188,1023],{"class":446},[342,23190,1262],{"class":496},[342,23192,1265],{"class":830},[342,23194,1268],{"class":830},[342,23196,837],{"class":446},[342,23198,566],{"class":446},[342,23200,1275],{"class":352},[342,23202,566],{"class":446},[342,23204,1280],{"class":446},[342,23206,23207,23209,23211,23214,23216,23218,23220,23222],{"class":344,"line":506},[342,23208,443],{"class":442},[342,23210,447],{"class":446},[342,23212,23213],{"class":450}," VaSwitch",[342,23215,454],{"class":446},[342,23217,457],{"class":442},[342,23219,4017],{"class":446},[342,23221,11914],{"class":352},[342,23223,5273],{"class":446},[342,23225,23226,23228,23230,23232,23234,23236,23238,23240,23242,23244],{"class":344,"line":530},[342,23227,443],{"class":442},[342,23229,447],{"class":446},[342,23231,1423],{"class":450},[342,23233,1047],{"class":446},[342,23235,5392],{"class":450},[342,23237,454],{"class":446},[342,23239,457],{"class":442},[342,23241,4017],{"class":446},[342,23243,1248],{"class":352},[342,23245,5273],{"class":446},[342,23247,23248,23250,23252,23254,23256,23258,23260,23262],{"class":344,"line":540},[342,23249,443],{"class":442},[342,23251,447],{"class":446},[342,23253,22691],{"class":450},[342,23255,454],{"class":446},[342,23257,457],{"class":442},[342,23259,4017],{"class":446},[342,23261,22528],{"class":352},[342,23263,5273],{"class":446},[342,23265,23266],{"class":344,"line":571},[342,23267,473],{"emptyLinePlaceholder":472},[342,23269,23270,23272,23274,23276,23278,23280,23282,23284,23286,23288,23290,23292,23294],{"class":344,"line":600},[342,23271,831],{"class":830},[342,23273,8926],{"class":450},[342,23275,837],{"class":446},[342,23277,8931],{"class":362},[342,23279,17923],{"class":446},[342,23281,1927],{"class":496},[342,23283,500],{"class":446},[342,23285,447],{"class":446},[342,23287,9028],{"class":496},[342,23289,500],{"class":446},[342,23291,22691],{"class":348},[342,23293,23009],{"class":446},[342,23295,1112],{"class":450},[342,23297,23298],{"class":344,"line":629},[342,23299,473],{"emptyLinePlaceholder":472},[342,23301,23302],{"class":344,"line":659},[342,23303,23304],{"class":436},"\u002F\u002F AG Grid strips reactivity for some reason, so we have to force it back.\n",[342,23306,23307,23309,23312,23314,23316,23318,23320,23322,23324,23327,23329,23331,23333],{"class":344,"line":688},[342,23308,831],{"class":830},[342,23310,23311],{"class":450}," user ",[342,23313,837],{"class":446},[342,23315,1423],{"class":362},[342,23317,487],{"class":450},[342,23319,941],{"class":446},[342,23321,1011],{"class":830},[342,23323,5392],{"class":362},[342,23325,23326],{"class":450},"(props",[342,23328,158],{"class":446},[342,23330,1927],{"class":450},[342,23332,158],{"class":446},[342,23334,23335],{"class":450},"data))\n",[342,23337,23338,23340,23342],{"class":344,"line":694},[342,23339,1505],{"class":446},[342,23341,1262],{"class":496},[342,23343,1280],{"class":446},[333,23345,23347],{"className":4501,"code":23346,"language":4503,"meta":338,"style":338},"const columnDefs = [\n    ...\n  { field: 'active', cellRenderer: UserActive, width: 75 },\n",[63,23348,23349,23359,23363],{"__ignoreMap":338},[342,23350,23351,23353,23355,23357],{"class":344,"line":345},[342,23352,831],{"class":830},[342,23354,22593],{"class":450},[342,23356,837],{"class":446},[342,23358,6554],{"class":450},[342,23360,23361],{"class":344,"line":359},[342,23362,20099],{"class":446},[342,23364,23365,23367,23369,23371,23373,23376,23378,23380,23382,23384,23387,23389,23391,23393,23396],{"class":344,"line":469},[342,23366,22602],{"class":446},[342,23368,22605],{"class":496},[342,23370,500],{"class":446},[342,23372,4017],{"class":446},[342,23374,23375],{"class":352},"active",[342,23377,3229],{"class":446},[342,23379,1047],{"class":446},[342,23381,23086],{"class":496},[342,23383,500],{"class":446},[342,23385,23386],{"class":450}," UserActive",[342,23388,1047],{"class":446},[342,23390,7669],{"class":496},[342,23392,500],{"class":446},[342,23394,23395],{"class":1208}," 75",[342,23397,4025],{"class":446},[13,23399,23400],{},[7602,23401],{"alt":338,"src":23402},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f9a90f6a576ee140.png",[333,23404,23406],{"className":4501,"code":23405,"language":4503,"meta":338,"style":338},"const active = computed(() => props.params.data.active)\nconst setActive = (value) => api.setActive(props.params.data.id, value)\n",[63,23407,23408,23440],{"__ignoreMap":338},[342,23409,23410,23412,23415,23417,23419,23421,23423,23425,23427,23429,23431,23433,23435,23437],{"class":344,"line":345},[342,23411,831],{"class":830},[342,23413,23414],{"class":450}," active ",[342,23416,837],{"class":446},[342,23418,1423],{"class":362},[342,23420,487],{"class":450},[342,23422,941],{"class":446},[342,23424,1011],{"class":830},[342,23426,13025],{"class":450},[342,23428,158],{"class":446},[342,23430,1927],{"class":450},[342,23432,158],{"class":446},[342,23434,9028],{"class":450},[342,23436,158],{"class":446},[342,23438,23439],{"class":450},"active)\n",[342,23441,23442,23444,23447,23449,23451,23453,23455,23457,23460,23462,23465,23467,23469,23471,23473,23475,23477,23479,23481],{"class":344,"line":359},[342,23443,831],{"class":830},[342,23445,23446],{"class":450}," setActive ",[342,23448,837],{"class":446},[342,23450,1138],{"class":446},[342,23452,1148],{"class":2819},[342,23454,294],{"class":446},[342,23456,1011],{"class":830},[342,23458,23459],{"class":450}," api",[342,23461,158],{"class":446},[342,23463,23464],{"class":362},"setActive",[342,23466,23326],{"class":450},[342,23468,158],{"class":446},[342,23470,1927],{"class":450},[342,23472,158],{"class":446},[342,23474,9028],{"class":450},[342,23476,158],{"class":446},[342,23478,1218],{"class":450},[342,23480,1047],{"class":446},[342,23482,23483],{"class":450}," value)\n",[333,23485,23488],{"className":23486,"code":23487,"language":374},[372],"\u003Cva-switch :modelValue=\"active\" @modelValue:update=\"setActive\" \u002F>\n",[63,23489,23487],{"__ignoreMap":338},[26,23491,23493],{"id":23492},"implement-sorting-for-all-columns","Implement sorting for all columns",[333,23495,23497],{"className":4501,"code":23496,"language":4503,"meta":338,"style":338},"const columnDefs = [\n  { field: 'image', headerName: '', cellRenderer: UserImage, width: 65 },\n  { field: 'name', sortable: true },\n  { field: 'email', sortable: true },\n  { field: 'active', cellRenderer: UserActive, width: 75, sortable: true },\n]\n",[63,23498,23499,23509,23550,23575,23599,23639],{"__ignoreMap":338},[342,23500,23501,23503,23505,23507],{"class":344,"line":345},[342,23502,831],{"class":830},[342,23504,22593],{"class":450},[342,23506,837],{"class":446},[342,23508,6554],{"class":450},[342,23510,23511,23513,23515,23517,23519,23521,23523,23525,23528,23530,23532,23534,23536,23538,23540,23542,23544,23546,23548],{"class":344,"line":359},[342,23512,22602],{"class":446},[342,23514,22605],{"class":496},[342,23516,500],{"class":446},[342,23518,4017],{"class":446},[342,23520,23079],{"class":352},[342,23522,3229],{"class":446},[342,23524,1047],{"class":446},[342,23526,23527],{"class":496}," headerName",[342,23529,500],{"class":446},[342,23531,4540],{"class":446},[342,23533,1047],{"class":446},[342,23535,23086],{"class":496},[342,23537,500],{"class":446},[342,23539,23091],{"class":450},[342,23541,1047],{"class":446},[342,23543,7669],{"class":496},[342,23545,500],{"class":446},[342,23547,23100],{"class":1208},[342,23549,4025],{"class":446},[342,23551,23552,23554,23556,23558,23560,23562,23564,23566,23569,23571,23573],{"class":344,"line":469},[342,23553,22602],{"class":446},[342,23555,22605],{"class":496},[342,23557,500],{"class":446},[342,23559,4017],{"class":446},[342,23561,4582],{"class":352},[342,23563,3229],{"class":446},[342,23565,1047],{"class":446},[342,23567,23568],{"class":496}," sortable",[342,23570,500],{"class":446},[342,23572,6343],{"class":3603},[342,23574,4025],{"class":446},[342,23576,23577,23579,23581,23583,23585,23587,23589,23591,23593,23595,23597],{"class":344,"line":476},[342,23578,22602],{"class":446},[342,23580,22605],{"class":496},[342,23582,500],{"class":446},[342,23584,4017],{"class":446},[342,23586,4590],{"class":352},[342,23588,3229],{"class":446},[342,23590,1047],{"class":446},[342,23592,23568],{"class":496},[342,23594,500],{"class":446},[342,23596,6343],{"class":3603},[342,23598,4025],{"class":446},[342,23600,23601,23603,23605,23607,23609,23611,23613,23615,23617,23619,23621,23623,23625,23627,23629,23631,23633,23635,23637],{"class":344,"line":493},[342,23602,22602],{"class":446},[342,23604,22605],{"class":496},[342,23606,500],{"class":446},[342,23608,4017],{"class":446},[342,23610,23375],{"class":352},[342,23612,3229],{"class":446},[342,23614,1047],{"class":446},[342,23616,23086],{"class":496},[342,23618,500],{"class":446},[342,23620,23386],{"class":450},[342,23622,1047],{"class":446},[342,23624,7669],{"class":496},[342,23626,500],{"class":446},[342,23628,23395],{"class":1208},[342,23630,1047],{"class":446},[342,23632,23568],{"class":496},[342,23634,500],{"class":446},[342,23636,6343],{"class":3603},[342,23638,4025],{"class":446},[342,23640,23641],{"class":344,"line":506},[342,23642,3699],{"class":450},[13,23644,23645],{},[7602,23646],{"alt":338,"src":23647},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f9c30e2557daa426.png",[26,23649,23651],{"id":23650},"implement-full-text-filtering","Implement full text filtering",[333,23653,23655],{"className":1246,"code":23654,"language":1248,"meta":338,"style":338},"\n\u003Ctemplate>\n  \u003Cva-input\n    v-model=\"filter\"\n    placeholder=\"Filter...\"\n  \u002F>\n  \u003Cdiv style=\"height: 300px; width: 100%;\">\n    \u003Cag-grid-vue\n      class=\"ag-theme-vuestic\"\n      style=\"width: 100%; height: 100%;\"\n      :columnDefs=\"columnDefs\"\n      :rowData=\"reactiveUsers\"\n      :isExternalFilterPresent=\"() => true\"\n      :doesExternalFilterPass=\"doesExternalFilterPass\"\n      @grid-ready=\"onGridReady\"\n      :modules=\"modules\"\n    \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\n\n...\n\nconst filter = ref('')\nlet gridApi: any = null\n\nconst onGridReady = (params: any) => {\n  gridApi = params.api\n}\n\nwatch(() => filter.value, () => {\n  gridApi.onFilterChanged()\n})\n\nconst doesExternalFilterPass = ({ data: user }: {user: User}) => {\n  return JSON.stringify(user).includes(filter.value)\n}\n\n...\n",[63,23656,23657,23661,23669,23676,23689,23703,23708,23726,23732,23744,23756,23768,23780,23794,23808,23822,23834,23838,23846,23854,23858,23878,23882,23886,23890,23907,23924,23928,23951,23966,23970,23974,24000,24011,24017,24021,24059,24091,24095,24099],{"__ignoreMap":338},[342,23658,23659],{"class":344,"line":345},[342,23660,473],{"emptyLinePlaceholder":472},[342,23662,23663,23665,23667],{"class":344,"line":359},[342,23664,1023],{"class":446},[342,23666,1522],{"class":496},[342,23668,1280],{"class":446},[342,23670,23671,23673],{"class":344,"line":469},[342,23672,1530],{"class":446},[342,23674,23675],{"class":496},"va-input\n",[342,23677,23678,23681,23683,23685,23687],{"class":344,"line":476},[342,23679,23680],{"class":830},"    v-model",[342,23682,837],{"class":446},[342,23684,566],{"class":446},[342,23686,14311],{"class":352},[342,23688,466],{"class":446},[342,23690,23691,23694,23696,23698,23701],{"class":344,"line":493},[342,23692,23693],{"class":830},"    placeholder",[342,23695,837],{"class":446},[342,23697,566],{"class":446},[342,23699,23700],{"class":352},"Filter...",[342,23702,466],{"class":446},[342,23704,23705],{"class":344,"line":506},[342,23706,23707],{"class":446},"  \u002F>\n",[342,23709,23710,23712,23714,23716,23718,23720,23722,23724],{"class":344,"line":530},[342,23711,1530],{"class":446},[342,23713,1533],{"class":496},[342,23715,7882],{"class":830},[342,23717,837],{"class":446},[342,23719,566],{"class":446},[342,23721,22339],{"class":352},[342,23723,566],{"class":446},[342,23725,1280],{"class":446},[342,23727,23728,23730],{"class":344,"line":540},[342,23729,1553],{"class":446},[342,23731,22350],{"class":496},[342,23733,23734,23736,23738,23740,23742],{"class":344,"line":571},[342,23735,19674],{"class":830},[342,23737,837],{"class":446},[342,23739,566],{"class":446},[342,23741,22361],{"class":352},[342,23743,466],{"class":446},[342,23745,23746,23748,23750,23752,23754],{"class":344,"line":600},[342,23747,22368],{"class":830},[342,23749,837],{"class":446},[342,23751,566],{"class":446},[342,23753,22375],{"class":352},[342,23755,466],{"class":446},[342,23757,23758,23760,23762,23764,23766],{"class":344,"line":629},[342,23759,22382],{"class":830},[342,23761,837],{"class":446},[342,23763,566],{"class":446},[342,23765,22389],{"class":352},[342,23767,466],{"class":446},[342,23769,23770,23772,23774,23776,23778],{"class":344,"line":659},[342,23771,22396],{"class":830},[342,23773,837],{"class":446},[342,23775,566],{"class":446},[342,23777,22403],{"class":352},[342,23779,466],{"class":446},[342,23781,23782,23785,23787,23789,23792],{"class":344,"line":688},[342,23783,23784],{"class":830},"      :isExternalFilterPresent",[342,23786,837],{"class":446},[342,23788,566],{"class":446},[342,23790,23791],{"class":352},"() => true",[342,23793,466],{"class":446},[342,23795,23796,23799,23801,23803,23806],{"class":344,"line":694},[342,23797,23798],{"class":830},"      :doesExternalFilterPass",[342,23800,837],{"class":446},[342,23802,566],{"class":446},[342,23804,23805],{"class":352},"doesExternalFilterPass",[342,23807,466],{"class":446},[342,23809,23810,23813,23815,23817,23820],{"class":344,"line":700},[342,23811,23812],{"class":830},"      @grid-ready",[342,23814,837],{"class":446},[342,23816,566],{"class":446},[342,23818,23819],{"class":352},"onGridReady",[342,23821,466],{"class":446},[342,23823,23824,23826,23828,23830,23832],{"class":344,"line":1461},[342,23825,22410],{"class":830},[342,23827,837],{"class":446},[342,23829,566],{"class":446},[342,23831,22417],{"class":352},[342,23833,466],{"class":446},[342,23835,23836],{"class":344,"line":1478},[342,23837,22424],{"class":446},[342,23839,23840,23842,23844],{"class":344,"line":1495},[342,23841,1803],{"class":446},[342,23843,1533],{"class":496},[342,23845,1280],{"class":446},[342,23847,23848,23850,23852],{"class":344,"line":1502},[342,23849,1505],{"class":446},[342,23851,1522],{"class":496},[342,23853,1280],{"class":446},[342,23855,23856],{"class":344,"line":1512},[342,23857,473],{"emptyLinePlaceholder":472},[342,23859,23860,23862,23864,23866,23868,23870,23872,23874,23876],{"class":344,"line":1517},[342,23861,1023],{"class":446},[342,23863,1262],{"class":496},[342,23865,1265],{"class":830},[342,23867,1268],{"class":830},[342,23869,837],{"class":446},[342,23871,566],{"class":446},[342,23873,1275],{"class":352},[342,23875,566],{"class":446},[342,23877,1280],{"class":446},[342,23879,23880],{"class":344,"line":1527},[342,23881,473],{"emptyLinePlaceholder":472},[342,23883,23884],{"class":344,"line":1550},[342,23885,22860],{"class":446},[342,23887,23888],{"class":344,"line":1581},[342,23889,473],{"emptyLinePlaceholder":472},[342,23891,23892,23894,23897,23899,23901,23903,23905],{"class":344,"line":1601},[342,23893,831],{"class":830},[342,23895,23896],{"class":450}," filter ",[342,23898,837],{"class":446},[342,23900,2132],{"class":362},[342,23902,487],{"class":450},[342,23904,4712],{"class":446},[342,23906,706],{"class":450},[342,23908,23909,23912,23915,23917,23920,23922],{"class":344,"line":1610},[342,23910,23911],{"class":830},"let",[342,23913,23914],{"class":450}," gridApi",[342,23916,500],{"class":446},[342,23918,23919],{"class":348}," any",[342,23921,1107],{"class":446},[342,23923,1224],{"class":446},[342,23925,23926],{"class":344,"line":1625},[342,23927,473],{"emptyLinePlaceholder":472},[342,23929,23930,23932,23935,23937,23939,23941,23943,23945,23947,23949],{"class":344,"line":1640},[342,23931,831],{"class":830},[342,23933,23934],{"class":450}," onGridReady ",[342,23936,837],{"class":446},[342,23938,1138],{"class":446},[342,23940,1927],{"class":2819},[342,23942,500],{"class":446},[342,23944,23919],{"class":348},[342,23946,294],{"class":446},[342,23948,1011],{"class":830},[342,23950,503],{"class":446},[342,23952,23953,23956,23958,23961,23963],{"class":344,"line":1655},[342,23954,23955],{"class":450},"  gridApi",[342,23957,1107],{"class":446},[342,23959,23960],{"class":450}," params",[342,23962,158],{"class":446},[342,23964,23965],{"class":450},"api\n",[342,23967,23968],{"class":344,"line":1670},[342,23969,956],{"class":446},[342,23971,23972],{"class":344,"line":1676},[342,23973,473],{"emptyLinePlaceholder":472},[342,23975,23976,23979,23981,23983,23985,23988,23990,23992,23994,23996,23998],{"class":344,"line":1685},[342,23977,23978],{"class":362},"watch",[342,23980,487],{"class":450},[342,23982,941],{"class":446},[342,23984,1011],{"class":830},[342,23986,23987],{"class":450}," filter",[342,23989,158],{"class":446},[342,23991,1148],{"class":450},[342,23993,1047],{"class":446},[342,23995,1008],{"class":446},[342,23997,1011],{"class":830},[342,23999,503],{"class":446},[342,24001,24002,24004,24006,24009],{"class":344,"line":1700},[342,24003,23955],{"class":450},[342,24005,158],{"class":446},[342,24007,24008],{"class":362},"onFilterChanged",[342,24010,1112],{"class":496},[342,24012,24013,24015],{"class":344,"line":1714},[342,24014,703],{"class":446},[342,24016,706],{"class":450},[342,24018,24019],{"class":344,"line":1729},[342,24020,473],{"emptyLinePlaceholder":472},[342,24022,24023,24025,24028,24030,24033,24035,24037,24040,24043,24045,24048,24050,24052,24055,24057],{"class":344,"line":1744},[342,24024,831],{"class":830},[342,24026,24027],{"class":450}," doesExternalFilterPass ",[342,24029,837],{"class":446},[342,24031,24032],{"class":446}," ({",[342,24034,1432],{"class":496},[342,24036,500],{"class":446},[342,24038,24039],{"class":2819}," user",[342,24041,24042],{"class":446}," }:",[342,24044,447],{"class":446},[342,24046,24047],{"class":496},"user",[342,24049,500],{"class":446},[342,24051,22691],{"class":348},[342,24053,24054],{"class":446},"})",[342,24056,1011],{"class":830},[342,24058,503],{"class":446},[342,24060,24061,24063,24066,24068,24071,24073,24075,24077,24079,24081,24083,24085,24087,24089],{"class":344,"line":1750},[342,24062,948],{"class":442},[342,24064,24065],{"class":450}," JSON",[342,24067,158],{"class":446},[342,24069,24070],{"class":362},"stringify",[342,24072,487],{"class":496},[342,24074,24047],{"class":450},[342,24076,294],{"class":496},[342,24078,158],{"class":446},[342,24080,4595],{"class":362},[342,24082,487],{"class":496},[342,24084,14311],{"class":450},[342,24086,158],{"class":446},[342,24088,1148],{"class":450},[342,24090,706],{"class":496},[342,24092,24093],{"class":344,"line":1779},[342,24094,956],{"class":446},[342,24096,24097],{"class":344,"line":1790},[342,24098,473],{"emptyLinePlaceholder":472},[342,24100,24101],{"class":344,"line":1800},[342,24102,22860],{"class":446},[13,24104,24105],{},[7602,24106],{"alt":338,"src":24107},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f9e2ccd0c4a200c3.png",[26,24109,24111],{"id":24110},"add-create-edit-and-delete-functionality","Add create, edit and delete functionality",[333,24113,24115],{"className":1246,"code":24114,"language":1248,"meta":338,"style":338},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cva-button flat icon=\"edit\" @click=\"editUser(user)\" \u002F>\n    \u003Cva-button flat icon=\"close\" @click=\"removeUser(user)\" \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { VaButton } from 'vuestic-ui'\nimport { computed, reactive } from 'vue'\nimport { User } from '..\u002Fdata\u002Fusers'\n\nconst props = defineProps\u003C{params: {data: User, context: {\n  editUser: (user: User) => {},\n  removeUser: (user: User) => {},\n}}}>()\n\nconst { editUser, removeUser } = props.params.context\n\nconst user = computed(() => reactive(props.params.data))\n\u003C\u002Fscript>\n",[63,24116,24117,24125,24133,24168,24200,24208,24216,24220,24240,24259,24281,24299,24303,24336,24358,24379,24386,24390,24419,24423,24451],{"__ignoreMap":338},[342,24118,24119,24121,24123],{"class":344,"line":345},[342,24120,1023],{"class":446},[342,24122,1522],{"class":496},[342,24124,1280],{"class":446},[342,24126,24127,24129,24131],{"class":344,"line":359},[342,24128,1530],{"class":446},[342,24130,1533],{"class":496},[342,24132,1280],{"class":446},[342,24134,24135,24137,24140,24143,24146,24148,24150,24153,24155,24157,24159,24161,24164,24166],{"class":344,"line":469},[342,24136,1553],{"class":446},[342,24138,24139],{"class":496},"va-button",[342,24141,24142],{"class":830}," flat",[342,24144,24145],{"class":830}," icon",[342,24147,837],{"class":446},[342,24149,566],{"class":446},[342,24151,24152],{"class":352},"edit",[342,24154,566],{"class":446},[342,24156,9670],{"class":830},[342,24158,837],{"class":446},[342,24160,566],{"class":446},[342,24162,24163],{"class":352},"editUser(user)",[342,24165,566],{"class":446},[342,24167,7779],{"class":446},[342,24169,24170,24172,24174,24176,24178,24180,24182,24185,24187,24189,24191,24193,24196,24198],{"class":344,"line":476},[342,24171,1553],{"class":446},[342,24173,24139],{"class":496},[342,24175,24142],{"class":830},[342,24177,24145],{"class":830},[342,24179,837],{"class":446},[342,24181,566],{"class":446},[342,24183,24184],{"class":352},"close",[342,24186,566],{"class":446},[342,24188,9670],{"class":830},[342,24190,837],{"class":446},[342,24192,566],{"class":446},[342,24194,24195],{"class":352},"removeUser(user)",[342,24197,566],{"class":446},[342,24199,7779],{"class":446},[342,24201,24202,24204,24206],{"class":344,"line":493},[342,24203,1803],{"class":446},[342,24205,1533],{"class":496},[342,24207,1280],{"class":446},[342,24209,24210,24212,24214],{"class":344,"line":506},[342,24211,1505],{"class":446},[342,24213,1522],{"class":496},[342,24215,1280],{"class":446},[342,24217,24218],{"class":344,"line":530},[342,24219,473],{"emptyLinePlaceholder":472},[342,24221,24222,24224,24226,24228,24230,24232,24234,24236,24238],{"class":344,"line":540},[342,24223,1023],{"class":446},[342,24225,1262],{"class":496},[342,24227,1265],{"class":830},[342,24229,1268],{"class":830},[342,24231,837],{"class":446},[342,24233,566],{"class":446},[342,24235,1275],{"class":352},[342,24237,566],{"class":446},[342,24239,1280],{"class":446},[342,24241,24242,24244,24246,24249,24251,24253,24255,24257],{"class":344,"line":571},[342,24243,443],{"class":442},[342,24245,447],{"class":446},[342,24247,24248],{"class":450}," VaButton",[342,24250,454],{"class":446},[342,24252,457],{"class":442},[342,24254,4017],{"class":446},[342,24256,11914],{"class":352},[342,24258,5273],{"class":446},[342,24260,24261,24263,24265,24267,24269,24271,24273,24275,24277,24279],{"class":344,"line":600},[342,24262,443],{"class":442},[342,24264,447],{"class":446},[342,24266,1423],{"class":450},[342,24268,1047],{"class":446},[342,24270,5392],{"class":450},[342,24272,454],{"class":446},[342,24274,457],{"class":442},[342,24276,4017],{"class":446},[342,24278,1248],{"class":352},[342,24280,5273],{"class":446},[342,24282,24283,24285,24287,24289,24291,24293,24295,24297],{"class":344,"line":629},[342,24284,443],{"class":442},[342,24286,447],{"class":446},[342,24288,22691],{"class":450},[342,24290,454],{"class":446},[342,24292,457],{"class":442},[342,24294,4017],{"class":446},[342,24296,22528],{"class":352},[342,24298,5273],{"class":446},[342,24300,24301],{"class":344,"line":659},[342,24302,473],{"emptyLinePlaceholder":472},[342,24304,24305,24307,24309,24311,24313,24315,24317,24319,24321,24323,24325,24327,24329,24332,24334],{"class":344,"line":688},[342,24306,831],{"class":830},[342,24308,8926],{"class":450},[342,24310,837],{"class":446},[342,24312,8931],{"class":450},[342,24314,17923],{"class":446},[342,24316,1927],{"class":496},[342,24318,500],{"class":446},[342,24320,447],{"class":446},[342,24322,9028],{"class":496},[342,24324,500],{"class":446},[342,24326,22691],{"class":450},[342,24328,1047],{"class":446},[342,24330,24331],{"class":496}," context",[342,24333,500],{"class":446},[342,24335,503],{"class":446},[342,24337,24338,24341,24343,24345,24347,24349,24351,24353,24355],{"class":344,"line":694},[342,24339,24340],{"class":362},"  editUser",[342,24342,500],{"class":446},[342,24344,1138],{"class":446},[342,24346,24047],{"class":2819},[342,24348,500],{"class":446},[342,24350,22691],{"class":348},[342,24352,294],{"class":446},[342,24354,1011],{"class":830},[342,24356,24357],{"class":446}," {},\n",[342,24359,24360,24363,24365,24367,24369,24371,24373,24375,24377],{"class":344,"line":700},[342,24361,24362],{"class":362},"  removeUser",[342,24364,500],{"class":446},[342,24366,1138],{"class":446},[342,24368,24047],{"class":2819},[342,24370,500],{"class":446},[342,24372,22691],{"class":348},[342,24374,294],{"class":446},[342,24376,1011],{"class":830},[342,24378,24357],{"class":446},[342,24380,24381,24384],{"class":344,"line":1461},[342,24382,24383],{"class":446},"}}}>",[342,24385,1112],{"class":450},[342,24387,24388],{"class":344,"line":1478},[342,24389,473],{"emptyLinePlaceholder":472},[342,24391,24392,24394,24396,24399,24401,24404,24406,24408,24410,24412,24414,24416],{"class":344,"line":1495},[342,24393,831],{"class":830},[342,24395,447],{"class":446},[342,24397,24398],{"class":450}," editUser",[342,24400,1047],{"class":446},[342,24402,24403],{"class":450}," removeUser ",[342,24405,703],{"class":446},[342,24407,1107],{"class":446},[342,24409,13025],{"class":450},[342,24411,158],{"class":446},[342,24413,1927],{"class":450},[342,24415,158],{"class":446},[342,24417,24418],{"class":450},"context\n",[342,24420,24421],{"class":344,"line":1502},[342,24422,473],{"emptyLinePlaceholder":472},[342,24424,24425,24427,24429,24431,24433,24435,24437,24439,24441,24443,24445,24447,24449],{"class":344,"line":1512},[342,24426,831],{"class":830},[342,24428,23311],{"class":450},[342,24430,837],{"class":446},[342,24432,1423],{"class":362},[342,24434,487],{"class":450},[342,24436,941],{"class":446},[342,24438,1011],{"class":830},[342,24440,5392],{"class":362},[342,24442,23326],{"class":450},[342,24444,158],{"class":446},[342,24446,1927],{"class":450},[342,24448,158],{"class":446},[342,24450,23335],{"class":450},[342,24452,24453,24455,24457],{"class":344,"line":1517},[342,24454,1505],{"class":446},[342,24456,1262],{"class":496},[342,24458,1280],{"class":446},[333,24460,24463],{"className":24461,"code":24462,"language":374},[372]," \u003Cva-button @click=\"createUser()\" icon=\"add\" text-color=\"white\" color=\"success\" \u002F>\n\n \u003Cva-modal\n  :model-value=\"!!selectedUser\"\n  ok-text=\"Save\"\n  cancel-text=\"Cancel\"\n  @cancel=\"selectedUser = null\"\n  @ok=\"saveUser()\"\n>\n  \u003Cva-input class=\"mb-3 d-block\" label=\"Image\" v-model=\"selectedUser.image\"\u002F>\n  \u003Cva-input class=\"mb-3 d-block\" label=\"Name\" v-model=\"selectedUser.name\"\u002F>\n  \u003Cva-input class=\"mb-3 d-block\" label=\"Email\" v-model=\"selectedUser.email\"\u002F>\n\u003C\u002Fva-modal>\n",[63,24464,24462],{"__ignoreMap":338},[333,24466,24468],{"className":4501,"code":24467,"language":4503,"meta":338,"style":338}," const editUser = (user: User) => {\n  selectedUser.value = { ...user }\n}\n\nconst removeUser = (userToRemove: User) => {\n  reactiveUsers.value = reactiveUsers.value.filter(user => user !== userToRemove)\n}\n\nconst createUser = () => {\n  selectedUser.value = {\n    id: Math.ceil(Math.random()*10000),\n    image: 'https:\u002F\u002Fpicsum.photos\u002Fid\u002F100\u002F50\u002F50',\n    name: '',\n    email: '',\n    active: true,\n  }\n}\n\nconst saveUser = () => {\n  const currentUser = selectedUser.value\n  selectedUser.value = null\n\n  if (!currentUser) {\n    return\n  }\n\n  const users = reactiveUsers.value\n  const existingUser = users.find(user => user.id === currentUser.id)\n\n  if (existingUser) {\n    reactiveUsers.value = reactiveUsers.value.map(user => existingUser === user ? currentUser : user)\n  } else {\n    reactiveUsers.value.push(currentUser)\n  }\n}\n\nconst agGridContext = {\n  editUser,\n  removeUser,\n}\n",[63,24469,24470,24493,24512,24516,24520,24543,24581,24585,24589,24604,24616,24652,24667,24677,24687,24697,24701,24705,24709,24724,24740,24752,24756,24771,24776,24780,24784,24798,24837,24841,24854,24899,24908,24927,24931,24935,24939,24950,24956,24962],{"__ignoreMap":338},[342,24471,24472,24474,24477,24479,24481,24483,24485,24487,24489,24491],{"class":344,"line":345},[342,24473,1000],{"class":830},[342,24475,24476],{"class":450}," editUser ",[342,24478,837],{"class":446},[342,24480,1138],{"class":446},[342,24482,24047],{"class":2819},[342,24484,500],{"class":446},[342,24486,22691],{"class":348},[342,24488,294],{"class":446},[342,24490,1011],{"class":830},[342,24492,503],{"class":446},[342,24494,24495,24498,24500,24502,24504,24506,24508,24510],{"class":344,"line":359},[342,24496,24497],{"class":450},"  selectedUser",[342,24499,158],{"class":446},[342,24501,1148],{"class":450},[342,24503,1107],{"class":446},[342,24505,447],{"class":446},[342,24507,10193],{"class":446},[342,24509,24047],{"class":450},[342,24511,2929],{"class":446},[342,24513,24514],{"class":344,"line":469},[342,24515,956],{"class":446},[342,24517,24518],{"class":344,"line":476},[342,24519,473],{"emptyLinePlaceholder":472},[342,24521,24522,24524,24526,24528,24530,24533,24535,24537,24539,24541],{"class":344,"line":493},[342,24523,831],{"class":830},[342,24525,24403],{"class":450},[342,24527,837],{"class":446},[342,24529,1138],{"class":446},[342,24531,24532],{"class":2819},"userToRemove",[342,24534,500],{"class":446},[342,24536,22691],{"class":348},[342,24538,294],{"class":446},[342,24540,1011],{"class":830},[342,24542,503],{"class":446},[342,24544,24545,24548,24550,24552,24554,24557,24559,24561,24563,24565,24567,24569,24571,24573,24576,24579],{"class":344,"line":506},[342,24546,24547],{"class":450},"  reactiveUsers",[342,24549,158],{"class":446},[342,24551,1148],{"class":450},[342,24553,1107],{"class":446},[342,24555,24556],{"class":450}," reactiveUsers",[342,24558,158],{"class":446},[342,24560,1148],{"class":450},[342,24562,158],{"class":446},[342,24564,14311],{"class":362},[342,24566,487],{"class":496},[342,24568,24047],{"class":2819},[342,24570,1011],{"class":830},[342,24572,24039],{"class":450},[342,24574,24575],{"class":446}," !==",[342,24577,24578],{"class":450}," userToRemove",[342,24580,706],{"class":496},[342,24582,24583],{"class":344,"line":530},[342,24584,956],{"class":446},[342,24586,24587],{"class":344,"line":540},[342,24588,473],{"emptyLinePlaceholder":472},[342,24590,24591,24593,24596,24598,24600,24602],{"class":344,"line":571},[342,24592,831],{"class":830},[342,24594,24595],{"class":450}," createUser ",[342,24597,837],{"class":446},[342,24599,1008],{"class":446},[342,24601,1011],{"class":830},[342,24603,503],{"class":446},[342,24605,24606,24608,24610,24612,24614],{"class":344,"line":600},[342,24607,24497],{"class":450},[342,24609,158],{"class":446},[342,24611,1148],{"class":450},[342,24613,1107],{"class":446},[342,24615,503],{"class":446},[342,24617,24618,24620,24622,24625,24627,24630,24632,24635,24637,24640,24642,24645,24648,24650],{"class":344,"line":629},[342,24619,22787],{"class":496},[342,24621,500],{"class":446},[342,24623,24624],{"class":450}," Math",[342,24626,158],{"class":446},[342,24628,24629],{"class":362},"ceil",[342,24631,487],{"class":496},[342,24633,24634],{"class":450},"Math",[342,24636,158],{"class":446},[342,24638,24639],{"class":362},"random",[342,24641,941],{"class":496},[342,24643,24644],{"class":446},"*",[342,24646,24647],{"class":1208},"10000",[342,24649,294],{"class":496},[342,24651,527],{"class":446},[342,24653,24654,24656,24658,24660,24663,24665],{"class":344,"line":659},[342,24655,22798],{"class":496},[342,24657,500],{"class":446},[342,24659,4017],{"class":446},[342,24661,24662],{"class":352},"https:\u002F\u002Fpicsum.photos\u002Fid\u002F100\u002F50\u002F50",[342,24664,3229],{"class":446},[342,24666,527],{"class":446},[342,24668,24669,24671,24673,24675],{"class":344,"line":688},[342,24670,14023],{"class":496},[342,24672,500],{"class":446},[342,24674,4540],{"class":446},[342,24676,527],{"class":446},[342,24678,24679,24681,24683,24685],{"class":344,"line":694},[342,24680,22829],{"class":496},[342,24682,500],{"class":446},[342,24684,4540],{"class":446},[342,24686,527],{"class":446},[342,24688,24689,24691,24693,24695],{"class":344,"line":700},[342,24690,22845],{"class":496},[342,24692,500],{"class":446},[342,24694,6343],{"class":3603},[342,24696,527],{"class":446},[342,24698,24699],{"class":344,"line":1461},[342,24700,1229],{"class":446},[342,24702,24703],{"class":344,"line":1478},[342,24704,956],{"class":446},[342,24706,24707],{"class":344,"line":1495},[342,24708,473],{"emptyLinePlaceholder":472},[342,24710,24711,24713,24716,24718,24720,24722],{"class":344,"line":1502},[342,24712,831],{"class":830},[342,24714,24715],{"class":450}," saveUser ",[342,24717,837],{"class":446},[342,24719,1008],{"class":446},[342,24721,1011],{"class":830},[342,24723,503],{"class":446},[342,24725,24726,24728,24731,24733,24736,24738],{"class":344,"line":1512},[342,24727,1101],{"class":830},[342,24729,24730],{"class":450}," currentUser",[342,24732,1107],{"class":446},[342,24734,24735],{"class":450}," selectedUser",[342,24737,158],{"class":446},[342,24739,2705],{"class":450},[342,24741,24742,24744,24746,24748,24750],{"class":344,"line":1517},[342,24743,24497],{"class":450},[342,24745,158],{"class":446},[342,24747,1148],{"class":450},[342,24749,1107],{"class":446},[342,24751,1224],{"class":446},[342,24753,24754],{"class":344,"line":1527},[342,24755,473],{"emptyLinePlaceholder":472},[342,24757,24758,24760,24762,24764,24767,24769],{"class":344,"line":1550},[342,24759,1135],{"class":442},[342,24761,1138],{"class":496},[342,24763,1141],{"class":446},[342,24765,24766],{"class":450},"currentUser",[342,24768,1151],{"class":496},[342,24770,490],{"class":446},[342,24772,24773],{"class":344,"line":1581},[342,24774,24775],{"class":442},"    return\n",[342,24777,24778],{"class":344,"line":1601},[342,24779,1229],{"class":446},[342,24781,24782],{"class":344,"line":1610},[342,24783,473],{"emptyLinePlaceholder":472},[342,24785,24786,24788,24790,24792,24794,24796],{"class":344,"line":1625},[342,24787,1101],{"class":830},[342,24789,22519],{"class":450},[342,24791,1107],{"class":446},[342,24793,24556],{"class":450},[342,24795,158],{"class":446},[342,24797,2705],{"class":450},[342,24799,24800,24802,24805,24807,24809,24811,24814,24816,24818,24820,24822,24824,24826,24829,24831,24833,24835],{"class":344,"line":1640},[342,24801,1101],{"class":830},[342,24803,24804],{"class":450}," existingUser",[342,24806,1107],{"class":446},[342,24808,22519],{"class":450},[342,24810,158],{"class":446},[342,24812,24813],{"class":362},"find",[342,24815,487],{"class":496},[342,24817,24047],{"class":2819},[342,24819,1011],{"class":830},[342,24821,24039],{"class":450},[342,24823,158],{"class":446},[342,24825,1218],{"class":450},[342,24827,24828],{"class":446}," ===",[342,24830,24730],{"class":450},[342,24832,158],{"class":446},[342,24834,1218],{"class":450},[342,24836,706],{"class":496},[342,24838,24839],{"class":344,"line":1655},[342,24840,473],{"emptyLinePlaceholder":472},[342,24842,24843,24845,24847,24850,24852],{"class":344,"line":1670},[342,24844,1135],{"class":442},[342,24846,1138],{"class":496},[342,24848,24849],{"class":450},"existingUser",[342,24851,1151],{"class":496},[342,24853,490],{"class":446},[342,24855,24856,24859,24861,24863,24865,24867,24869,24871,24873,24876,24878,24880,24882,24884,24886,24888,24891,24893,24895,24897],{"class":344,"line":1676},[342,24857,24858],{"class":450},"    reactiveUsers",[342,24860,158],{"class":446},[342,24862,1148],{"class":450},[342,24864,1107],{"class":446},[342,24866,24556],{"class":450},[342,24868,158],{"class":446},[342,24870,1148],{"class":450},[342,24872,158],{"class":446},[342,24874,24875],{"class":362},"map",[342,24877,487],{"class":496},[342,24879,24047],{"class":2819},[342,24881,1011],{"class":830},[342,24883,24804],{"class":450},[342,24885,24828],{"class":446},[342,24887,24039],{"class":450},[342,24889,24890],{"class":446}," ?",[342,24892,24730],{"class":450},[342,24894,10239],{"class":446},[342,24896,24039],{"class":450},[342,24898,706],{"class":496},[342,24900,24901,24903,24906],{"class":344,"line":1685},[342,24902,1401],{"class":446},[342,24904,24905],{"class":442}," else",[342,24907,503],{"class":446},[342,24909,24910,24912,24914,24916,24918,24921,24923,24925],{"class":344,"line":1700},[342,24911,24858],{"class":450},[342,24913,158],{"class":446},[342,24915,1148],{"class":450},[342,24917,158],{"class":446},[342,24919,24920],{"class":362},"push",[342,24922,487],{"class":496},[342,24924,24766],{"class":450},[342,24926,706],{"class":496},[342,24928,24929],{"class":344,"line":1714},[342,24930,1229],{"class":446},[342,24932,24933],{"class":344,"line":1729},[342,24934,956],{"class":446},[342,24936,24937],{"class":344,"line":1744},[342,24938,473],{"emptyLinePlaceholder":472},[342,24940,24941,24943,24946,24948],{"class":344,"line":1750},[342,24942,831],{"class":830},[342,24944,24945],{"class":450}," agGridContext ",[342,24947,837],{"class":446},[342,24949,503],{"class":446},[342,24951,24952,24954],{"class":344,"line":1779},[342,24953,24340],{"class":450},[342,24955,527],{"class":446},[342,24957,24958,24960],{"class":344,"line":1790},[342,24959,24362],{"class":450},[342,24961,527],{"class":446},[342,24963,24964],{"class":344,"line":1800},[342,24965,956],{"class":446},[333,24967,24970],{"className":24968,"code":24969,"language":374},[372],"\u003Cag-grid-vue\n   ...\n   :context=\"agGridContext\"\n\u002F>\n",[63,24971,24969],{"__ignoreMap":338},[13,24973,24974],{},[7602,24975],{"alt":338,"src":24976},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f9f48410be1e0023.png",[13,24978,24979],{},[7602,24980],{"alt":338,"src":24981},"\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002F6343f9ff2ecfad1c2db8.png",[26,24983,3106],{"id":3105},[3114,24985],{"label":3116,"to":3117},[3119,24987,24988],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":338,"searchDepth":469,"depth":469,"links":24990},[24991,24992,24993,24994,24995,24996,24997],{"id":22071,"depth":359,"text":22072},{"id":22310,"depth":359,"text":22311},{"id":23114,"depth":359,"text":23115},{"id":23492,"depth":359,"text":23493},{"id":23650,"depth":359,"text":23651},{"id":24110,"depth":359,"text":24111},{"id":3105,"depth":359,"text":3106},"Learn how to integrate AG Grid with Vuestic UI for powerful Vue 3 datatables. Follow our step-by-step tutorial to build feature-rich CRUD tables, implement sorting, filtering, and customization. Explore the best Vue.js open-source tools with Epicmax!","\u002Fimages\u002Fblog\u002Fvuestic-ui-with-ag-grid\u002Fcard.jpg",{},"\u002Fblog\u002Fvuestic-ui-with-ag-grid",{"title":21971,"description":24998},"blog\u002Fvuestic-ui-with-ag-grid","r5Lw3lV8yycbfaV4wfe_uXpcg9wbgyI0Wm2KmKasTl8",1784831291861]