[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"contentNavigation":3,"topic-performance":4,"newsletter-stats":9,"articles-feed-\u002Ftopics\u002Fperformance-1-performance-":11,"$fQa3xUpnRDPNiFFu2QS4K-DAjYmsapeR9fNhPNBdpkOc":-1,"home-tags":290},[],{"articleCount":5,"color":6,"id":7,"name":8,"slug":8},16,"#10b981","019d6bd8-ca26-775c-b9b5-c3439dbe5789","performance",{"confirmedCount":10},405,{"items":12,"page":287,"pageSize":288,"totalCount":289},[13,37,56,84,105,123,145,166,188,208,227,245,266],{"content":14,"createdAt":15,"id":16,"image":17,"isAffiliate":18,"isPublished":18,"publishedAt":19,"slug":20,"sourceId":21,"sourceName":22,"sourceType":23,"summary":24,"title":25,"updatedAt":26,"url":27,"urlHash":28,"tags":29},"Practical techniques for faster Nuxt apps","2026-07-15T08:00:00.738Z","019f64ca-32ca-73f5-b458-bc95f1b37561","https:\u002F\u002Fapi.certificates.dev\u002Fstorage\u002FxXZtFg8h2YcCNVmq93AeSIBqMnZSQXZKETKI6xgm.png",true,"2026-07-15T08:00:00.000Z","performance-optimization-in-nuxt-2","019d9eed-d835-729a-a029-7e6320cb67ed","Certificates.dev","certificatesdev","This article discusses practical techniques to optimize the performance of Nuxt applications, focusing on strategies to enhance speed and efficiency. It provides actionable insights for developers looking to improve their Nuxt app performance.","Performance Optimization in Nuxt","2026-07-15T08:00:22.182Z","https:\u002F\u002Fcertificates.dev\u002Fblog\u002Fperformance-optimization-in-nuxt-1?friend=MOKKAPPS","c25c56ffb5b668a73bb74fff1cecd1339bf8c9bca50b0d4c5bea7375009fc747",[30,33,34],{"color":6,"id":31,"name":32,"slug":32},"019d6bd8-fad3-70a9-a74d-ab96e3a2f45d","nuxt",{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":35,"name":36,"slug":36},"019e3af5-4a35-705c-8ab1-4404b653e0e8","optimization",{"content":14,"createdAt":38,"id":39,"image":40,"isAffiliate":18,"isPublished":18,"publishedAt":41,"slug":42,"sourceId":21,"sourceName":22,"sourceType":23,"summary":43,"title":25,"updatedAt":44,"url":45,"urlHash":46,"tags":47},"2026-07-01T16:00:01.983Z","019f1e68-a398-7614-a565-37bb57fd85c8","https:\u002F\u002Fapi.certificates.dev\u002Fstorage\u002FlAB305vJFYD8SXv60gboztHN0tgrQaSTFlfCfQhq.png","2026-07-01T06:00:00.000Z","performance-optimization-in-nuxt","This article discusses practical techniques for optimizing the performance of Nuxt applications, focusing on methods to achieve faster load times and improved user experience.","2026-07-01T16:00:20.532Z","https:\u002F\u002Fcertificates.dev\u002Fblog\u002Fperformance-optimization-in-nuxt?friend=MOKKAPPS","353723c33e99e065af48d6f8f29950fd1122acbb8fe3b727cf84cf41282804aa",[48,49,50,53],{"color":6,"id":31,"name":32,"slug":32},{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":51,"name":52,"slug":52},"019d6bd8-ca89-735e-a52a-ee53a80a77a9","ssr",{"color":6,"id":54,"name":55,"slug":55},"019e8e36-bd4b-740d-b23a-81858b24025b","ssg",{"content":57,"createdAt":58,"id":59,"image":60,"isAffiliate":61,"isPublished":18,"publishedAt":62,"slug":63,"sourceId":64,"sourceName":65,"sourceType":66,"summary":67,"title":68,"updatedAt":69,"url":70,"urlHash":71,"tags":72},"Performance has become one of the most important aspects of modern frontend development. Users expect websites to be fast and Google rewards fast websites. And yet... even experienced Vue developers still introduce performance issues that can significantly impact user experience. The tricky part? Most applications work perfectly fine during development. Problems usually appear later: larger datasets slower devices poor network conditions increased application complexity In this article, we'll look at 10 common Vue performance mistakes I still see in production applications and how to fix them. Let's dive in. 🤔 Why Vue Performance Matters Vue is already a highly optimized framework. However, even the best framework cannot compensate for inefficient application code. Poor performance can lead to: slow page loads delayed interactions poor Core Web Vitals scores increased battery usage frustrated users The good news? Most performance issues can be fixed with relatively small changes. 🟢 Mistake #1: Using Deep Watchers Everywhere A common mistake is enabling deep watchers on large objects. Example: watch( userData, () =&gt; { saveDraft() }, { deep: true } ) Deep watchers force Vue to traverse the entire object tree. For large datasets this can become very expensive. Instead: watch specific properties split large objects use computed values when possible 🟢 Mistake #2: Making Everything Reactive Not every piece of data needs reactivity. I often see code like this: const hugeDataset = ref(largeArray) When the data rarely changes, Vue still needs to create reactive proxies. For large collections this introduces unnecessary overhead. A better approach: const hugeDataset = shallowRef(largeArray) Or even: const hugeDataset = markRaw(largeArray) when reactivity isn't needed at all. 🟢 Mistake #3: Creating New Objects Inside Computed Properties Consider this: const userInfo = computed(() =&gt; ({ name: user.value.name, role: user.value.role })) A brand-new object is created every time the computed runs. This can trigger unnecessary component updates. Instead, prefer returning primitives when possible or memoizing expensive transformations. 🟢 Mistake #4: Using v-if Instead of v-show for Frequently Toggled Elements Many developers use: &lt;div v-if=\"isOpen\"&gt; Content &lt;\u002Fdiv&gt; But if the element is shown and hidden frequently, Vue must repeatedly: mount render destroy A better option: &lt;div v-show=\"isOpen\"&gt; Content &lt;\u002Fdiv&gt; This simply toggles CSS visibility. For frequently toggled UI elements, it's usually much faster. 🟢 Mistake #5: Rendering Huge Lists Without Virtualization Rendering thousands of DOM nodes is expensive. Example: &lt;div v-for=\"user in users\" :key=\"user.id\" &gt; {{ user.name }} &lt;\u002Fdiv&gt; This might work with 100 items. It won't feel great with 10,000. Instead consider: virtual scrolling pagination infinite loading Libraries like Vue Virtual Scroller can dramatically improve performance. 🟢 Mistake #6: Lazy Loading Nothing Many applications ship their entire codebase on the first page load. Example: import UserSettings from '.\u002FUserSettings.vue' This increases: bundle size download time parse time Instead: const UserSettings = defineAsyncComponent( () =&gt; import('.\u002FUserSettings.vue') ) Users only download code when it's actually needed. 🟢 Mistake #7: Fetching Data Sequentially A surprisingly common issue: const users = await fetchUsers() const posts = await fetchPosts() const comments = await fetchComments() Each request waits for the previous one. A faster approach: const [users, posts, comments] = await Promise.all([ fetchUsers(), fetchPosts(), fetchComments() ]) This can reduce loading times significantly. 🟢 Mistake #8: Forgetting About Image Optimization Images are often the largest assets on a page. Yet many applications still serve: oversized images uncompressed formats images outside the viewport For Vue and Nuxt applications: use WebP or AVIF lazy load images generate responsive sizes Image optimization frequently provides the biggest performance wins. 🟢 Mistake #9: Ignoring Component Re-Renders A component may render far more often than expected. For example: &lt;ExpensiveChart :data=\"chartData\" \u002F&gt; If chartData changes reference on every update, the chart keeps re-rendering. Common solutions: stabilize references use shallowRef avoid unnecessary reactive updates profile components with Vue DevTools Small changes here can have a huge impact. 🟢 Mistake #10: Never Measuring Performance The biggest mistake? Not measuring anything. Many teams optimize blindly. Instead, regularly check: Lighthouse Core Web Vitals Vue DevTools Chrome Performance Panel Network waterfalls Performance work should be data-driven. You can't improve what you don't measure. 🟢 Performance Checklist Before shipping a Vue application, ask yourself: ✅ Am I using deep watchers only when necessary? ✅ Do all objects really need reactivity? ✅ Are large lists virtualized? ✅ Are routes and components lazy loaded? ✅ Are API requests running in parallel? ✅ Are images optimized? ✅ Have I measured actual performance? If any answer is \"no\", there may be easy performance wins available. 🧪 Best Practices Use shallowRef for large datasets Avoid deep watchers whenever possible Lazy load routes and heavy components Virtualize large lists Optimize images aggressively Profile real-world user flows Monitor Core Web Vitals Measure before and after every optimization 📖 Learn more If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below: It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉 🧪 Advance skills A certification boosts your skills, builds credibility, and opens doors to new opportunities. Whether you're advancing your career or switching paths, it's a smart step toward success. Check out Certificates.dev by clicking this link or by clicking the image below: Invest in yourself—get certified in Vue.js, JavaScript, Nuxt, Angular, React, and more! ✅ Summary Vue is fast by default. But performance problems often come from how we use the framework rather than the framework itself. In this article, you learned: 10 common Vue performance mistakes Why they impact real-world applications How to identify them Practical ways to fix them Best practices for building faster Vue apps Many of these issues are easy to overlook during development but become expensive at scale. By avoiding these mistakes and measuring performance regularly, you can build applications that feel fast, responsive, and enjoyable to use. Take care! And happy coding as always 🖥️","2026-06-15T08:00:09.697Z","019eca4b-8dce-754e-ad17-a0f9b1aef338","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidhps7seqfikj9j24oz3.png",false,"2026-06-15T07:08:48.000Z","10-vue-performance-mistakes-i-still-see-in-production-apps","019d6bd6-7fe0-7244-80dc-9a4e8751886a","Jakub Andrzejewski","rss","This article discusses ten common performance mistakes that Vue developers often make in production applications, highlighting issues such as using deep watchers unnecessarily and making everything reactive. It emphasizes the importance of optimizing performance to enhance user experience and offers practical solutions to improve application efficiency.","10 Vue Performance Mistakes I Still See in Production Apps","2026-06-15T08:00:29.794Z","https:\u002F\u002Fdev.to\u002Fjacobandrewsky\u002F10-vue-performance-mistakes-i-still-see-in-production-apps-52a1","4bad7a0b92210da78523e2dbba139490f2eade4aa9b344635bb1bb7b69b6959a",[73,76,77,78,81],{"color":6,"id":74,"name":75,"slug":75},"019d6bd8-c9a8-7783-bd22-03145b355427","vue",{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":35,"name":36,"slug":36},{"color":6,"id":79,"name":80,"slug":80},"019daac3-2f85-7393-b891-9da3891267ca","reactivity",{"color":6,"id":82,"name":83,"slug":83},"019d6bd9-010e-772d-b2f0-9378a042a676","best-practices",{"content":85,"createdAt":86,"id":87,"image":88,"isAffiliate":61,"isPublished":18,"publishedAt":89,"slug":90,"sourceId":64,"sourceName":65,"sourceType":66,"summary":91,"title":92,"updatedAt":93,"url":94,"urlHash":95,"tags":96},"As frontend applications become more complex, debugging production issues becomes increasingly difficult. A user reports: \"The page feels slow.\" or: \"The dashboard sometimes freezes.\" But when you check your logs... everything looks fine. The problem is that traditional frontend monitoring often tells you what failed, but not why it happened. This is where observability comes in. And one of the most popular tools for modern observability is OpenTelemetry. In this article, we'll explore: What frontend observability is How it differs from traditional monitoring Why OpenTelemetry is becoming the industry standard How to add OpenTelemetry to a Vue application Best practices for collecting useful telemetry Let's dive in. 🤔 What Is Frontend Observability? Observability is the ability to understand what's happening inside your application by collecting telemetry data. Typically, this includes: traces metrics logs Unlike traditional monitoring, observability helps answer questions like: Why is this page slow? Which API request caused the delay? What happened before the error occurred? Which users are affected? Instead of simply knowing that something failed... 👉 You can understand the entire chain of events that led to the problem. 🟢 Why Is Frontend Observability Important? Most teams invest heavily in backend observability. They track: database queries API latency server errors infrastructure metrics But users interact with the frontend. And many issues happen before the request even reaches the backend. For example: slow component rendering blocked main thread failed network requests routing issues third-party script delays Without frontend observability these problems are often invisible. 🟢 What Is OpenTelemetry? OpenTelemetry (often called OTel) is an open-source observability framework used to collect and export telemetry data. It provides a standard way to collect: traces metrics logs And send them to tools such as: Grafana Jaeger Datadog New Relic Honeycomb Elastic The biggest advantage? Vendor-neutral instrumentation. You can change observability providers without rewriting your instrumentation. 🟢 How OpenTelemetry Helps Vue Applications Imagine a user loads a dashboard. Several things happen: Route change ↓ Component mounts ↓ API request starts ↓ Data is fetched ↓ UI renders Normally these events are disconnected. With OpenTelemetry you can trace the entire flow. This helps answer questions like: Which API call slowed down the page? How long did rendering take? Which routes are causing performance issues? 🟢 Setting Up OpenTelemetry in Vue Let's look at a simplified setup. Install the required packages: npm install \\ @opentelemetry\u002Fapi \\ @opentelemetry\u002Fsdk-trace-web \\ @opentelemetry\u002Finstrumentation-fetch \\ @opentelemetry\u002Finstrumentation-xml-http-request Basic initialization import { WebTracerProvider } from '@opentelemetry\u002Fsdk-trace-web' const provider = new WebTracerProvider() provider.register() This creates a tracer that can collect telemetry from the browser. 🟢 Creating Custom Traces One of the most powerful features is custom tracing. Example: import { trace } from '@opentelemetry\u002Fapi' const tracer = trace.getTracer('vue-app') Inside a component: const span = tracer.startSpan('load-users') try { await fetchUsers() } finally { span.end() } Now you'll see exactly how long that operation took. This becomes extremely useful when debugging production issues. 🟢 Tracking Route Performance Vue Router is another excellent place to add tracing. Example: router.beforeEach((to, from, next) =&gt; { performance.mark('navigation-start') next() }) router.afterEach(() =&gt; { performance.measure( 'navigation', 'navigation-start' ) }) Combined with OpenTelemetry exporters, this can help identify slow routes and navigation bottlenecks. 🟢 Monitoring API Requests Many performance problems originate from network calls. OpenTelemetry provides automatic instrumentation for: Fetch API XMLHttpRequest Example: registerInstrumentations({ instrumentations: [ new FetchInstrumentation() ] }) Now requests can automatically generate traces. You can see: request duration failures dependencies between actions without manually instrumenting every API call. 🧪 Best Practices Instrument critical user flows first Trace important API requests Avoid collecting unnecessary telemetry Sample traces in high-traffic applications Correlate frontend traces with backend traces Monitor route transitions and rendering bottlenecks Focus on actionable insights rather than collecting everything 📖 Learn more If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below: It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉 🧪 Advance skills A certification boosts your skills, builds credibility, and opens doors to new opportunities. Whether you're advancing your career or switching paths, it's a smart step toward success. Check out Certificates.dev by clicking this link or by clicking the image below: Invest in yourself—get certified in Vue.js, JavaScript, Nuxt, Angular, React, and more! ✅ Summary Frontend observability helps you understand not just what went wrong, but why it happened. As applications grow in complexity, logs and error tracking alone are often no longer enough. OpenTelemetry gives you deeper visibility into your Vue application, helping you identify bottlenecks, improve performance, and deliver a better user experience. Take care! And happy coding as always 🖥️","2026-06-08T12:00:03.683Z","019ea71a-ac51-7489-bbe0-07745fbb62d0","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiajryi395e201bloufp6.png","2026-06-08T08:59:35.000Z","frontend-observability-in-vue-apps-with-opentelemetry","This article discusses the importance of frontend observability in Vue applications, emphasizing how traditional monitoring falls short in diagnosing performance issues. It introduces OpenTelemetry as a solution for collecting telemetry data, providing insights into application behavior and performance. The article also includes a guide on setting up OpenTelemetry in Vue apps to enhance observability.","Frontend Observability in Vue Apps with OpenTelemetry","2026-06-10T14:35:13.766Z","https:\u002F\u002Fdev.to\u002Fjacobandrewsky\u002Ffrontend-observability-in-vue-apps-with-opentelemetry-3fb0","c8c141c41435740358afc38ce5d160217a0beb69c216de4d15879d516b3259e9",[97,98,101,104],{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":99,"name":100,"slug":100},"019ea71a-dc95-72b8-a7e3-70f9e2ac3710","opentelemetry",{"color":6,"id":102,"name":103,"slug":103},"019ea71a-dc9d-7607-9cfa-df0f31ab5876","observability",{"color":6,"id":7,"name":8,"slug":8},{"content":106,"createdAt":107,"id":108,"image":109,"isAffiliate":18,"isPublished":18,"publishedAt":110,"slug":111,"sourceId":21,"sourceName":22,"sourceType":23,"summary":112,"title":113,"updatedAt":114,"url":115,"urlHash":116,"tags":117},"Vite 8 replaced both esbuild and Rollup with Rolldown. Here's what that means for your Vue project in practice.","2026-05-27T12:00:00.450Z","019e694e-4fa0-7469-bfd2-3f6ebe3d830f","https:\u002F\u002Fapi.certificates.dev\u002Fstorage\u002FS7YSA0njipizzfbLK8yznLoWH5klbT1gMA2tMFUO.png","2026-05-27T06:00:00.000Z","rolldown-and-vite-8-what-changed","Vite 8 introduces Rolldown, replacing esbuild and Rollup, which significantly impacts Vue projects. This change aims to enhance performance and streamline the build process for developers using Vite with Vue.","Rolldown and Vite 8: What Changed","2026-05-27T12:00:17.146Z","https:\u002F\u002Fcertificates.dev\u002Fblog\u002Frolldown-and-vite-8-what-changed?friend=MOKKAPPS","59ea330c63cf36f923ea96c371f9b4a320dc0b74e1159db9b6080693f9bb0af8",[118,121,122],{"color":6,"id":119,"name":120,"slug":120},"019d9cf0-c9f8-7411-beed-02265d8271db","vite",{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":7,"name":8,"slug":8},{"content":124,"createdAt":125,"id":126,"image":127,"isAffiliate":61,"isPublished":18,"publishedAt":128,"slug":129,"sourceId":64,"sourceName":65,"sourceType":66,"summary":130,"title":131,"updatedAt":132,"url":133,"urlHash":134,"tags":135},"When building Vue applications, performance usually feels great by default - Vue’s reactivity system is incredibly efficient. But as applications grow larger, you may eventually run into situations where: components re-render too often large lists become expensive UI updates feel sluggish unnecessary computations happen repeatedly This is where memoization becomes useful. And in Vue, one of the easiest ways to optimize rendering is with v-memo. In this article, we’ll explore: What memoization is What problem it solves How v-memo works in Vue Real-world examples Best practices and common mistakes Let’s dive in. 🤔 What Is Memoization? Memoization is an optimization technique where results are cached and reused instead of recalculating them again. Instead of: recomputing re-rendering recalculating You simply use the cached version. 🟢 What Problem Does Memoization Solve? In reactive applications, updates can trigger many re-renders even when most data has not changed for example: Parent component updates Entire list re-renders Hundreds of child components update unnecessarily This can hurt performance, responsiveness, and even battery usage on mobile devices. Without memoization every update causes new rendering work - even for unchanged content. With memoization Vue can skip rendering when dependencies stay the same which reduces DOM operations, Virtual DOM diffing, and in general component work. 🟢 What Is v-memo in Vue? v-memo is a Vue directive that memoizes part of a template. Basic example: &lt;div v-memo=\"[value]\"&gt; {{ expensiveContent }} &lt;\u002Fdiv&gt; Vue will only re-render this block when value changes. Otherwise Vue reuses the previous rendered result. Let’s imagine a large user list. Without v-memo &lt;script setup lang=\"ts\"&gt; const users = ref([ { id: 1, name: 'John' }, { id: 2, name: 'Alice' } ]) const counter = ref(0) &lt;\u002Fscript&gt; &lt;template&gt; &lt;button @click=\"counter++\"&gt; {{ counter }} &lt;\u002Fbutton&gt; &lt;div v-for=\"user in users\" :key=\"user.id\" &gt; {{ user.name }} &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; Every counter update re-renders the whole list even though users never changed. With v-memo we can: &lt;script setup lang=\"ts\"&gt; const users = ref([ { id: 1, name: 'John' }, { id: 2, name: 'Alice' } ]) const counter = ref(0) &lt;\u002Fscript&gt; &lt;template&gt; &lt;button @click=\"counter++\"&gt; {{ counter }} &lt;\u002Fbutton&gt; &lt;div v-for=\"user in users\" :key=\"user.id\" v-memo=\"[user.id]\" &gt; {{ user.name }} &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; And now counter updates still happen but unchanged list items are skipped. This becomes extremely valuable for large lists, dashboards, or tables. 🟢 When NOT to Use It v-memo is not needed everywhere as Vue is already highly optimized. Avoid using it for tiny components, static content, or premature optimization as overusing memoization can: reduce readability make debugging harder introduce stale UI bugs 🧪 Best Practices Use v-memo only for measurable bottlenecks Great for large dynamic lists Keep dependency arrays minimal Avoid premature optimization Profile performance before optimizing Combine with virtual scrolling for huge datasets Prefer stable keys and predictable state updates 📖 Learn more If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below: It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉 🧪 Advance skills A certification boosts your skills, builds credibility, and opens doors to new opportunities. Whether you're advancing your career or switching paths, it's a smart step toward success. Check out Certificates.dev by clicking this link or by clicking the image below: Invest in yourself—get certified in Vue.js, JavaScript, Nuxt, Angular, React, and more! ✅ Summary Memoization is a powerful optimization technique that helps avoid unnecessary work. In this article, you learned: What memoization is What problem it solves How Vue’s v-memo works Practical examples for lists and components When to use it — and when not to Take care! And happy coding as always 🖥️","2026-05-18T12:00:05.150Z","019e3af5-2608-71df-9da8-92a67430a7ef","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7b8usfxc7u7injpn8yor.png","2026-05-18T09:21:34.000Z","avoid-unnecessary-re-renders-in-vue-with-v-memo","This article discusses the use of the `v-memo` directive in Vue to optimize rendering by memoizing parts of a template, which helps avoid unnecessary re-renders, especially in large applications. It explains the concept of memoization, its benefits, and provides examples and best practices for effective use in Vue components.","Avoid Unnecessary Re-renders in Vue with `v-memo`","2026-06-10T14:35:13.762Z","https:\u002F\u002Fdev.to\u002Fjacobandrewsky\u002Favoid-unnecessary-re-renders-in-vue-with-v-memo-49bo","15f62df5f7c0b28470c39c37cedc4db4537ead55e3c1365af313657060bac4f8",[136,137,138,141,142],{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":139,"name":140,"slug":140},"019e3af5-4a29-759a-81b0-b6d502b2449e","v-memo",{"color":6,"id":35,"name":36,"slug":36},{"color":6,"id":143,"name":144,"slug":144},"019d6bd8-fba5-743f-8f9f-4e23c0b31581","tutorial",{"content":146,"createdAt":147,"id":148,"image":149,"isAffiliate":61,"isPublished":18,"publishedAt":150,"slug":151,"sourceId":152,"sourceName":153,"sourceType":66,"summary":154,"title":155,"updatedAt":156,"url":157,"urlHash":158,"tags":159},"Understand the key distinction between useFetch and event.$fetch in Nuxt. Learn why using event.$fetch for server-side API calls is the recommended approach for better performance and SSR correctness.","2026-05-16T18:48:35.150Z","019e321e-6c32-75c9-b0a0-2ba5b5c9f791","https:\u002F\u002Fmokkapps.twic.pics\u002Fmokkapps.de\u002Fvue-tips\u002Fdifference-between-use-fetch-and-event-fetch-in-nuxt\u002Fog.png","2026-05-16T00:00:00.000Z","nuxt-tip-difference-between-usefetch-and-eventfetch","019d6bd5-57e0-742c-8de2-c0a3a1f49b60","Michael Hoffmann","This article explains the difference between useFetch and event.$fetch in Nuxt, highlighting that event.$fetch is preferred for server-side API calls due to its advantages in performance and SSR correctness.","Nuxt Tip: Difference Between useFetch and event.$fetch","2026-05-16T18:48:40.681Z","https:\u002F\u002Fmokkapps.de\u002Fvue-tips\u002Fdifference-between-use-fetch-and-event-fetch","0880d118888dfef0df3d7ab0fd12fa6cc04b0dec3263b3d9d33fd049fce7ac56",[160,161,162,165],{"color":6,"id":31,"name":32,"slug":32},{"color":6,"id":51,"name":52,"slug":52},{"color":6,"id":163,"name":164,"slug":164},"019d6bd8-fb46-77ef-a3b6-bd2d30ab8919","api",{"color":6,"id":7,"name":8,"slug":8},{"content":167,"createdAt":168,"id":169,"image":170,"isAffiliate":61,"isPublished":18,"publishedAt":171,"slug":172,"sourceId":64,"sourceName":65,"sourceType":66,"summary":173,"title":174,"updatedAt":175,"url":176,"urlHash":177,"tags":178},"When developers think about performance optimization, they usually focus on things like: lazy loading caching image optimization bundle size And while those things absolutely matter there’s another area that heavily impacts user experience -&gt; Loading states and layout stability. A badly implemented loading experience can make an app feel slow, jumpy, or frustrating to use. This is strongly connected to an important Core Web Vital metric Cumulative Layout Shift (CLS). In this article, we’ll explore: What CLS is Why loaders are critical for perceived performance How poor loading states hurt UX Practical examples in Vue Best practices for stable layouts Let’s dive in. 🤔 What Is Cumulative Layout Shift (CLS)? CLS measures how much elements unexpectedly move during page loading. Example of bad CLS: Text suddenly jumps down Buttons move while you try to click Images appear late and push content around We’ve all experienced websites like this: 👉 You try to click something… and suddenly the layout shifts. Extremely annoying. Why does this happen? Usually because: content loads asynchronously elements have no reserved space loaders are missing images don’t define dimensions components suddenly appear Why does CLS matter? Because it affects user experience, accessibility, or mobile usability (and obviously Google Core Web Vitals). Even if your app is technically fast poor layout stability can make it feel slow. 👉 Users care more about perceived performance than actual milliseconds. A good loader communicates progress, prevents layout jumping, and makes apps feel responsive A bad or missing loader creates uncertainty. Users start thinking: “Did the app freeze?” “Is something broken?” “Why is everything moving?” 🟢 Implementing proper loaders in Vue Let's take a look at the following example: &lt;script setup lang=\"ts\"&gt; const users = ref([]) const loading = ref(true) onMounted(async () =&gt; { users.value = await fetchUsers() loading.value = false }) &lt;\u002Fscript&gt; &lt;template&gt; &lt;div v-if=\"loading\" class=\"skeleton-list\"&gt; &lt;div v-for=\"n in 5\" :key=\"n\" class=\"skeleton-card\" \u002F&gt; &lt;\u002Fdiv&gt; &lt;UserCard v-else v-for=\"user in users\" :key=\"user.id\" :user=\"user\" \u002F&gt; &lt;\u002Ftemplate&gt; &lt;style scoped&gt; .skeleton-card { height: 120px; border-radius: 12px; margin-bottom: 16px; } &lt;\u002Fstyle&gt; We fetch users, but when the fetch is in progress we display the same hard coded number of loaders\u002Fskeletons. When the users are loaded there is no layout shift as it occupies the same space improving perceived performance and User Experience. If we don't know how many results there will be, we have to assume some number but it is still better than not having skeletons at all :) Many apps still use simple spinners or text\u002Ficon loaders like: &lt;div&gt;Loading...&lt;\u002Fdiv&gt; But modern UX usually prefers Skeleton loaders because they mimic final layout, reduce layout shift, and improve perceived speed. 🧪 Best Practices Prefer skeleton loaders over tiny spinners Reserve space before content loads Keep loading and final layouts similar Always define image dimensions Avoid injecting large content suddenly Test CLS using Lighthouse or Core Web Vitals tools Think about perceived performance — not just raw speed 📖 Learn more If you would like to learn more about Vue, Nuxt, JavaScript or other useful technologies, checkout VueSchool by clicking this link or by clicking the image below: It covers most important concepts while building modern Vue or Nuxt applications that can help you in your daily work or side projects 😉 🧪 Advance skills A certification boosts your skills, builds credibility, and opens doors to new opportunities. Whether you're advancing your career or switching paths, it's a smart step toward success. Check out Certificates.dev by clicking this link or by clicking the image below: Invest in yourself—get certified in Vue.js, JavaScript, Nuxt, Angular, React, and more! ✅ Summary Loaders are much more important than most developers realize. In this article, you learned: What Cumulative Layout Shift (CLS) is Why poor loading states hurt UX How skeleton loaders improve perceived performance How to avoid layout jumping in Vue and other frameworks Best practices for stable, responsive interfaces Fast apps are great. But apps that feel smooth and stable are what users truly remember. Take care! And happy coding as always 🖥️","2026-05-11T12:00:12.770Z","019e16e8-bfd2-7191-94bc-00d70b24a540","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbym0bvla6v4jibn66jc3.png","2026-05-11T09:29:43.000Z","why-loaders-matter-for-performance","This article discusses the importance of loaders in enhancing perceived performance and user experience in web applications, particularly focusing on the concept of Cumulative Layout Shift (CLS). It highlights how proper implementation of loaders in Vue can prevent layout shifts and improve user engagement by providing a stable loading experience. The article also offers practical examples and best practices for implementing effective loaders.","Why Loaders Matter for Performance","2026-06-10T14:35:13.764Z","https:\u002F\u002Fdev.to\u002Fjacobandrewsky\u002Fwhy-loaders-matter-for-performance-4b8a","d90fb6c45448cd6496f59658878a5381e26f71ac1b914ec7c7c4c29bf64f2c6e",[179,180,181,184,187],{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":182,"name":183,"slug":183},"019e16e8-dbfa-76d6-bb2d-793aee049186","loading",{"color":6,"id":185,"name":186,"slug":186},"019e16e8-dc00-714d-911a-a5bf39238587","user-experience",{"color":6,"id":82,"name":83,"slug":83},{"content":189,"createdAt":190,"id":191,"image":192,"isAffiliate":61,"isPublished":18,"publishedAt":193,"slug":194,"sourceId":195,"sourceName":196,"sourceType":197,"summary":198,"title":199,"updatedAt":200,"url":201,"urlHash":202,"tags":203},"Even experienced developers fall into common traps when working with Nuxt and Vue. In this talk, we'll explore performance ...","2026-05-02T12:00:07.346Z","019de88f-6ea2-70a9-9b59-ed5b77339c68","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FNZn_YY2wCZI\u002Fhqdefault.jpg","2026-05-02T10:00:06.000Z","julien-huang---stop-making-these-nuxt-amp-vue-mistakes-introducing-nuxthints-10","019d9ce0-e8f2-774a-ba57-a61c19469fe1","Vuejs Amsterdam","youtube","Julien Huang discusses common mistakes developers make while using Nuxt and Vue, introducing the @nuxt\u002Fhints 1.0 tool to help avoid these pitfalls and improve performance. The talk aims to enhance the development experience by addressing frequent issues faced by both new and seasoned developers.","Julien Huang - Stop making these Nuxt &amp; Vue mistakes: introducing @nuxt\u002Fhints 1.0","2026-05-02T12:00:23.318Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NZn_YY2wCZI","d468b007f85431c35301d8f1481694bdff471425503175fdc5c8ccd2d48bb322",[204,205,206,207],{"color":6,"id":31,"name":32,"slug":32},{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":143,"name":144,"slug":144},{"content":209,"createdAt":210,"id":211,"image":212,"isAffiliate":61,"isPublished":18,"publishedAt":213,"slug":214,"sourceId":195,"sourceName":196,"sourceType":197,"summary":215,"title":216,"updatedAt":217,"url":218,"urlHash":219,"tags":220},"Tree-shaking is widely regarded as a performance optimization technique in the Vue and Nuxt ecosystem—but have you ever ...","2026-05-01T12:00:08.125Z","019de369-15ab-754a-8e04-da461cb7068b","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FYXG514QueOc\u002Fhqdefault.jpg","2026-05-01T10:00:06.000Z","serko-vincent-ngai---when-tree-shaking-fails-security-risks-in-nuxt-amp-vue","The article discusses the security risks associated with tree shaking in the Vue and Nuxt frameworks, highlighting potential vulnerabilities that can arise when this optimization technique fails. It emphasizes the importance of understanding these risks to maintain secure applications.","SerKo Vincent Ngai - When Tree Shaking Fails: Security Risks in Nuxt &amp; Vue","2026-05-01T12:00:16.190Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=YXG514QueOc","305f1c090af38f6fa7ea715fff88cec084c05ba031f23220f80d6639a9a0f86a",[221,222,223,226],{"color":6,"id":31,"name":32,"slug":32},{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":224,"name":225,"slug":225},"019dcdf3-fc84-73aa-a0be-6b7196c5a2e9","security",{"color":6,"id":7,"name":8,"slug":8},{"content":228,"createdAt":229,"id":230,"image":231,"isAffiliate":61,"isPublished":18,"publishedAt":232,"slug":233,"sourceId":195,"sourceName":196,"sourceType":197,"summary":234,"title":235,"updatedAt":236,"url":237,"urlHash":238,"tags":239},"Modern Frontend Engineering: Tools, Tradeoffs, and the AI Shift. It will focus on software quality in the era of AI with the Creator of ...","2026-04-28T12:00:01.729Z","019dd3f5-e8ad-708a-8f6e-4c164bd16e7c","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FL5aJVG5g7k0\u002Fhqdefault.jpg","2026-04-28T10:00:06.000Z","jakub-andrzejewski-evan-you---panel-beyond-the-vibe-code-quality-first","The panel featuring Jakub Andrzejewski and Evan You discusses the importance of code quality in modern frontend engineering, particularly in the context of AI advancements. They explore various tools and tradeoffs that developers face today.","Jakub Andrzejewski, Evan You - Panel: Beyond The Vibe: Code Quality First","2026-04-28T12:00:12.454Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=L5aJVG5g7k0","eaffed00699dc730765b58457c919e290cc6cef62eeb40ad522499d42448b87f",[240,241,242],{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":243,"name":244,"slug":244},"019d9cf0-ca04-75bb-ad86-ce8da1c0be23","architecture",{"content":246,"createdAt":247,"id":248,"image":249,"isAffiliate":61,"isPublished":18,"publishedAt":250,"slug":251,"sourceId":252,"sourceName":253,"sourceType":66,"summary":254,"title":255,"updatedAt":256,"url":257,"urlHash":258,"tags":259},"Your E2E tests pass. The page loads, buttons work. But open the browser console: Hydration failed because the server rendered HTML didn't match the client. This is a hydration mismatch. The server sent one thing and the client replaced it with something else. The page still works, so you don’t notice. Your tests don’t check for it, so they pass. What are SSR and hydration? SSR (server-side rendering) means the server generates HTML and sends it to the browser before JavaScript loads. Users see content before client code boots, and search engines can index it. Astro and Nuxt build on this model. Hydration is the next step: client JavaScript takes over the server-rendered HTML, attaching event handlers and state to the existing markup. The contract: the first client render must match what the server sent. When it does not match, the framework discards the server HTML and re-renders on the client. That re-render is a hydration mismatch. Common causes Anything that produces different HTML on client and server: Reading localStorage or window.matchMedia() during render Calling new Date() or Math.random() during render Formatting dates or numbers differently across server and client Rendering conditional branches based on browser-only state Theme toggles and locale formatting cause most of them. If you use Vue with SSR, the window is not defined error comes from the same root cause. VueUse has a pattern for it: A real bug I found I was working on an Astro page and my theme hook was reading browser state during the first render: function getInitialTheme(): Theme { const stored = localStorage.getItem(SITE.themeStorageKey); if (stored === \"light\" || stored === \"dark\") return stored; return window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\"; } export function useTheme() { const [theme, setTheme] = useState&lt;Theme&gt;(getInitialTheme); } The server defaulted to dark, but the browser picked light. React saw the mismatch, logged a hydration warning, and re-rendered from scratch. The page still worked, the button still existed. Normal E2E tests passed. The fix: start with a deterministic value, resolve browser state after mount. export function useTheme() { const [theme, setTheme] = useState&lt;Theme&gt;(\"dark\"); const [mounted, setMounted] = useState(false); useEffect(() =&gt; { const preferredTheme = getPreferredTheme(); document.documentElement.classList.toggle(\"dark\", preferredTheme === \"dark\"); setTheme(preferredTheme); setMounted(true); }, []); useEffect(() =&gt; { if (!mounted) return; const root = document.documentElement; root.classList.toggle(\"dark\", theme === \"dark\"); localStorage.setItem(SITE.themeStorageKey, theme); }, [mounted, theme]); return { theme, setTheme, toggleTheme: () =&gt; setTheme((t) =&gt; (t === \"dark\" ? \"light\" : \"dark\")) }; } The core idea Listen to the browser console during a Playwright test. If a hydration warning appears, fail the test. React and Vue log hydration mismatches to the console. You don’t check the console during automated tests, so this fixture does. The fixture Fixtures are Playwright's way of setting up and tearing down what each test needs. Built-in fixtures like `page` and `browser` come for free. You create custom ones with `base.extend()`. Each fixture runs when a test requests it and gets cleaned up afterward. The fixture below injects `hydrationErrors` and `runtimeErrors` into every test that asks for them. I first saw this approach in the npmx.dev open source project and adapted it for my Astro site. My version covers React and Vue hydration strings and catches uncaught runtime exceptions: const HYDRATION_ERROR_PATTERNS = [ \u002Fhydration failed because the server rendered html didn't match the client\u002Fi, \u002Fhydration completed but contains mismatches\u002Fi, \u002Fhydration text content mismatch\u002Fi, \u002Fhydration node mismatch\u002Fi, \u002Fhydration attribute mismatch\u002Fi, ]; function isHydrationError(text: string): boolean { return HYDRATION_ERROR_PATTERNS.some((pattern) =&gt; pattern.test(text)); } function toConsoleText(message: ConsoleMessage): string { return message.text().trim(); } export const test = base.extend&lt;{ hydrationErrors: string[]; runtimeErrors: string[]; }&gt;({ hydrationErrors: async ({ page }, use) =&gt; { const hydrationErrors: string[] = []; const handleConsole = (message: ConsoleMessage) =&gt; { const text = toConsoleText(message); if (isHydrationError(text)) { hydrationErrors.push(text); } }; page.on(\"console\", handleConsole); await use(hydrationErrors); page.off(\"console\", handleConsole); }, runtimeErrors: async ({ page }, use) =&gt; { const runtimeErrors: string[] = []; const handleConsole = (message: ConsoleMessage) =&gt; { const text = toConsoleText(message); if (message.type() === \"error\" &amp;&amp; text.length &gt; 0 &amp;&amp; !isHydrationError(text)) { runtimeErrors.push(text); } }; const handlePageError = (error: Error) =&gt; { runtimeErrors.push(error.message); }; page.on(\"console\", handleConsole); page.on(\"pageerror\", handlePageError); await use(runtimeErrors); page.off(\"console\", handleConsole); page.off(\"pageerror\", handlePageError); }, }); export { expect }; Drop this into test\u002Fe2e\u002Ftest-utils.ts and import from there instead of @playwright\u002Ftest. Related: a full AI-driven QA workflow with Playwright: Using it test(\"home page hydrates cleanly\", async ({ page, hydrationErrors, runtimeErrors }) =&gt; { await page.goto(\"\u002F\", { waitUntil: \"domcontentloaded\" }); await expect(page.getByRole(\"heading\", { name: \"Home\" })).toBeVisible(); expect(hydrationErrors).toEqual([]); expect(runtimeErrors).toEqual([]); }); Start with your homepage. Add one interactive route, then one with a theme toggle or client-only widget. That surfaces most bugs. How npmx.dev does it at scale The npmx.dev project tests hydration correctness for every combination of user settings across every page, around 48 checks from a single fixture. They inject localStorage values via Playwright’s page.addInitScript() before navigation, simulating a returning user with saved preferences. Returning users with non-default settings trigger most hydration mismatches. const PAGES = [\"\u002F\", \"\u002Fabout\", \"\u002Fsettings\", \"\u002Fcompare\", \"\u002Fsearch\", \"\u002Fpackage\u002Fnuxt\"]; test.describe(\"color mode: dark\", () =&gt; { for (const page of PAGES) { test(`${page}`, async ({ page: pw, goto, hydrationErrors }) =&gt; { await injectLocalStorage(pw, { \"npmx-color-mode\": \"dark\" }); await goto(page, { waitUntil: \"hydration\" }); expect(hydrationErrors).toEqual([]); }); } }); async function injectLocalStorage(page: Page, entries: Record&lt;string, string&gt;) { await page.addInitScript((e: Record&lt;string, string&gt;) =&gt; { for (const [key, value] of Object.entries(e)) { localStorage.setItem(key, value); } }, entries); } They repeat this for every setting type, locale, accent color, background theme, package manager, relative dates, each with a non-default value. If any combination causes a hydration mismatch on any page, the test fails. Their fixture uses Vue-specific error strings (\"Hydration completed but contains mismatches\") while mine uses React patterns. The approach is the same, only the strings you match against change. More on how E2E tests relate to unit and integration tests: If you ship an SSR app and do not check for hydration errors in your browser tests, you have one in production right now.","2026-04-09T06:11:38.114Z","019d70de-1de7-70ea-8344-aded9a900a5c","https:\u002F\u002Falexop.dev\u002Fposts\u002Fhow-to-catch-hydration-errors-in-playwright-tests-astro-nuxt-react-ssr\u002Findex.png","2026-04-06T00:00:00.000Z","how-to-catch-hydration-errors-in-playwright-tests-astro-nuxt-react-ssr","019d70dd-e3e7-76db-84a4-87b896dea004","alexop.dev","The article discusses how to identify and address hydration errors in Playwright tests, particularly in applications using SSR with frameworks like Nuxt and Astro. It explains the concept of hydration mismatches, common causes, and offers solutions to ensure consistent rendering between server and client. The focus is on maintaining a deterministic initial state to prevent hydration warnings during testing.","How to Catch Hydration Errors in Playwright Tests (Astro, Nuxt, React SSR)","2026-04-09T06:11:45.745Z","https:\u002F\u002Falexop.dev\u002Fposts\u002Fcatch-hydration-errors-playwright-tests\u002F","a61a606d501f4750cf6e2136ea2210d1a0550f673021e364846f1ce1953dd9cd",[260,261,262,265],{"color":6,"id":31,"name":32,"slug":32},{"color":6,"id":51,"name":52,"slug":52},{"color":6,"id":263,"name":264,"slug":264},"019d70de-3c07-76bf-9688-9619e1b0d427","testing",{"color":6,"id":7,"name":8,"slug":8},{"content":267,"createdAt":268,"id":269,"image":270,"isAffiliate":18,"isPublished":18,"publishedAt":271,"slug":272,"sourceId":273,"sourceName":274,"sourceType":275,"summary":276,"title":277,"updatedAt":278,"url":279,"urlHash":280,"tags":281},"If you have ever built a chat thread, card feed, whiteboard label editor, or masonry layout in Vue, you have probably ended up doing something slightly gross: render text into the DOM, measure it, and then rerender or reposition everything based on that measurement. That works, but it comes with baggage: hidden measurement nodes getBoundingClientRect() and offsetHeight reads in hot paths resize loops that mix layout and app state virtualization code that needs a height before the row is even mounted Pretext is interesting because it attacks that exact problem. Instead of asking the DOM how tall wrapped text became, it prepares the text once and then lays it out against a width using cached measurements. In other words, you can know a text's height before it is even mounted so that Vue can stay focused on state and rendering while Pretext handles the text math. This is not a replacement for Vue, CSS, or the browser layout engine. It is a way to stop using the DOM as your text calculator when all you really need is line count and height. 👉 Don't quite follow? Check out the demo. It breaks down things visually for you. What Pretext actually does The core model is simple: prepare(text, font) does the expensive work once. layout(prepared, width, lineHeight) returns the wrapped height and line count for that width. That split matters. If the text and font stay the same while the available width changes, you do not need to re-measure every grapheme from scratch on every resize. You reuse the prepared value and run layout again. That makes Pretext especially compelling in UI that has lots of text blocks with widths changing over time: virtualized feeds resizable sidebars draggable canvases with labels shrink-wrapped chat bubbles card grids where item height depends on text The Vue angle Vue is already very good at state transitions. The problem is that text measurement traditionally drags you back into imperative DOM work. You start with clean reactive code: const messages = ref&lt;Message[]&gt;([]); Then layout requirements show up and suddenly you are doing things like: await nextTick(); const height = node.getBoundingClientRect().height; That is the line I would try to delete first. With Pretext, the flow looks more like this: Vue owns the text, width, and rendering state. Pretext derives height and line count from text plus width. Your list, grid, or canvas logic consumes those numbers without mounting probe elements first. That is a much cleaner separation of concerns. Install it ni @chenglou\u002Fpretext A simple Vue composable The main trick is to make prepare() depend on the text and font, but not the width. Width changes should only trigger layout(). import { computed, toValue } from &quot;vue&quot;; import { layout, prepare } from &quot;@chenglou\u002Fpretext&quot;; export function usePretextLayout(options) { const text = computed(() =&gt; toValue(options.text)); const font = computed(() =&gt; toValue(options.font)); const width = computed(() =&gt; toValue(options.width)); const lineHeight = computed(() =&gt; toValue(options.lineHeight)); const prepared = computed(() =&gt; prepare(text.value, font.value)); const result = computed(() =&gt; layout(prepared.value, Math.max(1, width.value), lineHeight.value), ); const height = computed(() =&gt; result.value.height); const lineCount = computed(() =&gt; result.value.lineCount); return { text, font, width, lineHeight, prepared, result, height, lineCount, }; } Why split it this way? Text changes should invalidate the prepared measurement. Font changes should also invalidate it. Width changes should only rerun layout. That maps nicely onto Vue's computed graph. Use it in a component Here is a minimal card example. The width is reactive, but the text measurement does not require mounting a hidden probe element just to discover its height. &lt;script setup lang=&quot;ts&quot;&gt; import { ref } from &quot;vue&quot;; import { usePretextLayout } from &quot;.\u002Fcomposables\u002FusePretextLayout&quot;; const body = ref( &quot;Pretext lets Vue apps estimate wrapped text height without measuring hidden DOM nodes first.&quot;, ); const cardWidth = ref(320); const font = ref(&quot;400 16px Inter, system-ui, sans-serif&quot;); const lineHeight = ref(24); const { height, lineCount } = usePretextLayout({ text: body, font, width: cardWidth, lineHeight, }); &lt;\u002Fscript&gt; &lt;template&gt; &lt;article class=&quot;card&quot; :style=&quot;{ width: `${cardWidth}px` }&quot;&gt; &lt;p&gt;{{ body }}&lt;\u002Fp&gt; &lt;footer&gt;{{ lineCount }} lines, {{ height }}px tall&lt;\u002Ffooter&gt; &lt;\u002Farticle&gt; &lt;\u002Ftemplate&gt; This is the important mindset shift: the card height is no longer something you discover after the browser lays out the paragraph. It is something you can derive from the same reactive inputs that already describe the UI. Where this gets really useful The simplest demo is a single card, but that is not where the real value is. The real value shows up when the old approach creates layout thrash or architectural awkwardness. 1. Virtualized lists with variable-height text Virtualization loves predictable heights. Text-heavy UIs often do not have them. Pretext gives you a better story: prepare message text when data arrives compute row height from the current column width feed that height into your virtualizer rerun layout when the list width changes That is much better than mounting off-screen rows just to measure them. import { layout, prepare, type PreparedText } from &quot;@chenglou\u002Fpretext&quot;; type Row = { id: string; body: string; prepared: PreparedText; }; const font = &quot;400 15px Inter, system-ui, sans-serif&quot;; const lineHeight = 22; const rows: Row[] = apiRows.map((row) =&gt; ({ id: row.id, body: row.body, prepared: prepare(row.body, font), })); function getRowHeight(row: Row, contentWidth: number) { return layout(row.prepared, contentWidth, lineHeight).height + 24; } That pattern fits Vue very naturally. You can prepare once when rows are normalized, then derive heights wherever your layout logic needs them. 2. Chat bubbles that size to content If your message UI wants to make decisions based on line count or wrapped height, Pretext is a cleaner primitive than &quot;render first, inspect later.&quot; Examples: deciding whether a bubble gets compact or roomy chrome estimating whether a message should collapse behind &quot;show more&quot; aligning metadata differently for one-line versus multi-line messages Those are layout decisions based on text shape, not business logic. They should not require DOM probes in every component instance. 3. Canvas, whiteboards, and design tools This is where Pretext starts to feel like a category change rather than a small optimization. Its advanced APIs, including prepareWithSegments(), layoutWithLines(), and layoutNextLine(), are designed for cases where you need more than total height: drawing each wrapped line manually finding the widest produced line routing text line by line through changing widths That is useful for labels on canvases, text around shapes, or any UI where the browser is not directly painting the final text layout for you. A practical caveat: your font string has to match reality Pretext is not guessing in the abstract. It measures text against a font declaration and then lays out against a width and line height. That means two values need to match what your UI actually renders: the font string you pass to prepare() the lineHeight you pass to layout() If your component renders with a different font weight, font family, font size, or line height than the values you gave Pretext, your estimated result will drift from the actual DOM layout. So keep the typography source of truth tight. If the component uses: .message { font: 400 16px Inter, system-ui, sans-serif; line-height: 24px; } then your measurement inputs should match those values. What Pretext does not replace This is the part worth being explicit about, because the interesting thing about Pretext is not that it replaces everything. It replaces one very specific pain point. Pretext does not replace: Vue rendering CSS text styling actual width measurement of your container browser selection, caret behavior, or editing UX the browser's final paint of the real text node You still need a width from somewhere. Sometimes that is a prop. Sometimes it comes from your layout model. Sometimes a ResizeObserver is still appropriate. The difference is that you are no longer using the DOM to answer &quot;how tall did this paragraph become?&quot; That is a much smaller and cleaner dependency on layout. When I would reach for it I would consider Pretext when all of these are true: text height or line count affects layout decisions there are many text blocks, or the calculation happens often hidden measurement DOM is making the code awkward or slow you need the answer before mounting the final row or card I would probably not reach for it when: you are rendering a handful of static paragraphs CSS alone solves the problem you only need the browser to lay out the text once and never revisit it In other words, this is not &quot;replace CSS with a library.&quot; It is &quot;stop abusing the DOM as a calculator in text-heavy reactive UIs.&quot; The broader idea What makes Pretext interesting is not just the API. It is the shift in mental model. For a long time, web developers mostly accepted that wrapped text measurement had to be a DOM problem. Pretext challenges that assumption. In a Vue app, that means some layout decisions that used to live in nextTick(), probe elements, and measurement loops can move back into pure reactive derivation. That is exactly the kind of change I like: not because it is flashy, but because it removes a category of awkward code. Summary Pretext gives Vue developers a better option for text-heavy UI where height and line count matter. You prepare text once, lay it out against width as needed, and stop relying on hidden DOM nodes to tell you what wrapped text looks like.","2026-04-08T06:47:33.045Z","019d6bd8-a30c-720e-b82e-9253cd4d2970","https:\u002F\u002Fblog.vueschool.io\u002Fwp-content\u002Fuploads\u002F2026\u002F04\u002Ffeature-v2-1.jpg","2026-04-03T20:45:27.000Z","using-pretext-in-vue-to-build-variable-height-ui-without-layout-thrash","019d6bd5-87de-77ef-ac92-98aa56cda920","VueSchool","vueschool","The article discusses how to use Pretext in Vue to create variable-height UIs without the common pitfalls of layout thrashing. By preparing text measurements in advance, Pretext allows developers to avoid direct DOM manipulations for height calculations, leading to cleaner and more reactive code. This approach is particularly beneficial for UIs with dynamic text and changing widths.","Using Pretext in Vue to Build Variable-Height UI Without Layout Thrash","2026-04-08T06:47:42.561Z","https:\u002F\u002Fblog.vueschool.io\u002Fvuejs-tutorials\u002Fusing-pretext-in-vue-to-build-variable-height-ui-without-layout-thrash\u002F?friend=MOKKAPPS","75c6e75824a6e8739844c6c1bc511bbf6c5bc3302502ab9c9e1c87376385d07c",[282,283,284],{"color":6,"id":74,"name":75,"slug":75},{"color":6,"id":7,"name":8,"slug":8},{"color":6,"id":285,"name":286,"slug":286},"019d6bd8-ca8b-709c-b9a5-77d4910da162","ui-components",1,20,13,{"tags":291},[292,297,301,306,310,314,317,319,323,327,331,335,340,342,346,350,354,358,362,366,370,374,378,382,386,390,394,398,402,406,410,414,418,422,426,430,434,438,442,446,450,454,458,462,466,470,474,478,482,486,488,492,496,500,504,508,512,516,520,524,528,532,535,539,541,545,547,549,553,555,559,563,567,571,575,579,581,585,589,593,597,601,603,607,611,615,619,621,624,628,632,636,640,644,648,650,654,658,661,665,667,669,671,675,679,681,685,689,692,696,700,705],{"articleCount":293,"color":6,"createdAt":294,"id":295,"name":296,"slug":296,"updatedAt":294},0,"2026-04-30T04:19:02.605Z","019ddc9c-954c-7703-8288-76d562fec577","accelerator",{"articleCount":287,"color":6,"createdAt":298,"id":299,"name":300,"slug":300,"updatedAt":298},"2026-04-08T06:47:43.120Z","019d6bd8-caef-76b9-bacd-363e31e6d2a9","accessibility",{"articleCount":302,"color":6,"createdAt":303,"id":304,"name":305,"slug":305,"updatedAt":303},8,"2026-04-17T20:27:50.780Z","019d9d20-e07b-7685-9ebb-fae0b963f243","ai",{"articleCount":293,"color":6,"createdAt":307,"id":308,"name":309,"slug":309,"updatedAt":307},"2026-05-05T00:00:19.031Z","019df56f-8257-752e-a918-cdbb07c32b86","ai-agents",{"articleCount":287,"color":6,"createdAt":311,"id":312,"name":313,"slug":313,"updatedAt":311},"2026-06-01T12:00:14.713Z","019e830e-5378-722b-929b-a57d67bcf605","animation",{"articleCount":315,"color":6,"createdAt":316,"id":163,"name":164,"slug":164,"updatedAt":316},4,"2026-04-08T06:47:55.499Z",{"articleCount":302,"color":6,"createdAt":318,"id":243,"name":244,"slug":244,"updatedAt":318},"2026-04-17T19:35:19.300Z",{"articleCount":293,"color":6,"createdAt":320,"id":321,"name":322,"slug":322,"updatedAt":320},"2026-04-23T12:00:11.615Z","019dba36-435e-765d-bfb2-c5be05362c27","astro",{"articleCount":293,"color":6,"createdAt":324,"id":325,"name":326,"slug":326,"updatedAt":324},"2026-06-01T20:00:15.958Z","019e84c5-cc55-7377-9e5d-77240ea8a880","authentication",{"articleCount":293,"color":6,"createdAt":328,"id":329,"name":330,"slug":330,"updatedAt":328},"2026-05-05T00:00:19.020Z","019df56f-824c-7031-9e34-2f47ad139eb6","automation",{"articleCount":293,"color":6,"createdAt":332,"id":333,"name":334,"slug":334,"updatedAt":332},"2026-06-11T16:00:12.019Z","019eb769-9af2-7471-b482-3f1a404f802e","azure",{"articleCount":336,"color":6,"createdAt":337,"id":338,"name":339,"slug":339,"updatedAt":337},2,"2026-04-17T20:27:50.776Z","019d9d20-e077-71cc-a605-8ac2a1566143","backend",{"articleCount":315,"color":6,"createdAt":341,"id":82,"name":83,"slug":83,"updatedAt":341},"2026-04-08T06:47:56.976Z",{"articleCount":293,"color":6,"createdAt":343,"id":344,"name":345,"slug":345,"updatedAt":343},"2026-06-06T00:00:11.711Z","019e9a3a-e5be-74b3-a01a-92c1f9427a2c","beta",{"articleCount":293,"color":6,"createdAt":347,"id":348,"name":349,"slug":349,"updatedAt":347},"2026-06-10T13:53:29.711Z","019eb1cf-3e6e-70f0-a761-0fb9b61d5675","budgeting",{"articleCount":287,"color":6,"createdAt":351,"id":352,"name":353,"slug":353,"updatedAt":351},"2026-06-16T16:11:31.438Z","019ed133-c4ee-70bb-8e21-7dc86e8adee4","bun",{"articleCount":293,"color":6,"createdAt":355,"id":356,"name":357,"slug":357,"updatedAt":355},"2026-05-19T20:00:14.559Z","019e41d3-1ade-77b0-9e4f-e9b97a6361ca","cdn",{"articleCount":287,"color":6,"createdAt":359,"id":360,"name":361,"slug":361,"updatedAt":359},"2026-06-27T16:00:12.237Z","019f09cf-5bcd-751c-8d46-9e123bd784af","clean-code",{"articleCount":287,"color":6,"createdAt":363,"id":364,"name":365,"slug":365,"updatedAt":363},"2026-05-16T18:48:40.777Z","019e321e-8248-75cc-9b69-59c2db6dd846","cli",{"articleCount":293,"color":6,"createdAt":367,"id":368,"name":369,"slug":369,"updatedAt":367},"2026-05-05T00:00:19.014Z","019df56f-8246-747f-be4b-a716863a93ea","cloud-platform",{"articleCount":287,"color":6,"createdAt":371,"id":372,"name":373,"slug":373,"updatedAt":371},"2026-06-16T16:11:31.289Z","019ed133-c458-74d8-a5c9-654f77837e7c","cloudflare",{"articleCount":287,"color":6,"createdAt":375,"id":376,"name":377,"slug":377,"updatedAt":375},"2026-07-27T18:11:45.304Z","019fa4c6-93c3-76ea-baa3-7d5fe541ac5b","cms",{"articleCount":287,"color":6,"createdAt":379,"id":380,"name":381,"slug":381,"updatedAt":379},"2026-04-30T04:19:02.045Z","019ddc9c-931c-743d-a1cb-e4449630fe47","collaboration",{"articleCount":287,"color":6,"createdAt":383,"id":384,"name":385,"slug":385,"updatedAt":383},"2026-06-10T13:53:29.567Z","019eb1cf-3dde-776d-9398-4863764f9ac3","community",{"articleCount":287,"color":6,"createdAt":387,"id":388,"name":389,"slug":389,"updatedAt":387},"2026-05-06T16:00:17.128Z","019dfe04-bee8-7384-bc58-a712f677807c","comparison",{"articleCount":287,"color":6,"createdAt":391,"id":392,"name":393,"slug":393,"updatedAt":391},"2026-05-14T12:00:21.354Z","019e265b-f569-71d8-a02a-a17ec8180644","component-design",{"articleCount":287,"color":6,"createdAt":395,"id":396,"name":397,"slug":397,"updatedAt":395},"2026-07-18T20:00:18.730Z","019f76d0-bb29-72d6-8d0b-05ffd1d3c8ed","composables",{"articleCount":315,"color":6,"createdAt":399,"id":400,"name":401,"slug":401,"updatedAt":399},"2026-04-08T06:47:42.915Z","019d6bd8-ca21-71c5-a236-37d94fe57d24","composition-api",{"articleCount":287,"color":6,"createdAt":403,"id":404,"name":405,"slug":405,"updatedAt":403},"2026-06-27T12:00:13.113Z","019f08f3-a538-74ed-82c5-038edce7100a","copilot",{"articleCount":293,"color":6,"createdAt":407,"id":408,"name":409,"slug":409,"updatedAt":407},"2026-04-25T12:00:12.674Z","019dc482-ff81-73ac-9b1c-6ad47f0afde0","data-management",{"articleCount":293,"color":6,"createdAt":411,"id":412,"name":413,"slug":413,"updatedAt":411},"2026-06-04T20:00:17.367Z","019e9438-e5d7-731f-bf47-8dcd86c6655c","data-privacy",{"articleCount":287,"color":6,"createdAt":415,"id":416,"name":417,"slug":417,"updatedAt":415},"2026-05-05T12:00:17.083Z","019df802-a8bb-775a-b843-93d883c7dfc5","data-science",{"articleCount":293,"color":6,"createdAt":419,"id":420,"name":421,"slug":421,"updatedAt":419},"2026-06-11T16:00:12.028Z","019eb769-9afb-7709-8a67-2a12f5e557c7","deepseek",{"articleCount":287,"color":6,"createdAt":423,"id":424,"name":425,"slug":425,"updatedAt":423},"2026-05-25T08:00:12.834Z","019e5e26-0e21-7366-87c7-0b1334180b0a","dependency-cruiser",{"articleCount":287,"color":6,"createdAt":427,"id":428,"name":429,"slug":429,"updatedAt":427},"2026-04-18T04:30:00.710Z","019d9eda-5005-7329-a463-189572e25635","deployment",{"articleCount":336,"color":6,"createdAt":431,"id":432,"name":433,"slug":433,"updatedAt":431},"2026-04-21T12:00:11.373Z","019dafe9-8a6d-7218-81f1-37052cbe9b78","development",{"articleCount":287,"color":6,"createdAt":435,"id":436,"name":437,"slug":437,"updatedAt":435},"2026-06-29T08:00:12.101Z","019f1264-9f44-762d-b5fd-837839fdc586","devtools",{"articleCount":293,"color":6,"createdAt":439,"id":440,"name":441,"slug":441,"updatedAt":439},"2026-05-29T20:00:13.197Z","019e7552-ad8c-731f-ad8b-49253ed0e1b9","docker",{"articleCount":287,"color":6,"createdAt":443,"id":444,"name":445,"slug":445,"updatedAt":443},"2026-06-27T12:00:13.081Z","019f08f3-a518-7607-aa8c-782b4f10c2ed","documentation",{"articleCount":287,"color":6,"createdAt":447,"id":448,"name":449,"slug":449,"updatedAt":447},"2026-04-30T04:19:02.055Z","019ddc9c-9327-744f-a2da-31b64c7b6ba4","editor",{"articleCount":287,"color":6,"createdAt":451,"id":452,"name":453,"slug":453,"updatedAt":451},"2026-07-06T12:00:24.261Z","019f374d-0cc4-71ff-b906-ebaec8b0c343","eslint",{"articleCount":293,"color":6,"createdAt":455,"id":456,"name":457,"slug":457,"updatedAt":455},"2026-05-02T00:00:23.123Z","019de5fc-7e52-740a-807c-d322c373865b","firewall",{"articleCount":287,"color":6,"createdAt":459,"id":460,"name":461,"slug":461,"updatedAt":459},"2026-05-06T16:00:17.134Z","019dfe04-beee-7481-b8e7-4034640e840a","frameworks",{"articleCount":293,"color":6,"createdAt":463,"id":464,"name":465,"slug":465,"updatedAt":463},"2026-04-08T06:47:56.883Z","019d6bd9-00b2-7199-8e88-2530ef258032","generics",{"articleCount":287,"color":6,"createdAt":467,"id":468,"name":469,"slug":469,"updatedAt":467},"2026-07-27T18:11:45.390Z","019fa4c6-9419-7657-844e-58ec868ed664","git",{"articleCount":336,"color":6,"createdAt":471,"id":472,"name":473,"slug":473,"updatedAt":471},"2026-07-15T00:00:22.207Z","019f6313-12bf-7151-81f2-c8b43b09d979","html",{"articleCount":293,"color":6,"createdAt":475,"id":476,"name":477,"slug":477,"updatedAt":475},"2026-05-06T00:00:17.012Z","019dfa95-d673-71ef-be4a-8761512ffe5c","infrastructure",{"articleCount":287,"color":6,"createdAt":479,"id":480,"name":481,"slug":481,"updatedAt":479},"2026-06-16T16:11:31.264Z","019ed133-c43f-704f-8c1a-fbc299c8a3e5","javascript",{"articleCount":287,"color":6,"createdAt":483,"id":484,"name":485,"slug":485,"updatedAt":483},"2026-07-13T08:00:18.266Z","019f5a7d-bf5a-76e0-903a-c87435cc3e09","lazy-loading",{"articleCount":287,"color":6,"createdAt":487,"id":182,"name":183,"slug":183,"updatedAt":487},"2026-05-11T12:00:19.963Z",{"articleCount":293,"color":6,"createdAt":489,"id":490,"name":491,"slug":491,"updatedAt":489},"2026-04-25T12:00:12.682Z","019dc482-ff8a-7698-8af5-95db54a26d6b","local-first",{"articleCount":287,"color":6,"createdAt":493,"id":494,"name":495,"slug":495,"updatedAt":493},"2026-05-14T12:00:21.370Z","019e265b-f579-71cd-84b2-ac385b5225ae","maintainability",{"articleCount":287,"color":6,"createdAt":497,"id":498,"name":499,"slug":499,"updatedAt":497},"2026-05-10T16:00:18.613Z","019e129e-34b4-7107-acfb-27f6b8604eb0","management",{"articleCount":287,"color":6,"createdAt":501,"id":502,"name":503,"slug":503,"updatedAt":501},"2026-06-27T12:00:13.065Z","019f08f3-a508-7334-aa03-12b281698ea8","markdown",{"articleCount":287,"color":6,"createdAt":505,"id":506,"name":507,"slug":507,"updatedAt":505},"2026-07-28T12:00:27.878Z","019fa899-02e6-758e-bdcb-8a4a923507df","mcp",{"articleCount":287,"color":6,"createdAt":509,"id":510,"name":511,"slug":511,"updatedAt":509},"2026-06-17T12:00:12.439Z","019ed574-0a96-7485-9c90-522e4be3e092","meta-tags",{"articleCount":293,"color":6,"createdAt":513,"id":514,"name":515,"slug":515,"updatedAt":513},"2026-05-26T08:00:14.598Z","019e634c-7105-7073-bc86-c32fa0a4401b","microfrontends",{"articleCount":293,"color":6,"createdAt":517,"id":518,"name":519,"slug":519,"updatedAt":517},"2026-05-19T16:00:13.516Z","019e40f7-5ccc-729c-92ba-bcc0aad8a16f","microvm",{"articleCount":293,"color":6,"createdAt":521,"id":522,"name":523,"slug":523,"updatedAt":521},"2026-05-05T00:00:19.025Z","019df56f-8250-768c-a659-b9f524ff902c","multi-tenant",{"articleCount":293,"color":6,"createdAt":525,"id":526,"name":527,"slug":527,"updatedAt":525},"2026-05-08T04:00:17.998Z","019e05be-4c4d-759e-a51e-8ac760f82f6d","nextjs",{"articleCount":287,"color":6,"createdAt":529,"id":530,"name":531,"slug":531,"updatedAt":529},"2026-04-17T19:35:19.293Z","019d9cf0-c9fc-76a0-8c4e-b818a89c3774","nitro",{"articleCount":533,"color":6,"createdAt":534,"id":31,"name":32,"slug":32,"updatedAt":534},27,"2026-04-08T06:47:55.381Z",{"articleCount":287,"color":6,"createdAt":536,"id":537,"name":538,"slug":538,"updatedAt":536},"2026-04-30T04:19:02.038Z","019ddc9c-9315-735e-a7e8-8424790e8859","nuxt-ui",{"articleCount":287,"color":6,"createdAt":540,"id":102,"name":103,"slug":103,"updatedAt":540},"2026-06-08T12:00:16.030Z",{"articleCount":293,"color":6,"createdAt":542,"id":543,"name":544,"slug":544,"updatedAt":542},"2026-05-04T20:00:20.503Z","019df493-ce17-76ed-bd80-2a3914e0f409","open-source",{"articleCount":287,"color":6,"createdAt":546,"id":99,"name":100,"slug":100,"updatedAt":546},"2026-06-08T12:00:16.022Z",{"articleCount":315,"color":6,"createdAt":548,"id":35,"name":36,"slug":36,"updatedAt":548},"2026-05-18T12:00:14.389Z",{"articleCount":287,"color":6,"createdAt":550,"id":551,"name":552,"slug":552,"updatedAt":550},"2026-05-28T20:00:13.016Z","019e702c-50d7-74eb-8849-8b1cebcf411e","orchestration",{"articleCount":5,"color":6,"createdAt":554,"id":7,"name":8,"slug":8,"updatedAt":554},"2026-04-08T06:47:42.920Z",{"articleCount":287,"color":6,"createdAt":556,"id":557,"name":558,"slug":558,"updatedAt":556},"2026-06-10T13:53:29.575Z","019eb1cf-3de7-7326-87d9-c55ad1c0fa39","personalization",{"articleCount":287,"color":6,"createdAt":560,"id":561,"name":562,"slug":562,"updatedAt":560},"2026-04-17T19:35:19.263Z","019d9cf0-c9de-70b2-9057-76d771c3379e","pinia",{"articleCount":293,"color":6,"createdAt":564,"id":565,"name":566,"slug":566,"updatedAt":564},"2026-05-02T00:00:23.112Z","019de5fc-7e47-7075-8aeb-9e90f7689f57","postgres",{"articleCount":293,"color":6,"createdAt":568,"id":569,"name":570,"slug":570,"updatedAt":568},"2026-05-19T20:00:14.575Z","019e41d3-1aee-70c8-a07c-cec870115d14","pricing",{"articleCount":287,"color":6,"createdAt":572,"id":573,"name":574,"slug":574,"updatedAt":572},"2026-05-10T16:00:18.603Z","019e129e-34aa-723b-a568-45737915c7bf","qa",{"articleCount":287,"color":6,"createdAt":576,"id":577,"name":578,"slug":578,"updatedAt":576},"2026-05-08T04:00:18.013Z","019e05be-4c5c-75ad-8df5-602b3db57983","react",{"articleCount":315,"color":6,"createdAt":580,"id":79,"name":80,"slug":80,"updatedAt":580},"2026-04-20T12:00:11.653Z",{"articleCount":315,"color":6,"createdAt":582,"id":583,"name":584,"slug":584,"updatedAt":582},"2026-04-17T20:27:50.585Z","019d9d20-dfb9-7041-9973-39c545b33a2d","release",{"articleCount":293,"color":6,"createdAt":586,"id":587,"name":588,"slug":588,"updatedAt":586},"2026-05-26T08:00:14.619Z","019e634c-711a-728f-9b63-20f2c8b37ccb","routing",{"articleCount":293,"color":6,"createdAt":590,"id":591,"name":592,"slug":592,"updatedAt":590},"2026-05-19T16:00:13.509Z","019e40f7-5cc4-72e1-8f8b-692dd29fc716","sandbox",{"articleCount":287,"color":6,"createdAt":594,"id":595,"name":596,"slug":596,"updatedAt":594},"2026-05-14T12:00:21.362Z","019e265b-f571-7677-a537-2f7e96a490bf","scalability",{"articleCount":293,"color":6,"createdAt":598,"id":599,"name":600,"slug":600,"updatedAt":598},"2026-05-04T20:00:20.521Z","019df493-ce28-75e9-93d5-13251407a27b","scanning",{"articleCount":315,"color":6,"createdAt":602,"id":224,"name":225,"slug":225,"updatedAt":602},"2026-04-27T08:00:12.420Z",{"articleCount":287,"color":6,"createdAt":604,"id":605,"name":606,"slug":606,"updatedAt":604},"2026-06-17T12:00:12.428Z","019ed574-0a8b-7566-976f-8c281c820ed0","seo",{"articleCount":287,"color":6,"createdAt":608,"id":609,"name":610,"slug":610,"updatedAt":608},"2026-06-17T12:00:12.433Z","019ed574-0a91-74d8-b806-56681bb4b477","sitemap",{"articleCount":287,"color":6,"createdAt":612,"id":613,"name":614,"slug":614,"updatedAt":612},"2026-07-18T16:00:27.576Z","019f75f5-23b8-72eb-a2bf-79dd1fed3863","software-development",{"articleCount":287,"color":6,"createdAt":616,"id":617,"name":618,"slug":618,"updatedAt":616},"2026-04-17T19:35:19.297Z","019d9cf0-ca00-749d-9101-1c63bf62e215","spas",{"articleCount":336,"color":6,"createdAt":620,"id":54,"name":55,"slug":55,"updatedAt":620},"2026-06-03T16:00:12.620Z",{"articleCount":622,"color":6,"createdAt":623,"id":51,"name":52,"slug":52,"updatedAt":623},9,"2026-04-08T06:47:43.020Z",{"articleCount":293,"color":6,"createdAt":625,"id":626,"name":627,"slug":627,"updatedAt":625},"2026-04-30T04:19:02.613Z","019ddc9c-9555-7544-a3e3-0605d2c1ea82","startup",{"articleCount":315,"color":6,"createdAt":629,"id":630,"name":631,"slug":631,"updatedAt":629},"2026-04-18T12:00:12.027Z","019da076-78fb-706b-9a4c-cee0c989cfff","state-management",{"articleCount":293,"color":6,"createdAt":633,"id":634,"name":635,"slug":635,"updatedAt":633},"2026-06-06T00:00:11.717Z","019e9a3a-e5c5-76d3-86e4-576c151a87b3","storage",{"articleCount":287,"color":6,"createdAt":637,"id":638,"name":639,"slug":639,"updatedAt":637},"2026-06-17T12:00:12.446Z","019ed574-0a9e-7712-8bc5-a0102787fe48","structured-data",{"articleCount":287,"color":6,"createdAt":641,"id":642,"name":643,"slug":643,"updatedAt":641},"2026-05-10T16:00:18.597Z","019e129e-34a4-771a-844d-09a7edefcd64","tdd",{"articleCount":293,"color":6,"createdAt":645,"id":646,"name":647,"slug":647,"updatedAt":645},"2026-06-04T20:00:17.351Z","019e9438-e5c6-7681-8704-897c7b499eb4","terms-of-service",{"articleCount":336,"color":6,"createdAt":649,"id":263,"name":264,"slug":264,"updatedAt":649},"2026-04-09T06:11:45.799Z",{"articleCount":287,"color":6,"createdAt":651,"id":652,"name":653,"slug":653,"updatedAt":651},"2026-06-16T16:11:31.088Z","019ed133-c390-75bf-8f1a-5c57cd221f14","threejs",{"articleCount":293,"color":6,"createdAt":655,"id":656,"name":657,"slug":657,"updatedAt":655},"2026-05-02T00:00:23.130Z","019de5fc-7e59-7426-8d46-e79e4bcf0d56","tls",{"articleCount":659,"color":6,"createdAt":660,"id":143,"name":144,"slug":144,"updatedAt":660},10,"2026-04-08T06:47:55.591Z",{"articleCount":315,"color":6,"createdAt":662,"id":663,"name":664,"slug":664,"updatedAt":662},"2026-04-08T06:47:56.778Z","019d6bd9-0049-72ba-8cfa-139b1c1b249d","typescript",{"articleCount":666,"color":6,"createdAt":623,"id":285,"name":286,"slug":286,"updatedAt":623},5,{"articleCount":287,"color":6,"createdAt":668,"id":185,"name":186,"slug":186,"updatedAt":668},"2026-05-11T12:00:19.969Z",{"articleCount":287,"color":6,"createdAt":670,"id":139,"name":140,"slug":140,"updatedAt":670},"2026-05-18T12:00:14.379Z",{"articleCount":293,"color":6,"createdAt":672,"id":673,"name":674,"slug":674,"updatedAt":672},"2026-04-30T04:19:02.228Z","019ddc9c-93d3-763e-ad3c-d3ad78642de7","vercel",{"articleCount":287,"color":6,"createdAt":676,"id":677,"name":678,"slug":678,"updatedAt":676},"2026-07-13T08:00:18.274Z","019f5a7d-bf61-746b-a44b-5c0a16ff57d3","video",{"articleCount":315,"color":6,"createdAt":680,"id":119,"name":120,"slug":120,"updatedAt":680},"2026-04-17T19:35:19.289Z",{"articleCount":287,"color":6,"createdAt":682,"id":683,"name":684,"slug":684,"updatedAt":682},"2026-04-25T18:08:02.132Z","019dc5d3-c054-70e2-bca4-08e2a2b9a096","vitest",{"articleCount":287,"color":6,"createdAt":686,"id":687,"name":688,"slug":688,"updatedAt":686},"2026-06-27T12:00:13.107Z","019f08f3-a532-7049-a02c-c17a4fd99306","vscode",{"articleCount":690,"color":6,"createdAt":691,"id":74,"name":75,"slug":75,"updatedAt":691},32,"2026-04-08T06:47:42.793Z",{"articleCount":287,"color":6,"createdAt":693,"id":694,"name":695,"slug":695,"updatedAt":693},"2026-04-18T12:00:12.007Z","019da076-78e6-76bd-b0d4-4e0597d36378","vue-router",{"articleCount":293,"color":6,"createdAt":697,"id":698,"name":699,"slug":699,"updatedAt":697},"2026-05-04T20:00:20.510Z","019df493-ce1d-7039-811a-ed57bf081d26","vulnerability",{"articleCount":701,"color":6,"createdAt":702,"id":703,"name":704,"slug":704,"updatedAt":702},3,"2026-05-05T12:00:17.078Z","019df802-a8b6-74d2-a0cd-b0509cf502fa","web-development",{"articleCount":701,"color":6,"createdAt":706,"id":707,"name":708,"slug":708,"updatedAt":706},"2026-05-10T16:00:18.576Z","019e129e-3490-7739-a218-5454a8c15d6e","workflow"]