[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"contentNavigation":3,"topic-tutorial":4,"newsletter-stats":9,"articles-feed-\u002Ftopics\u002Ftutorial-1-tutorial-":11,"$fQa3xUpnRDPNiFFu2QS4K-DAjYmsapeR9fNhPNBdpkOc":-1,"home-tags":238},[],{"articleCount":5,"color":6,"id":7,"name":8,"slug":8},10,"#10b981","019d6bd8-fba5-743f-8f9f-4e23c0b31581","tutorial",{"confirmedCount":10},405,{"items":12,"page":236,"pageSize":237,"totalCount":5},[13,38,66,90,117,137,156,171,195,218],{"content":14,"createdAt":15,"id":16,"image":17,"isAffiliate":18,"isPublished":19,"publishedAt":20,"slug":21,"sourceId":22,"sourceName":23,"sourceType":24,"summary":25,"title":26,"updatedAt":27,"url":28,"urlHash":29,"tags":30},"A beginner-friendly, practical guide to adding an MCP server to an existing Nuxt app using the Nuxt MCP Toolkit and a mocked weather tool.","2026-07-28T12:00:23.261Z","019fa898-f0cd-7411-90ba-41fc972176f5","https:\u002F\u002Fmokkapps.twic.pics\u002Fmokkapps.de\u002Fblog\u002Fhow-to-setup-an-mcp-server-for-an-existing-nuxt-app\u002Fog.png",false,true,"2026-07-28T00:00:00.000Z","how-to-set-up-an-mcp-server-for-an-existing-nuxt-app","019d6bd5-57e0-742c-8de2-c0a3a1f49b60","Michael Hoffmann","rss","This article provides a beginner-friendly guide on setting up an MCP server for an existing Nuxt application, utilizing the Nuxt MCP Toolkit along with a mocked weather tool. It aims to help developers integrate this functionality seamlessly into their projects.","How to Set Up an MCP Server for an Existing Nuxt App","2026-07-28T12:00:27.797Z","https:\u002F\u002Fmokkapps.de\u002Fblog\u002Fhow-to-setup-an-mcp-server-for-an-existing-nuxt-app","820384c811f77ee3d2398ce478a910377e50d47ab020bd62e85ae9ad2e8b4bb7",[31,34,37],{"color":6,"id":32,"name":33,"slug":33},"019d6bd8-fad3-70a9-a74d-ab96e3a2f45d","nuxt",{"color":6,"id":35,"name":36,"slug":36},"019fa899-02e6-758e-bdcb-8a4a923507df","mcp",{"color":6,"id":7,"name":8,"slug":8},{"content":39,"createdAt":40,"id":41,"image":42,"isAffiliate":18,"isPublished":19,"publishedAt":43,"slug":44,"sourceId":45,"sourceName":46,"sourceType":24,"summary":47,"title":48,"updatedAt":49,"url":50,"urlHash":51,"tags":52},"Animations can make an application feel faster, smoother, and more polished. However, many developers think animations are only useful for things like: page transitions modals enter\u002Fleave effects But Vue provides another powerful pattern - State-driven animations. Instead of animating when elements are added or removed from the DOM, you animate changes in reactive state. This allows you to create rich interactive experiences while keeping your code declarative and easy to maintain. In this article, we'll explore: What state-driven animations are How they differ from regular Vue transitions What problems they solve How to implement them in Vue Best practices for creating smooth UI interactions Let's dive in. 🤔 What Are State-Driven Animations? Most Vue developers are familiar with the &lt;Transition&gt; component. Example: &lt;Transition&gt; &lt;Modal v-if=\"isOpen\" \u002F&gt; &lt;\u002FTransition&gt; This animates an element when it enters or leaves the DOM. But what if the element already exists and only its state changes? For example: a progress bar grows a card expands a chart updates a panel changes size a value changes position This is where state-driven animations shine. Instead of animating DOM insertion or removal, you animate changes caused by reactive state. 🟢 What Problem Do State-Driven Animations Solve? Without animations, state changes can feel abrupt. Example: &lt;div :style=\"{ width: progress + '%' }\"&gt;&lt;\u002Fdiv&gt; When progress changes: progress.value = 80 The width instantly jumps. This works technically... but it doesn't feel great. 🟢 A Simple Example Let's create an animated progress bar. &lt;script setup lang=\"ts\"&gt; const progress = ref(20) function increase() { progress.value += 20 } &lt;\u002Fscript&gt; &lt;template&gt; &lt;button @click=\"increase\"&gt; Increase Progress &lt;\u002Fbutton&gt; &lt;div class=\"progress-container\"&gt; &lt;div class=\"progress-bar\" :style=\"{ width: `${progress}%` }\" \u002F&gt; &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; CSS: .progress-container { width: 100%; height: 12px; background: #eee; } .progress-bar { height: 100%; background: #42b883; transition: width 0.3s ease; } Now whenever progress changes, the bar animates smoothly. The animation is entirely driven by reactive state. 🟢 Animating Multiple Properties State-driven animations are not limited to width. Example: &lt;div class=\"card\" :style=\"{ transform: expanded ? 'scale(1.1)' : 'scale(1)', opacity: expanded ? 1 : 0.7 }\" \u002F&gt; CSS: .card { transition: transform 0.3s ease, opacity 0.3s ease; } Now changing: expanded.value = true animates scale and opacity at the same time. 🟢 Using Vue Reactivity with Animations One of the biggest advantages is that animations stay connected to Vue's reactivity system. Example: &lt;script setup lang=\"ts\"&gt; const isActive = ref(false) &lt;\u002Fscript&gt; &lt;template&gt; &lt;button @click=\"isActive = !isActive\"&gt; Toggle &lt;\u002Fbutton&gt; &lt;div class=\"box\" :class=\"{ active: isActive }\" \u002F&gt; &lt;\u002Ftemplate&gt; CSS: .box { width: 100px; height: 100px; transition: all 0.4s ease; } .box.active { transform: rotate(180deg); } Vue handles state. CSS handles animation. The result is clean and maintainable. 🧪 Best Practices Keep animations subtle and purposeful Prefer CSS transitions for simple effects Avoid animating expensive properties when possible Use transforms instead of layout-changing properties when appropriate Don't animate everything Keep durations short (typically 200–400ms) Use animations to communicate state changes, not distract users 📖 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 State-driven animations are a powerful way to create smooth and engaging user experiences in Vue. While &lt;Transition&gt; is perfect for entering and leaving elements, state-driven animations excel when existing elements need to react smoothly to changing data. Used thoughtfully, they can make your applications feel significantly more responsive and professional. Take care! And happy coding as always 🖥️","2026-06-01T12:00:05.566Z","019e830e-2fa7-74ed-8c74-71fc2c69cb52","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%2Fvih3y2fdc8upo7ni9m42.png","2026-06-01T09:54:37.000Z","state-driven-animations-in-vue-create-smooth-ui-transitions-with-reactive-state","019d6bd6-7fe0-7244-80dc-9a4e8751886a","Jakub Andrzejewski","This article explores state-driven animations in Vue, highlighting how they enhance UI transitions by animating changes in reactive state rather than just DOM insertion or removal. It provides examples and best practices for implementing these animations to create smoother, more interactive user experiences.","State-Driven Animations in Vue: Create Smooth UI Transitions with Reactive State","2026-06-10T14:35:13.767Z","https:\u002F\u002Fdev.to\u002Fjacobandrewsky\u002Fstate-driven-animations-in-vue-create-smooth-ui-transitions-with-reactive-state-d3i","b6046ff3585c584d9e28b56411b296d962249089895ddbb6902902fabcfdafec",[53,56,59,62,65],{"color":6,"id":54,"name":55,"slug":55},"019d6bd8-c9a8-7783-bd22-03145b355427","vue",{"color":6,"id":57,"name":58,"slug":58},"019e830e-5378-722b-929b-a57d67bcf605","animation",{"color":6,"id":60,"name":61,"slug":61},"019daac3-2f85-7393-b891-9da3891267ca","reactivity",{"color":6,"id":63,"name":64,"slug":64},"019d6bd8-ca8b-709c-b9a5-77d4910da162","ui-components",{"color":6,"id":7,"name":8,"slug":8},{"content":67,"createdAt":68,"id":69,"image":70,"isAffiliate":18,"isPublished":19,"publishedAt":71,"slug":72,"sourceId":45,"sourceName":46,"sourceType":24,"summary":73,"title":74,"updatedAt":75,"url":76,"urlHash":77,"tags":78},"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",[79,80,83,86,89],{"color":6,"id":54,"name":55,"slug":55},{"color":6,"id":81,"name":82,"slug":82},"019d6bd8-ca26-775c-b9b5-c3439dbe5789","performance",{"color":6,"id":84,"name":85,"slug":85},"019e3af5-4a29-759a-81b0-b6d502b2449e","v-memo",{"color":6,"id":87,"name":88,"slug":88},"019e3af5-4a35-705c-8ab1-4404b653e0e8","optimization",{"color":6,"id":7,"name":8,"slug":8},{"content":91,"createdAt":92,"id":93,"image":94,"isAffiliate":19,"isPublished":19,"publishedAt":95,"slug":96,"sourceId":97,"sourceName":98,"sourceType":99,"summary":100,"title":101,"updatedAt":102,"url":103,"urlHash":104,"tags":105},"This entry is part 1 of 2 in the series Vue Component DesignGuess what!? We’ve just published a new course all about component design patterns in Vue.js. It’s sure to improve your Vue codebase with battle-tested patterns for scalability and maintainability. We created the course primarily for beginners to help them up their component development skills, but even more learned Vue devs might find a new pattern or two. Go checkout the course now or keep reading for an overview of some of the patterns discussed in the videos. 1. The Branching Component Design Pattern Extract complex conditional rendering into separate components. Instead of multiple v-if branches in one component, create dedicated components for each state. The resulting code is easier to read and maintain over time. &lt;!-- Before --&gt; &lt;template&gt; &lt;div&gt; &lt;div v-if=&quot;loading&quot;&gt;Loading...&lt;\u002Fdiv&gt; &lt;div v-else-if=&quot;error&quot;&gt;Error: {{ error }}&lt;\u002Fdiv&gt; &lt;div v-else&gt; &lt;!-- Complex content --&gt; &lt;\u002Fdiv&gt; &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; &lt;!-- After --&gt; &lt;template&gt; &lt;div&gt; &lt;LoadingState v-if=&quot;loading&quot; \u002F&gt; &lt;ErrorState v-else-if=&quot;error&quot; :message=&quot;error&quot; \u002F&gt; &lt;ContentState v-else :data=&quot;data&quot; \u002F&gt; &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; 2. Slots and Template Props Design Pattern Whenever you have a prop that gets passed directly to the template, that’s a good sign you should be using a slot instead. Why? They serve the same purpose, but the slot allows more flexibility. &lt;!-- AppButton.vue --&gt; &lt;!--Before--&gt; &lt;script setup&gt; defineProps({ label: { type: String, default: &quot;Click Me&quot; } }) &lt;\u002Fscript&gt; &lt;template&gt; &lt;button class=&quot;btn&quot;&gt; &lt;!-- the label makes a beeline to the template and makes no stops along the way--&gt; {{ label }} &lt;\u002Fbutton&gt; &lt;\u002Ftemplate&gt; &lt;!--After--&gt; &lt;template&gt; &lt;button class=&quot;btn&quot;&gt; &lt;!-- ahh, that&#039;s better now we can pass in markup, icons, components whatever--&gt; &lt;slot&gt;&lt;\u002Fslot&gt; &lt;\u002Fbutton&gt; &lt;\u002Ftemplate&gt; Thanks Michael Thiessen for this wonderful pattern and a memorable name to boot! 3. List with ListItem Component Pattern Separate list logic and item display into distinct components. This allows for bundling up list empty state, content, controls, and more into a contained List component. Extracting each item in the list to it’s own ListItem component, makes the List component easy to read, and provides more focus when developing and styling each item. &lt;!-- UsersList.vue --&gt; &lt;script setup&gt; const props = defineProps({ users: { type: Array, required: true } }); const filter = ref(&quot;&quot;) const filteredUsers = computed(()=&gt;{ \u002F\u002F filter logic here }) &lt;\u002Fscript&gt; &lt;template&gt; &lt;div&gt; &lt;!-- List Controls --&gt; &lt;UserFilters v-model=&quot;filter&quot; \u002F&gt; &lt;!-- List Content --&gt; &lt;ul v-if=&quot;filteredUsers.length&quot;&gt; &lt;!-- items extracted to their own component (see below) --&gt; &lt;UserListItem v-for=&quot;user in filteredUsers&quot; :key=&quot;user.id&quot; :user=&quot;user&quot; \u002F&gt; &lt;\u002Ful&gt; &lt;!-- Empty State--&gt; &lt;EmptyState v-else message=&quot;No users found&quot; \u002F&gt; &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; &lt;!-- UserListItem.vue --&gt; &lt;script setup&gt; defineProps({ user: { type: Object, required: true } }) &lt;\u002Fscript&gt; &lt;template&gt; &lt;li class=&quot;user-item&quot;&gt; &lt;img :src=&quot;user.avatar&quot; \u002F&gt; &lt;span&gt;{{ user.name }}&lt;\u002Fspan&gt; &lt;\u002Fli&gt; &lt;\u002Ftemplate&gt; 4. Smart vs Dumb Components Pattern Separate data handling from presentation. Smart components manage logic and data fetching, dumb components simply take in props to handle display. Furthermore, base components provide the fundamental building blocks of your application (such as cards, buttons, etc) and by convention start with v, base, or app. &lt;!-- Smart Component --&gt; &lt;script setup&gt; import { ref } from &#039;vue&#039; const users = ref([]) const loading = ref(true) async function fetchUsers() { users.value = await api.getUsers() loading.value = false } function createUser(){ \u002F\u002F open modal to create user or whatever } &lt;\u002Fscript&gt; &lt;template&gt; &lt;AppButton @click=&quot;createUser&quot; &gt;Create User&lt;\u002FAppButton&gt; &lt;UserList :users=&quot;users&quot; :loading=&quot;loading&quot; \u002F&gt; &lt;\u002Ftemplate&gt; &lt;!-- Dumb Component --&gt; &lt;script setup&gt; defineProps({ users: Array, loading: Boolean }) &lt;\u002Fscript&gt; &lt;template&gt; &lt;div class=&quot;user-list&quot;&gt; &lt;!-- Pure presentation logic --&gt; &lt;\u002Fdiv&gt; &lt;\u002Ftemplate&gt; 5. Form Component Design Pattern v-model on native input fields is awesome! We can do similar with whole forms. This approach accounts for the way users interact with forms: they expect committed data only after click of submit. It also works like a charm with native input validation as the submit event never fires until native validation passes. &lt;script setup&gt; \u002F\u002F support v-model const user = defineModel() \u002F\u002F clone the modelValue to local data \u002F\u002F and provide a fallback user if none provided const form = ref(clone(user) || { name: &#039;&#039;, emaill: &#039;&#039; }) \u002F\u002F only update the modelValue when the form is submitted function handleSubmit() { user.value = clone(form.value); } \u002F\u002F Reset form when prop changes watch(user, () =&gt; form.value = clone(user.value)) const clone = (obj)=&gt; JSON.parse(JSON.stringify(obj)) &lt;\u002Fscript&gt; &lt;template&gt; &lt;form @submit.prevent=&quot;handleSubmit&quot;&gt; &lt;!-- Bind the form inputs to the local data instead of the modelValue --&gt; &lt;input v-model=&quot;form.name&quot; required\u002F&gt; &lt;input v-model=&quot;form.email&quot; \u002F&gt; &lt;!-- Bonus! You can even have dynamic submit button labels based on the modelValue passed in--&gt; &lt;button type=&quot;submit&quot;&gt; {{ user ? &#039;Update&#039; : &#039;Create&#039; }} User &lt;\u002Fbutton&gt; &lt;\u002Fform&gt; &lt;\u002Ftemplate&gt; Looking for More Patterns? We cover all these patterns discussed above in more detail in our course: Vue Component Design. Plus get a preview of some more advanced patterns like the Tightly Coupled Components Pattern, Recursive Components, and Lazy Dynamic Components. Besides our own course, Michael Thiessen’s Clean Components Toolkit is a great resource for further exploring not only component patterns but also patterns for stores, composables, and more.","2026-05-14T12:00:11.684Z","019e265b-cf92-75c7-bb8d-65f5cb9a819d","https:\u002F\u002Fblog.vueschool.io\u002Fwp-content\u002Fuploads\u002F2024\u002F12\u002FVS_5-Component-Design-Patterns-to-Boost-Your-Vue.js-Applications-1.png","2026-05-14T08:00:26.000Z","5-component-design-patterns-to-boost-your-vuejs-applications","019d6bd5-87de-77ef-ac92-98aa56cda920","VueSchool","vueschool","This article introduces a new course focused on component design patterns in Vue.js, aimed at enhancing scalability and maintainability of Vue applications. It outlines several design patterns, such as the Branching Component Design Pattern and the use of slots, which can help developers improve their code structure and readability.","5 Component Design Patterns to Boost Your Vue.js Applications","2026-05-14T12:00:21.296Z","https:\u002F\u002Fblog.vueschool.io\u002Fvuejs-tutorials\u002F5-component-design-patterns-to-boost-your-vue-js-applications\u002F?friend=MOKKAPPS","998659ef4f9a67af7ef263b481fc23eaa61736efd82e77946c3a11812701ff78",[106,107,110,113,116],{"color":6,"id":54,"name":55,"slug":55},{"color":6,"id":108,"name":109,"slug":109},"019e265b-f569-71d8-a02a-a17ec8180644","component-design",{"color":6,"id":111,"name":112,"slug":112},"019e265b-f571-7677-a537-2f7e96a490bf","scalability",{"color":6,"id":114,"name":115,"slug":115},"019e265b-f579-71cd-84b2-ac385b5225ae","maintainability",{"color":6,"id":7,"name":8,"slug":8},{"content":118,"createdAt":119,"id":120,"image":121,"isAffiliate":18,"isPublished":19,"publishedAt":122,"slug":123,"sourceId":124,"sourceName":125,"sourceType":126,"summary":127,"title":128,"updatedAt":129,"url":130,"urlHash":131,"tags":132},"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",[133,134,135,136],{"color":6,"id":32,"name":33,"slug":33},{"color":6,"id":54,"name":55,"slug":55},{"color":6,"id":81,"name":82,"slug":82},{"color":6,"id":7,"name":8,"slug":8},{"content":138,"createdAt":139,"id":140,"image":141,"isAffiliate":19,"isPublished":19,"publishedAt":142,"slug":143,"sourceId":144,"sourceName":145,"sourceType":146,"summary":147,"title":148,"updatedAt":149,"url":150,"urlHash":151,"tags":152},"Using Vue plainly or adding Nuxt to it:  side-by-side comparison of building the same features in plain Vue and Nuxt, so you can see exactly what the framework gives you.","2026-04-30T04:18:53.710Z","019ddc9c-727b-76e6-bbe9-2e57c4783354","https:\u002F\u002Fapi.certificates.dev\u002Fstorage\u002FHnWHeIb9HLni5mNy5Zx4DoLdS9NquZGL4xFDyMR4.png","2026-04-29T06:00:00.000Z","plain-vue-or-going-meta","019d9eed-d835-729a-a029-7e6320cb67ed","Certificates.dev","certificatesdev","This article provides a side-by-side comparison of building features using plain Vue versus using Nuxt, highlighting the advantages and additional capabilities that Nuxt offers. It aims to help developers decide whether to use Vue alone or to incorporate Nuxt for their projects.","Plain Vue or going Meta?","2026-04-30T04:19:02.039Z","https:\u002F\u002Fcertificates.dev\u002Fblog\u002Fplain-vue-or-going-meta?friend=MOKKAPPS","fc38b56c8a637ec563a6ea40daeaf83218a14d09ff989566709fcabc9fd085c2",[153,154,155],{"color":6,"id":54,"name":55,"slug":55},{"color":6,"id":32,"name":33,"slug":33},{"color":6,"id":7,"name":8,"slug":8},{"content":157,"createdAt":158,"id":159,"image":160,"isAffiliate":18,"isPublished":19,"publishedAt":161,"slug":162,"sourceId":124,"sourceName":125,"sourceType":126,"summary":163,"title":164,"updatedAt":165,"url":166,"urlHash":167,"tags":168},"Ever wondered how deep your knowledge of the framework really goes? Beyond the daily routine of components and ...","2026-04-22T12:00:01.693Z","019db50f-c08a-70b8-8987-727e9b105054","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FfNJCzx4k2Ik\u002Fhqdefault.jpg","2026-04-22T10:01:13.000Z","thorsten-seyschab---vue-think-you-know-it-all-the-ultimate-vue-live-quiz","This article introduces an engaging live quiz designed to test your knowledge of Vue.js, challenging participants to go beyond their everyday understanding of the framework. It promises to reveal the depths of your expertise in Vue through a fun and interactive format.","Thorsten Seyschab - Vue Think You Know It All The Ultimate Vue Live Quiz","2026-04-22T12:00:13.537Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=fNJCzx4k2Ik","3acaa865cfa59009f9baeaf70e602f21c68f900e6f62702e325ccab335ecaa1f",[169,170],{"color":6,"id":54,"name":55,"slug":55},{"color":6,"id":7,"name":8,"slug":8},{"content":172,"createdAt":173,"id":174,"image":175,"isAffiliate":19,"isPublished":19,"publishedAt":176,"slug":177,"sourceId":144,"sourceName":145,"sourceType":146,"summary":178,"title":179,"updatedAt":180,"url":181,"urlHash":182,"tags":183},"Learn how to use composables in Nuxt effectively, avoid common SSR and state pitfalls, and build production-ready patterns in this practical 15-minute guide.","2026-04-25T18:07:52.417Z","019dc5d3-9a44-72c8-8f6c-0262bb4041d2","https:\u002F\u002Fapi.certificates.dev\u002Fstorage\u002FfLgc0NdkTT9Vgq2AhUNulkZwaj6txgVnAAOsHKFu.png","2026-04-22T07:00:00.000Z","composable-best-practices-in-nuxt","This article provides a practical guide on using composables in Nuxt, highlighting best practices to avoid common pitfalls related to server-side rendering (SSR) and state management. It aims to help developers build production-ready patterns efficiently.","Composable Best Practices in Nuxt","2026-04-25T18:08:01.170Z","https:\u002F\u002Fcertificates.dev\u002Fblog\u002Fcomposable-best-practices-in-nuxt?friend=MOKKAPPS","b506b0480c7b2d23a037e48443ae0e8d769398478e0b1f5d56539112166fea86",[184,185,188,191,194],{"color":6,"id":32,"name":33,"slug":33},{"color":6,"id":186,"name":187,"slug":187},"019d6bd8-ca21-71c5-a236-37d94fe57d24","composition-api",{"color":6,"id":189,"name":190,"slug":190},"019da076-78fb-706b-9a4c-cee0c989cfff","state-management",{"color":6,"id":192,"name":193,"slug":193},"019d6bd8-ca89-735e-a52a-ee53a80a77a9","ssr",{"color":6,"id":7,"name":8,"slug":8},{"content":196,"createdAt":197,"id":198,"image":199,"isAffiliate":18,"isPublished":19,"publishedAt":200,"slug":201,"sourceId":202,"sourceName":203,"sourceType":126,"summary":204,"title":205,"updatedAt":206,"url":207,"urlHash":208,"tags":209},"Thank you InsForge for sponsoring this video! https:\u002F\u002Finsforge.dev\u002F Nuxt Course: https:\u002F\u002Fwww.learnnuxt.dev In this video, I'll ...","2026-04-17T20:27:43.729Z","019d9d20-c4e3-74d8-abc9-b905cb285ced","https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FIR4l6GjoxH8\u002Fhqdefault.jpg","2026-04-17T17:00:17.000Z","build-a-backend-in-minutes-with-ai-prompts-claude-insforge-amp-nuxt","019d6c23-e4e7-7379-a6f7-80d0e5734cd1","John Komarnicki","This article discusses how to quickly build a backend using AI prompts with Claude and InsForge, while integrating Nuxt into the process. It highlights the tools and resources available for developers looking to streamline backend development.","Build a Backend in Minutes with AI Prompts | Claude + InsForge &amp; Nuxt","2026-04-17T20:27:50.745Z","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=IR4l6GjoxH8","4e7e7fe0f09ec09c17d4760065d998b8251ff49d6184fc19b7d17093010c3688",[210,211,214,217],{"color":6,"id":32,"name":33,"slug":33},{"color":6,"id":212,"name":213,"slug":213},"019d9d20-e077-71cc-a605-8ac2a1566143","backend",{"color":6,"id":215,"name":216,"slug":216},"019d9d20-e07b-7685-9ebb-fae0b963f243","ai",{"color":6,"id":7,"name":8,"slug":8},{"content":219,"createdAt":220,"id":221,"image":222,"isAffiliate":19,"isPublished":19,"publishedAt":223,"slug":224,"sourceId":97,"sourceName":98,"sourceType":99,"summary":225,"title":226,"updatedAt":227,"url":228,"urlHash":229,"tags":230},"RAG (Retrieval-Augmented Generation) is one of the most practical ways to make AI apps useful in the real world. Instead of asking a model to answer from generic training data, you: Index your own documents. Retrieve the most relevant portions of those documents (called &quot;chunks&quot;) at query time. Generate an answer grounded in those chunks. In this tutorial, you will build a working Nuxt backend that does exactly that using Google's robust but easy-to-implement RAG solution: Gemini File Search. I'll also provide the frontend UI so you can test the flow end to end. What we are building Server side utility functions for interacting with the Gemini File Search API. Server side utility functions for managing the indexing and asking processes. A Nuxt server endpoint that creates a File Search store. A Nuxt server endpoint that uploads and indexes documents (text from a textarea input) into a Gemini File Search store. A polling endpoint that checks the status of the indexing operation. Another endpoint that queries the store with the File Search tool to answer a question. To best showcase the RAG pipeline in a practical way, I'll also provide you with a simple UI to test things out. This includes: An input field to name your File Search store (auto-generated the first time). A text area to provide plain text documents to upload and index into the store. An input field to ask a question about any of the indexed documents Status updates for the indexing and asking processes. A section to display the answer grounded in the indexed text. I'll also provide you with a page to manage the indexed documents in the store (view and delete them). You can download and run the completed demo app from the GitHub repo. Definition of Relevant RAG Terms Throughout this guide, we will use the following terms related to RAG and the Gemini File Search API. Make sure you understand them before continuing. RAG: Retrieval-Augmented Generation is a technique that uses a large language model to answer questions by retrieving relevant context from a knowledge base. File Search: File Search is a Gemini API that allows you to index and search through your own documents. (aka. a batteries-included RAG pipeline) Chunk: A chunk is a portion of a document that is indexed by the File Search API. Documents are split into these chunks so that the model can retrieve only the most relevant context when answering questions. Store: A store is a collection of documents that are indexed by the File Search API. You can query 1 store at a time. Useful for organizing your documents into logical groups. Tool: A tool is a function that can be called by a language model to perform a task. (in this case, the File Search tool) Prerequisites Node.js and npm installed A Google Gemini API key (you can get one from Google AI Studio here) Step 1) Create a Nuxt App Create a new Nuxt app with the minimal template: npm create nuxt@latest nuxt-rag-app -- -t minimal Then work from your app root: cd nuxt-rag-app All paths in the rest of this guide are relative to that project root. Step 2) Install dependencies (Gemini SDK) ni @google\u002Fgenai @nuxtjs\u002Fmdc @google\u002Fgenai is the Google Gemini API SDK. Alternately you could use the AI SDK as we discuss in our course AI Interfaces with Vue, Nuxt, and the AI SDK. It would make streaming the model output to the frontend a piece of cake. Plus it has other benefits but we're going to keep it simple for this tutorial and forego streaming. Step 3) Expose Your API Key via Nuxt Runtime Config Create a .env file in the root of your project and add your API key: NUXT_GOOGLE_GENERATIVE_AI_API_KEY=your-api-key Then update nuxt.config.ts to expose it via runtime config: \u002F\u002F https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fapi\u002Fconfiguration\u002Fnuxt-config export default defineNuxtConfig({ compatibilityDate: &quot;2025-07-15&quot;, devtools: { enabled: true }, modules: [&quot;@nuxtjs\u002Fmdc&quot;], runtimeConfig: { \u002F\u002F this variable name must match the name in the .env file (converted to camelCase and without the NUXT_ prefix) googleGenerativeAiApiKey: &quot;&quot;, }, }); Step 4) Add File Search Helper Functions Before we add API endpoints, let's create a set of helper functions in server\u002Futils\u002Fgemini-file-search.ts to manage the File Search operations with Gemini. 1. Import dependencies First, import the required modules. \u002F\u002F server\u002Futils\u002Fgemini-file-search.ts import { GoogleGenAI } from &quot;@google\u002Fgenai&quot;; 2. Helper function to create an authenticated API client Then create a function that initializes the Gemini API client with your API key and handles missing API key errors. \u002F\u002F server\u002Futils\u002Fgemini-file-search.ts function getApiKey() { const runtimeConfig = useRuntimeConfig(); const apiKey = runtimeConfig.googleGenerativeAiApiKey; if (!apiKey) { throw createError({ statusCode: 500, statusMessage: &quot;Missing NUXT_GOOGLE_GENERATIVE_AI_API_KEY .env variable.&quot;, }); } return apiKey; } function getClient() { return new GoogleGenAI({ apiKey: getApiKey() }); } 3. Create a File Search store Next, provide a helper function for creating a File Search store. \u002F\u002F server\u002Futils\u002Fgemini-file-search.ts export async function createFileSearchStore(params: { displayName: string }) { const ai = getClient(); const store = await ai.fileSearchStores.create({ config: { displayName: params.displayName, }, }); return store.name ?? &quot;&quot;; } 4. Wait for an asynchronous operation from the API to complete Gemini File Search indexing happens asynchronously. This function polls the operation status until it's done. async function waitForOperation(ai: GoogleGenAI, operation: any) { let current = operation; while (!current.done) { await new Promise((resolve) =&gt; setTimeout(resolve, 1000)); current = await ai.operations.get({ operation: current }); } return current; } 5. Upload and index a text document in the store This function uploads your text directly from memory as a Blob, then waits for indexing to finish. export async function uploadTextToStore(params: { fileSearchStoreName: string; content: string; displayName: string; }) { const ai = getClient(); const markdownBlob = new Blob([params.content], { type: &quot;text\u002Fmarkdown&quot; }); const operation = await ai.fileSearchStores.uploadToFileSearchStore({ file: markdownBlob, fileSearchStoreName: params.fileSearchStoreName, config: { displayName: params.displayName, mimeType: &quot;text\u002Fmarkdown&quot;, }, }); await waitForOperation(ai, operation); } 6. Ask questions using the indexed File Search context This function sends a question to Gemini, instructing it to answer only using retrieved context from your uploaded files. If Gemini can't answer with the provided context, it will say so. Most importantly, note the usage of the fileSearch tool that gives Gemini access to the indexed documents! export async function askStore(params: { fileSearchStoreName: string; question: string; }) { const ai = getClient(); const groundedQuestion = [ &quot;Answer using only the retrieved File Search context.&quot;, &#039;If the context does not contain the answer, say: &quot;I do not know based on the uploaded documents.&quot;&#039;, `Question: ${params.question}`, ].join(&quot;\\n&quot;); const response = await ai.models.generateContent({ model: &quot;gemini-3-flash-preview&quot;, contents: groundedQuestion, config: { \u002F\u002F 👇 this is the key part that gives Gemini access to the indexed documents! tools: [ { fileSearch: { fileSearchStoreNames: [params.fileSearchStoreName], }, }, ], }, }); return { text: response.text ?? &quot;&quot;, groundingMetadata: response.candidates?.[0]?.groundingMetadata ?? null, }; } Step 5) Add display-name helper functions After creating the File Search helpers, add one small utility file for generating store and document names. Create server\u002Futils\u002Frag-display-names.ts: \u002F\u002F used if no store name is provided export function createStoreDisplayName() { return `nuxt-rag-store-${Date.now()}`; } \u002F\u002F since we&#039;re using a text input instead of an actual document with a filename, we need to generate a unique name for the document we import \u002F\u002F this does that based on the first non-empty line of the content export function createDisplayNameFromContent(content: string) { const firstNonEmptyLine = content .split(&quot;\\n&quot;) .map((line) =&gt; line.trim()) .find((line) =&gt; line.length &gt; 0); const base = (firstNonEmptyLine || &quot;notes&quot;) .replace(\u002F^#+\\s*\u002F, &quot;&quot;) .replace(\u002F[^a-zA-Z0-9\\s-]\u002Fg, &quot;&quot;) .trim() .replace(\u002F\\s+\u002Fg, &quot;-&quot;) .toLowerCase() .slice(0, 48); return `${base || &quot;notes&quot;}-${Date.now()}`; } Step 6) Add storage and indexing-status helper functions Indexing is asynchronous, so for a better UX we will: create an index job immediately, update job status in KV storage (useStorage()), and poll job status from the frontend. Let's create some helper functions, variables, and types to help manage this process. 1. Define Types and Constants We'll start by defining the job status type, the job object shape, and a key prefix for our storage. \u002F\u002F server\u002Futils\u002Frag-index-jobs.ts import { randomUUID } from &quot;node:crypto&quot;; \u002F\u002F Possible statuses for an indexing job export type RAGIndexJobStatus = &quot;pending&quot; | &quot;succeeded&quot; | &quot;failed&quot;; \u002F\u002F The job object structure export type RAGIndexJob = { id: string; status: RAGIndexJobStatus; fileSearchStoreName: string; displayName: string; createdAt: string; updatedAt: string; errorMessage?: string; \u002F\u002F Populated if failed }; \u002F\u002F Prefix for storing job entries in KV export const INDEX_JOB_PREFIX = &quot;rag:index-job:&quot;; 2. Helpers for Job Storage Keys Next, we need a function to generate a unique storage key for each job based on its ID. export function getJobKey(jobId: string) { return `${INDEX_JOB_PREFIX}${jobId}`; } 3. Creating a New Index Job When we start an indexing operation, we want to create a new job entry in our KV storage with a unique ID and an initial status of &quot;pending&quot;. export async function createIndexJob(params: { fileSearchStoreName: string; displayName: string; }) { const job: RAGIndexJob = { id: randomUUID(), status: &quot;pending&quot;, fileSearchStoreName: params.fileSearchStoreName, displayName: params.displayName, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; await useStorage().setItem(getJobKey(job.id), job); return job; } 4. Retrieving a Saved Index Job This function allows you to fetch job details from storage by job ID. export async function getIndexJob(jobId: string) { return await useStorage().getItem&lt;RAGIndexJob&gt;(getJobKey(jobId)); } 5. Marking a Job as Succeeded When an indexing operation completes successfully, use this to update its status. export async function markIndexJobSucceeded(jobId: string) { const existing = await getIndexJob(jobId); if (!existing) return; await useStorage().setItem(getJobKey(jobId), { ...existing, status: &quot;succeeded&quot;, updatedAt: new Date().toISOString(), errorMessage: undefined, }); } 6. Marking a Job as Failed If indexing fails, call this to record the failure and the error message. export async function markIndexJobFailed(params: { jobId: string; errorMessage: string; }) { const existing = await getIndexJob(params.jobId); if (!existing) return; await useStorage().setItem(getJobKey(params.jobId), { ...existing, status: &quot;failed&quot;, updatedAt: new Date().toISOString(), errorMessage: params.errorMessage, }); } With these helpers in place, you can create, update, check, and manage the lifecycle of document indexing jobs in your app. Step 7) Add API endpoints for stores, indexing, and asking questions Now the API layer can focus on request validation and orchestration while reusing helper functions from server\u002Futils. Let's see how to create the API endpoints for creating stores, indexing documents, and asking questions. 1. Create a store API endpoint Create server\u002Fapi\u002Frag\u002Fstore.post.ts with the following code to support creating a store. export default defineEventHandler(async (event) =&gt; { \u002F\u002F get the name of the store from the body const body = await readBody&lt;{ displayName?: string }&gt;(event); const displayName = body.displayName?.trim() || createStoreDisplayName(); \u002F\u002F create the store with the helper function const fileSearchStoreName = await createFileSearchStore({ displayName, }); \u002F\u002F if the store creation failed, throw an error if (!fileSearchStoreName) { throw createError({ statusCode: 500, statusMessage: &quot;Failed to create a File Search store.&quot;, }); } \u002F\u002F return the name of the store return { fileSearchStoreName, }; }); 2. Create an indexing API endpoint Create server\u002Fapi\u002Frag\u002Findex.ts with the following code to support indexing a document. export default defineEventHandler(async (event) =&gt; { const body = await readBody&lt;{ content?: string; displayName?: string; storeName?: string; }&gt;(event); \u002F\u002F get the content of the document to index \u002F\u002F and the store to index it into \u002F\u002F from the request body const content = body.content?.trim(); const fileSearchStoreName = body.storeName?.trim(); \u002F\u002F if the content is not provided, throw an error if (!content) { throw createError({ statusCode: 400, statusMessage: &#039;Request body needs a non-empty &quot;content&quot; field.&#039;, }); } \u002F\u002F if the store name is not provided, throw an error if (!fileSearchStoreName) { throw createError({ statusCode: 400, statusMessage: &#039;Request body needs a non-empty &quot;storeName&quot; field.&#039;, }); } \u002F\u002F if the display name for the document is not provided, create one from the content const displayName = body.displayName || createDisplayNameFromContent(content); \u002F\u002F and then initialize the indexing job in KV storage const job = await createIndexJob({ fileSearchStoreName, displayName, }); \u002F\u002F create a function to bundle \u002F\u002F - doing the indexing \u002F\u002F - and update the indexing status in KV storage const runIndexing = async () =&gt; { try { await uploadTextToStore({ fileSearchStoreName, content, displayName, }); await markIndexJobSucceeded(job.id); } catch (error: any) { await markIndexJobFailed({ jobId: job.id, errorMessage: error?.data?.statusMessage || error?.message || &quot;Unknown indexing error&quot;, }); } }; \u002F\u002F Do the indexing in the background event.waitUntil(runIndexing()); \u002F\u002F and return the job id and status immediately with a 202 status code setResponseStatus(event, 202); return { ok: true, accepted: true, jobId: job.id, jobStatus: &quot;pending&quot;, fileSearchStoreName, }; }); 3. Create an index-status API endpoint With the indexing API endpoint in place, we can kick off the indexing process but we don't yet have a way to check the status of the indexing job. Let's create an endpoint to do that. Create server\u002Fapi\u002Frag\u002Findex-status.get.ts: export default defineEventHandler(async (event) =&gt; { const query = getQuery(event); const jobId = String(query.jobId || &quot;&quot;).trim(); \u002F\u002F require the job id to be provided \u002F\u002F we can&#039;t check the status of a job if we don&#039;t know the job id 🤪 if (!jobId) { throw createError({ statusCode: 400, statusMessage: &#039;Query string needs a non-empty &quot;jobId&quot; value.&#039;, }); } \u002F\u002F get the job from the KV storage const job = await getIndexJob(jobId); \u002F\u002F if the job is not found, throw an error if (!job) { throw createError({ statusCode: 404, statusMessage: &quot;Index job not found.&quot;, }); } \u002F\u002F return the job from the KV storage return { job }; }); Great work. You now have the backend flow for indexing documents. Easier than you thought, right? 4. Create an ask API endpoint What's a document index without a way to ask questions about it? Now let's create an endpoint to do that. Create server\u002Fapi\u002Frag\u002Fask.post.ts: export default defineEventHandler(async (event) =&gt; { \u002F\u002F get the question from the request body \u002F\u002F and the store of documents to ask the question about const body = await readBody&lt;{ question?: string; storeName?: string }&gt;(event); const question = body.question?.trim(); const fileSearchStoreName = body.storeName?.trim(); \u002F\u002F if the question is not provided, throw an error if (!question) { throw createError({ statusCode: 400, statusMessage: &#039;Request body needs a non-empty &quot;question&quot; field.&#039;, }); } \u002F\u002F if the store name is not provided, throw an error if (!fileSearchStoreName) { throw createError({ statusCode: 400, statusMessage: &#039;Request body needs a non-empty &quot;storeName&quot; field.&#039;, }); } \u002F\u002F use the askStore helper function to ask the question of the File Search store \u002F\u002F You could stream this response, but for simplicity we are not doing that here. const result = await askStore({ fileSearchStoreName, question }); \u002F\u002F The groundingChunks from the File Search API return the context used to answer the question. \u002F\u002F we need to map that to the title, text, and fileSearchStore of the document that was used \u002F\u002F so we can display the attributions in the UI const attributions = (result.groundingMetadata?.groundingChunks ?? []) .map((chunk: any) =&gt; chunk?.retrievedContext) .filter(Boolean) .map((ctx: any) =&gt; ({ title: ctx.title ?? &quot;Untitled document&quot;, text: ctx.text ?? &quot;&quot;, fileSearchStore: ctx.fileSearchStore ?? fileSearchStoreName, })); \u002F\u002F that&#039;s it! return { answer: result.text, attributions, groundingMetadata: result.groundingMetadata, fileSearchStoreName, }; }); Step 8) Hook up the UI Since this tutorial focuses on the Nuxt backend and Gemini File Search API, you can find the full frontend code in the GitHub repo. Bonus) Add documents management API endpoints The Gemini File Search API also allows you to list and delete documents from a store. While not strictly necessary for our simple app, it's a good way to showcase the full capabilities of the API. And, of course, you'll likely need to list documents or remove a document from a store at some point in your own apps! Let's add those endpoints to the backend. 1) Add document listing and delete helpers Extend server\u002Futils\u002Fgemini-file-search.ts with: \u002F\u002F server\u002Futils\u002Fgemini-file-search.ts export async function listStoreDocuments(params: { fileSearchStoreName: string; }) { const ai = getClient(); const result: Array&lt;{ name: string; displayName: string }&gt; = []; const documents = await ai.fileSearchStores.documents.list({ parent: params.fileSearchStoreName, }); for await (const document of documents as any) { result.push({ name: document.name ?? &quot;&quot;, displayName: document.displayName ?? document.name ?? &quot;Untitled document&quot;, }); } return result; } export async function deleteStoreDocument(params: { documentName: string }) { const ai = getClient(); await ai.fileSearchStores.documents.delete({ name: params.documentName, config: { force: true }, }); } 2) Add API endpoints Create server\u002Fapi\u002Frag\u002Fdocuments.get.ts: \u002F\u002F server\u002Fapi\u002Frag\u002Fdocuments.get.ts export default defineEventHandler(async (event) =&gt; { const query = getQuery(event); const fileSearchStoreName = String(query.storeName || &quot;&quot;).trim(); if (!fileSearchStoreName) { throw createError({ statusCode: 400, statusMessage: &#039;Query string needs a non-empty &quot;storeName&quot; value.&#039;, }); } const documents = await listStoreDocuments({ fileSearchStoreName }); return { fileSearchStoreName, documents }; }); Create server\u002Fapi\u002Frag\u002Fdocuments.delete.ts: \u002F\u002F server\u002Fapi\u002Frag\u002Fdocuments.delete.ts export default defineEventHandler(async (event) =&gt; { const body = await readBody&lt;{ documentName?: string }&gt;(event); const documentName = body.documentName?.trim(); if (!documentName) throw createError({ statusCode: 400, statusMessage: &quot;Missing documentName&quot;, }); await deleteStoreDocument({ documentName }); return { ok: true }; }); And that's it! You now have a backend API for managing your File Search stores, indexing documents, and asking questions. What we've built: Here are the API endpoints provided in this tutorial: 1. List Documents in a File Search Store Endpoint: GET \u002Fapi\u002Frag\u002Fdocuments?storeName=fileSearchStores\u002Fyour-store-name Returns the list of documents stored in the given File Search store. Example Request: curl &quot;http:\u002F\u002Flocalhost:4310\u002Fapi\u002Frag\u002Fdocuments?storeName=fileSearchStores\u002Fyour-store-name&quot; Example Response: { &quot;fileSearchStoreName&quot;: &quot;fileSearchStores\u002Fyour-store-name&quot;, &quot;documents&quot;: [ { &quot;name&quot;: &quot;fileSearchStores\u002Fyour-store-name\u002Fdocuments\u002Fdoc-1&quot;, &quot;displayName&quot;: &quot;My Document&quot; } ] } 2. Delete a Document from a Store Endpoint: DELETE \u002Fapi\u002Frag\u002Fdocuments Send a JSON body with the document's name to delete it from the store. Example Request: curl -X DELETE &quot;http:\u002F\u002Flocalhost:4310\u002Fapi\u002Frag\u002Fdocuments&quot; \\ -H &quot;content-type: application\u002Fjson&quot; \\ -d &#039;{&quot;documentName&quot;:&quot;fileSearchStores\u002Fyour-store-name\u002Fdocuments\u002Fdoc-1&quot;}&#039; Example Response: { &quot;ok&quot;: true } 3. Create a File Search Store Endpoint: POST \u002Fapi\u002Frag\u002Fstore Creates a new File Search store that will hold your indexed documents. Example Request: curl -s -X POST &quot;http:\u002F\u002Flocalhost:4310\u002Fapi\u002Frag\u002Fstore&quot; \\ -H &quot;content-type: application\u002Fjson&quot; \\ -d &#039;{&quot;displayName&quot;:&quot;smoke-test-store&quot;}&#039; Example Response: { &quot;fileSearchStoreName&quot;: &quot;fileSearchStores\u002Fabc123&quot; } 4. Upload and Index a Document Endpoint: POST \u002Fapi\u002Frag Uploads text content (as plain text) and indexes it into the specified File Search store. Example Request: curl -s -X POST &quot;http:\u002F\u002Flocalhost:4310\u002Fapi\u002Frag&quot; \\ -H &quot;content-type: application\u002Fjson&quot; \\ -d &#039;{&quot;storeName&quot;:&quot;fileSearchStores\u002Fyour-store-name&quot;,&quot;content&quot;:&quot;RAG means Retrieval-Augmented Generation. It retrieves relevant context from your private docs before generation.&quot;,&quot;displayName&quot;:&quot;smoke-test-notes&quot;}&#039; Example Response: { &quot;ok&quot;: true, &quot;accepted&quot;: true, &quot;jobId&quot;: &quot;3f2d41a9-1f2d-43c2-9e21-7f4ce0d3a9b6&quot;, &quot;jobStatus&quot;: &quot;pending&quot;, &quot;fileSearchStoreName&quot;: &quot;fileSearchStores\u002Fyour-store-name&quot; } 5. Poll for Indexing Status Endpoint: GET \u002Fapi\u002Frag\u002Findex-status?jobId=... Checks the status of a long-running indexing operation. Example Request: curl -s &quot;http:\u002F\u002Flocalhost:4310\u002Fapi\u002Frag\u002Findex-status?jobId=3f2d41a9-1f2d-43c2-9e21-7f4ce0d3a9b6&quot; Example Response: { &quot;job&quot;: { &quot;id&quot;: &quot;3f2d41a9-1f2d-43c2-9e21-7f4ce0d3a9b6&quot;, &quot;status&quot;: &quot;succeeded&quot;, &quot;fileSearchStoreName&quot;: &quot;fileSearchStores\u002Fyour-store-name&quot;, &quot;displayName&quot;: &quot;smoke-test-notes&quot; } } 6. Ask Questions Grounded In Your Indexed Docs Endpoint: POST \u002Fapi\u002Frag\u002Fask Sends a natural language question to the backend and gets a grounded answer based on your previously indexed documents. Example Request: curl -s -X POST &quot;http:\u002F\u002Flocalhost:4310\u002Fapi\u002Frag\u002Fask&quot; \\ -H &quot;content-type: application\u002Fjson&quot; \\ -d &#039;{&quot;storeName&quot;:&quot;fileSearchStores\u002Fyour-store-name&quot;,&quot;question&quot;:&quot;What does RAG mean?&quot;}&#039; Example Response: { &quot;answer&quot;: &quot;RAG means Retrieval-Augmented Generation. It retrieves relevant context from your private docs before generation.&quot;, &quot;attributions&quot;: [ { &quot;title&quot;: &quot;smoke-test-notes&quot;, &quot;text&quot;: &quot;RAG means Retrieval-Augmented Generation. It retrieves relevant context from your private docs before generation.&quot;, &quot;fileSearchStore&quot;: &quot;fileSearchStores\u002Fyour-store-name&quot; } ], &quot;groundingMetadata&quot;: { \u002F* ... *\u002F }, &quot;fileSearchStoreName&quot;: &quot;fileSearchStores\u002Fyour-store-name&quot; } Wrapping up If you enjoyed this article you might also be interested in the comprehensive RAG course over on aidd.io. It goes deep into detail about how RAG works and shows you step by step how to build every part of the process from scratch: from gathering documents, to chunking them, to generating embeddings, to indexing them, and more! You'll also probably want to check out the official docs for Gemini File Search.","2026-04-08T06:47:51.245Z","019d6bd8-ea2a-734b-9c29-2aef0720a232","https:\u002F\u002Fblog.vueschool.io\u002Fwp-content\u002Fuploads\u002F2026\u002F03\u002Fdemo-screenshot.jpg","2026-03-25T23:00:54.000Z","rag-with-nuxt-and-gemini-file-search","This tutorial guides you through building a Nuxt backend that implements Retrieval-Augmented Generation (RAG) using Google's Gemini File Search. You'll learn to create server-side utility functions, manage document indexing, and develop a simple UI for querying indexed documents and displaying answers. The article provides a comprehensive overview of the RAG pipeline along with practical implementation steps.","RAG with Nuxt and Gemini File Search","2026-04-08T06:47:55.039Z","https:\u002F\u002Fblog.vueschool.io\u002Fvuejs-tutorials\u002Frag-with-nuxt-and-gemini-file-search\u002F?friend=MOKKAPPS","cd9445a0fbd730d2ddf9b7b249798a6f5ccabf7741514acd63444f85fdfa6076",[231,232,235],{"color":6,"id":32,"name":33,"slug":33},{"color":6,"id":233,"name":234,"slug":234},"019d6bd8-fb46-77ef-a3b6-bd2d30ab8919","api",{"color":6,"id":7,"name":8,"slug":8},1,20,{"tags":239},[240,245,249,252,256,258,261,265,269,273,277,281,284,288,292,296,300,304,308,312,316,320,324,328,332,336,338,342,344,348,352,356,360,364,368,372,376,380,384,388,392,396,400,404,408,412,416,420,424,428,432,436,438,442,446,448,452,456,460,464,468,472,475,479,483,487,491,493,497,500,504,508,512,516,520,524,526,530,534,538,540,544,548,552,556,560,564,568,571,575,577,581,585,589,593,597,601,605,607,611,613,617,619,623,627,631,635,639,642,646,650,655],{"articleCount":241,"color":6,"createdAt":242,"id":243,"name":244,"slug":244,"updatedAt":242},0,"2026-04-30T04:19:02.605Z","019ddc9c-954c-7703-8288-76d562fec577","accelerator",{"articleCount":236,"color":6,"createdAt":246,"id":247,"name":248,"slug":248,"updatedAt":246},"2026-04-08T06:47:43.120Z","019d6bd8-caef-76b9-bacd-363e31e6d2a9","accessibility",{"articleCount":250,"color":6,"createdAt":251,"id":215,"name":216,"slug":216,"updatedAt":251},8,"2026-04-17T20:27:50.780Z",{"articleCount":241,"color":6,"createdAt":253,"id":254,"name":255,"slug":255,"updatedAt":253},"2026-05-05T00:00:19.031Z","019df56f-8257-752e-a918-cdbb07c32b86","ai-agents",{"articleCount":236,"color":6,"createdAt":257,"id":57,"name":58,"slug":58,"updatedAt":257},"2026-06-01T12:00:14.713Z",{"articleCount":259,"color":6,"createdAt":260,"id":233,"name":234,"slug":234,"updatedAt":260},4,"2026-04-08T06:47:55.499Z",{"articleCount":250,"color":6,"createdAt":262,"id":263,"name":264,"slug":264,"updatedAt":262},"2026-04-17T19:35:19.300Z","019d9cf0-ca04-75bb-ad86-ce8da1c0be23","architecture",{"articleCount":241,"color":6,"createdAt":266,"id":267,"name":268,"slug":268,"updatedAt":266},"2026-04-23T12:00:11.615Z","019dba36-435e-765d-bfb2-c5be05362c27","astro",{"articleCount":241,"color":6,"createdAt":270,"id":271,"name":272,"slug":272,"updatedAt":270},"2026-06-01T20:00:15.958Z","019e84c5-cc55-7377-9e5d-77240ea8a880","authentication",{"articleCount":241,"color":6,"createdAt":274,"id":275,"name":276,"slug":276,"updatedAt":274},"2026-05-05T00:00:19.020Z","019df56f-824c-7031-9e34-2f47ad139eb6","automation",{"articleCount":241,"color":6,"createdAt":278,"id":279,"name":280,"slug":280,"updatedAt":278},"2026-06-11T16:00:12.019Z","019eb769-9af2-7471-b482-3f1a404f802e","azure",{"articleCount":282,"color":6,"createdAt":283,"id":212,"name":213,"slug":213,"updatedAt":283},2,"2026-04-17T20:27:50.776Z",{"articleCount":259,"color":6,"createdAt":285,"id":286,"name":287,"slug":287,"updatedAt":285},"2026-04-08T06:47:56.976Z","019d6bd9-010e-772d-b2f0-9378a042a676","best-practices",{"articleCount":241,"color":6,"createdAt":289,"id":290,"name":291,"slug":291,"updatedAt":289},"2026-06-06T00:00:11.711Z","019e9a3a-e5be-74b3-a01a-92c1f9427a2c","beta",{"articleCount":241,"color":6,"createdAt":293,"id":294,"name":295,"slug":295,"updatedAt":293},"2026-06-10T13:53:29.711Z","019eb1cf-3e6e-70f0-a761-0fb9b61d5675","budgeting",{"articleCount":236,"color":6,"createdAt":297,"id":298,"name":299,"slug":299,"updatedAt":297},"2026-06-16T16:11:31.438Z","019ed133-c4ee-70bb-8e21-7dc86e8adee4","bun",{"articleCount":241,"color":6,"createdAt":301,"id":302,"name":303,"slug":303,"updatedAt":301},"2026-05-19T20:00:14.559Z","019e41d3-1ade-77b0-9e4f-e9b97a6361ca","cdn",{"articleCount":236,"color":6,"createdAt":305,"id":306,"name":307,"slug":307,"updatedAt":305},"2026-06-27T16:00:12.237Z","019f09cf-5bcd-751c-8d46-9e123bd784af","clean-code",{"articleCount":236,"color":6,"createdAt":309,"id":310,"name":311,"slug":311,"updatedAt":309},"2026-05-16T18:48:40.777Z","019e321e-8248-75cc-9b69-59c2db6dd846","cli",{"articleCount":241,"color":6,"createdAt":313,"id":314,"name":315,"slug":315,"updatedAt":313},"2026-05-05T00:00:19.014Z","019df56f-8246-747f-be4b-a716863a93ea","cloud-platform",{"articleCount":236,"color":6,"createdAt":317,"id":318,"name":319,"slug":319,"updatedAt":317},"2026-06-16T16:11:31.289Z","019ed133-c458-74d8-a5c9-654f77837e7c","cloudflare",{"articleCount":236,"color":6,"createdAt":321,"id":322,"name":323,"slug":323,"updatedAt":321},"2026-07-27T18:11:45.304Z","019fa4c6-93c3-76ea-baa3-7d5fe541ac5b","cms",{"articleCount":236,"color":6,"createdAt":325,"id":326,"name":327,"slug":327,"updatedAt":325},"2026-04-30T04:19:02.045Z","019ddc9c-931c-743d-a1cb-e4449630fe47","collaboration",{"articleCount":236,"color":6,"createdAt":329,"id":330,"name":331,"slug":331,"updatedAt":329},"2026-06-10T13:53:29.567Z","019eb1cf-3dde-776d-9398-4863764f9ac3","community",{"articleCount":236,"color":6,"createdAt":333,"id":334,"name":335,"slug":335,"updatedAt":333},"2026-05-06T16:00:17.128Z","019dfe04-bee8-7384-bc58-a712f677807c","comparison",{"articleCount":236,"color":6,"createdAt":337,"id":108,"name":109,"slug":109,"updatedAt":337},"2026-05-14T12:00:21.354Z",{"articleCount":236,"color":6,"createdAt":339,"id":340,"name":341,"slug":341,"updatedAt":339},"2026-07-18T20:00:18.730Z","019f76d0-bb29-72d6-8d0b-05ffd1d3c8ed","composables",{"articleCount":259,"color":6,"createdAt":343,"id":186,"name":187,"slug":187,"updatedAt":343},"2026-04-08T06:47:42.915Z",{"articleCount":236,"color":6,"createdAt":345,"id":346,"name":347,"slug":347,"updatedAt":345},"2026-06-27T12:00:13.113Z","019f08f3-a538-74ed-82c5-038edce7100a","copilot",{"articleCount":241,"color":6,"createdAt":349,"id":350,"name":351,"slug":351,"updatedAt":349},"2026-04-25T12:00:12.674Z","019dc482-ff81-73ac-9b1c-6ad47f0afde0","data-management",{"articleCount":241,"color":6,"createdAt":353,"id":354,"name":355,"slug":355,"updatedAt":353},"2026-06-04T20:00:17.367Z","019e9438-e5d7-731f-bf47-8dcd86c6655c","data-privacy",{"articleCount":236,"color":6,"createdAt":357,"id":358,"name":359,"slug":359,"updatedAt":357},"2026-05-05T12:00:17.083Z","019df802-a8bb-775a-b843-93d883c7dfc5","data-science",{"articleCount":241,"color":6,"createdAt":361,"id":362,"name":363,"slug":363,"updatedAt":361},"2026-06-11T16:00:12.028Z","019eb769-9afb-7709-8a67-2a12f5e557c7","deepseek",{"articleCount":236,"color":6,"createdAt":365,"id":366,"name":367,"slug":367,"updatedAt":365},"2026-05-25T08:00:12.834Z","019e5e26-0e21-7366-87c7-0b1334180b0a","dependency-cruiser",{"articleCount":236,"color":6,"createdAt":369,"id":370,"name":371,"slug":371,"updatedAt":369},"2026-04-18T04:30:00.710Z","019d9eda-5005-7329-a463-189572e25635","deployment",{"articleCount":282,"color":6,"createdAt":373,"id":374,"name":375,"slug":375,"updatedAt":373},"2026-04-21T12:00:11.373Z","019dafe9-8a6d-7218-81f1-37052cbe9b78","development",{"articleCount":236,"color":6,"createdAt":377,"id":378,"name":379,"slug":379,"updatedAt":377},"2026-06-29T08:00:12.101Z","019f1264-9f44-762d-b5fd-837839fdc586","devtools",{"articleCount":241,"color":6,"createdAt":381,"id":382,"name":383,"slug":383,"updatedAt":381},"2026-05-29T20:00:13.197Z","019e7552-ad8c-731f-ad8b-49253ed0e1b9","docker",{"articleCount":236,"color":6,"createdAt":385,"id":386,"name":387,"slug":387,"updatedAt":385},"2026-06-27T12:00:13.081Z","019f08f3-a518-7607-aa8c-782b4f10c2ed","documentation",{"articleCount":236,"color":6,"createdAt":389,"id":390,"name":391,"slug":391,"updatedAt":389},"2026-04-30T04:19:02.055Z","019ddc9c-9327-744f-a2da-31b64c7b6ba4","editor",{"articleCount":236,"color":6,"createdAt":393,"id":394,"name":395,"slug":395,"updatedAt":393},"2026-07-06T12:00:24.261Z","019f374d-0cc4-71ff-b906-ebaec8b0c343","eslint",{"articleCount":241,"color":6,"createdAt":397,"id":398,"name":399,"slug":399,"updatedAt":397},"2026-05-02T00:00:23.123Z","019de5fc-7e52-740a-807c-d322c373865b","firewall",{"articleCount":236,"color":6,"createdAt":401,"id":402,"name":403,"slug":403,"updatedAt":401},"2026-05-06T16:00:17.134Z","019dfe04-beee-7481-b8e7-4034640e840a","frameworks",{"articleCount":241,"color":6,"createdAt":405,"id":406,"name":407,"slug":407,"updatedAt":405},"2026-04-08T06:47:56.883Z","019d6bd9-00b2-7199-8e88-2530ef258032","generics",{"articleCount":236,"color":6,"createdAt":409,"id":410,"name":411,"slug":411,"updatedAt":409},"2026-07-27T18:11:45.390Z","019fa4c6-9419-7657-844e-58ec868ed664","git",{"articleCount":282,"color":6,"createdAt":413,"id":414,"name":415,"slug":415,"updatedAt":413},"2026-07-15T00:00:22.207Z","019f6313-12bf-7151-81f2-c8b43b09d979","html",{"articleCount":241,"color":6,"createdAt":417,"id":418,"name":419,"slug":419,"updatedAt":417},"2026-05-06T00:00:17.012Z","019dfa95-d673-71ef-be4a-8761512ffe5c","infrastructure",{"articleCount":236,"color":6,"createdAt":421,"id":422,"name":423,"slug":423,"updatedAt":421},"2026-06-16T16:11:31.264Z","019ed133-c43f-704f-8c1a-fbc299c8a3e5","javascript",{"articleCount":236,"color":6,"createdAt":425,"id":426,"name":427,"slug":427,"updatedAt":425},"2026-07-13T08:00:18.266Z","019f5a7d-bf5a-76e0-903a-c87435cc3e09","lazy-loading",{"articleCount":236,"color":6,"createdAt":429,"id":430,"name":431,"slug":431,"updatedAt":429},"2026-05-11T12:00:19.963Z","019e16e8-dbfa-76d6-bb2d-793aee049186","loading",{"articleCount":241,"color":6,"createdAt":433,"id":434,"name":435,"slug":435,"updatedAt":433},"2026-04-25T12:00:12.682Z","019dc482-ff8a-7698-8af5-95db54a26d6b","local-first",{"articleCount":236,"color":6,"createdAt":437,"id":114,"name":115,"slug":115,"updatedAt":437},"2026-05-14T12:00:21.370Z",{"articleCount":236,"color":6,"createdAt":439,"id":440,"name":441,"slug":441,"updatedAt":439},"2026-05-10T16:00:18.613Z","019e129e-34b4-7107-acfb-27f6b8604eb0","management",{"articleCount":236,"color":6,"createdAt":443,"id":444,"name":445,"slug":445,"updatedAt":443},"2026-06-27T12:00:13.065Z","019f08f3-a508-7334-aa03-12b281698ea8","markdown",{"articleCount":236,"color":6,"createdAt":447,"id":35,"name":36,"slug":36,"updatedAt":447},"2026-07-28T12:00:27.878Z",{"articleCount":236,"color":6,"createdAt":449,"id":450,"name":451,"slug":451,"updatedAt":449},"2026-06-17T12:00:12.439Z","019ed574-0a96-7485-9c90-522e4be3e092","meta-tags",{"articleCount":241,"color":6,"createdAt":453,"id":454,"name":455,"slug":455,"updatedAt":453},"2026-05-26T08:00:14.598Z","019e634c-7105-7073-bc86-c32fa0a4401b","microfrontends",{"articleCount":241,"color":6,"createdAt":457,"id":458,"name":459,"slug":459,"updatedAt":457},"2026-05-19T16:00:13.516Z","019e40f7-5ccc-729c-92ba-bcc0aad8a16f","microvm",{"articleCount":241,"color":6,"createdAt":461,"id":462,"name":463,"slug":463,"updatedAt":461},"2026-05-05T00:00:19.025Z","019df56f-8250-768c-a659-b9f524ff902c","multi-tenant",{"articleCount":241,"color":6,"createdAt":465,"id":466,"name":467,"slug":467,"updatedAt":465},"2026-05-08T04:00:17.998Z","019e05be-4c4d-759e-a51e-8ac760f82f6d","nextjs",{"articleCount":236,"color":6,"createdAt":469,"id":470,"name":471,"slug":471,"updatedAt":469},"2026-04-17T19:35:19.293Z","019d9cf0-c9fc-76a0-8c4e-b818a89c3774","nitro",{"articleCount":473,"color":6,"createdAt":474,"id":32,"name":33,"slug":33,"updatedAt":474},27,"2026-04-08T06:47:55.381Z",{"articleCount":236,"color":6,"createdAt":476,"id":477,"name":478,"slug":478,"updatedAt":476},"2026-04-30T04:19:02.038Z","019ddc9c-9315-735e-a7e8-8424790e8859","nuxt-ui",{"articleCount":236,"color":6,"createdAt":480,"id":481,"name":482,"slug":482,"updatedAt":480},"2026-06-08T12:00:16.030Z","019ea71a-dc9d-7607-9cfa-df0f31ab5876","observability",{"articleCount":241,"color":6,"createdAt":484,"id":485,"name":486,"slug":486,"updatedAt":484},"2026-05-04T20:00:20.503Z","019df493-ce17-76ed-bd80-2a3914e0f409","open-source",{"articleCount":236,"color":6,"createdAt":488,"id":489,"name":490,"slug":490,"updatedAt":488},"2026-06-08T12:00:16.022Z","019ea71a-dc95-72b8-a7e3-70f9e2ac3710","opentelemetry",{"articleCount":259,"color":6,"createdAt":492,"id":87,"name":88,"slug":88,"updatedAt":492},"2026-05-18T12:00:14.389Z",{"articleCount":236,"color":6,"createdAt":494,"id":495,"name":496,"slug":496,"updatedAt":494},"2026-05-28T20:00:13.016Z","019e702c-50d7-74eb-8849-8b1cebcf411e","orchestration",{"articleCount":498,"color":6,"createdAt":499,"id":81,"name":82,"slug":82,"updatedAt":499},16,"2026-04-08T06:47:42.920Z",{"articleCount":236,"color":6,"createdAt":501,"id":502,"name":503,"slug":503,"updatedAt":501},"2026-06-10T13:53:29.575Z","019eb1cf-3de7-7326-87d9-c55ad1c0fa39","personalization",{"articleCount":236,"color":6,"createdAt":505,"id":506,"name":507,"slug":507,"updatedAt":505},"2026-04-17T19:35:19.263Z","019d9cf0-c9de-70b2-9057-76d771c3379e","pinia",{"articleCount":241,"color":6,"createdAt":509,"id":510,"name":511,"slug":511,"updatedAt":509},"2026-05-02T00:00:23.112Z","019de5fc-7e47-7075-8aeb-9e90f7689f57","postgres",{"articleCount":241,"color":6,"createdAt":513,"id":514,"name":515,"slug":515,"updatedAt":513},"2026-05-19T20:00:14.575Z","019e41d3-1aee-70c8-a07c-cec870115d14","pricing",{"articleCount":236,"color":6,"createdAt":517,"id":518,"name":519,"slug":519,"updatedAt":517},"2026-05-10T16:00:18.603Z","019e129e-34aa-723b-a568-45737915c7bf","qa",{"articleCount":236,"color":6,"createdAt":521,"id":522,"name":523,"slug":523,"updatedAt":521},"2026-05-08T04:00:18.013Z","019e05be-4c5c-75ad-8df5-602b3db57983","react",{"articleCount":259,"color":6,"createdAt":525,"id":60,"name":61,"slug":61,"updatedAt":525},"2026-04-20T12:00:11.653Z",{"articleCount":259,"color":6,"createdAt":527,"id":528,"name":529,"slug":529,"updatedAt":527},"2026-04-17T20:27:50.585Z","019d9d20-dfb9-7041-9973-39c545b33a2d","release",{"articleCount":241,"color":6,"createdAt":531,"id":532,"name":533,"slug":533,"updatedAt":531},"2026-05-26T08:00:14.619Z","019e634c-711a-728f-9b63-20f2c8b37ccb","routing",{"articleCount":241,"color":6,"createdAt":535,"id":536,"name":537,"slug":537,"updatedAt":535},"2026-05-19T16:00:13.509Z","019e40f7-5cc4-72e1-8f8b-692dd29fc716","sandbox",{"articleCount":236,"color":6,"createdAt":539,"id":111,"name":112,"slug":112,"updatedAt":539},"2026-05-14T12:00:21.362Z",{"articleCount":241,"color":6,"createdAt":541,"id":542,"name":543,"slug":543,"updatedAt":541},"2026-05-04T20:00:20.521Z","019df493-ce28-75e9-93d5-13251407a27b","scanning",{"articleCount":259,"color":6,"createdAt":545,"id":546,"name":547,"slug":547,"updatedAt":545},"2026-04-27T08:00:12.420Z","019dcdf3-fc84-73aa-a0be-6b7196c5a2e9","security",{"articleCount":236,"color":6,"createdAt":549,"id":550,"name":551,"slug":551,"updatedAt":549},"2026-06-17T12:00:12.428Z","019ed574-0a8b-7566-976f-8c281c820ed0","seo",{"articleCount":236,"color":6,"createdAt":553,"id":554,"name":555,"slug":555,"updatedAt":553},"2026-06-17T12:00:12.433Z","019ed574-0a91-74d8-b806-56681bb4b477","sitemap",{"articleCount":236,"color":6,"createdAt":557,"id":558,"name":559,"slug":559,"updatedAt":557},"2026-07-18T16:00:27.576Z","019f75f5-23b8-72eb-a2bf-79dd1fed3863","software-development",{"articleCount":236,"color":6,"createdAt":561,"id":562,"name":563,"slug":563,"updatedAt":561},"2026-04-17T19:35:19.297Z","019d9cf0-ca00-749d-9101-1c63bf62e215","spas",{"articleCount":282,"color":6,"createdAt":565,"id":566,"name":567,"slug":567,"updatedAt":565},"2026-06-03T16:00:12.620Z","019e8e36-bd4b-740d-b23a-81858b24025b","ssg",{"articleCount":569,"color":6,"createdAt":570,"id":192,"name":193,"slug":193,"updatedAt":570},9,"2026-04-08T06:47:43.020Z",{"articleCount":241,"color":6,"createdAt":572,"id":573,"name":574,"slug":574,"updatedAt":572},"2026-04-30T04:19:02.613Z","019ddc9c-9555-7544-a3e3-0605d2c1ea82","startup",{"articleCount":259,"color":6,"createdAt":576,"id":189,"name":190,"slug":190,"updatedAt":576},"2026-04-18T12:00:12.027Z",{"articleCount":241,"color":6,"createdAt":578,"id":579,"name":580,"slug":580,"updatedAt":578},"2026-06-06T00:00:11.717Z","019e9a3a-e5c5-76d3-86e4-576c151a87b3","storage",{"articleCount":236,"color":6,"createdAt":582,"id":583,"name":584,"slug":584,"updatedAt":582},"2026-06-17T12:00:12.446Z","019ed574-0a9e-7712-8bc5-a0102787fe48","structured-data",{"articleCount":236,"color":6,"createdAt":586,"id":587,"name":588,"slug":588,"updatedAt":586},"2026-05-10T16:00:18.597Z","019e129e-34a4-771a-844d-09a7edefcd64","tdd",{"articleCount":241,"color":6,"createdAt":590,"id":591,"name":592,"slug":592,"updatedAt":590},"2026-06-04T20:00:17.351Z","019e9438-e5c6-7681-8704-897c7b499eb4","terms-of-service",{"articleCount":282,"color":6,"createdAt":594,"id":595,"name":596,"slug":596,"updatedAt":594},"2026-04-09T06:11:45.799Z","019d70de-3c07-76bf-9688-9619e1b0d427","testing",{"articleCount":236,"color":6,"createdAt":598,"id":599,"name":600,"slug":600,"updatedAt":598},"2026-06-16T16:11:31.088Z","019ed133-c390-75bf-8f1a-5c57cd221f14","threejs",{"articleCount":241,"color":6,"createdAt":602,"id":603,"name":604,"slug":604,"updatedAt":602},"2026-05-02T00:00:23.130Z","019de5fc-7e59-7426-8d46-e79e4bcf0d56","tls",{"articleCount":5,"color":6,"createdAt":606,"id":7,"name":8,"slug":8,"updatedAt":606},"2026-04-08T06:47:55.591Z",{"articleCount":259,"color":6,"createdAt":608,"id":609,"name":610,"slug":610,"updatedAt":608},"2026-04-08T06:47:56.778Z","019d6bd9-0049-72ba-8cfa-139b1c1b249d","typescript",{"articleCount":612,"color":6,"createdAt":570,"id":63,"name":64,"slug":64,"updatedAt":570},5,{"articleCount":236,"color":6,"createdAt":614,"id":615,"name":616,"slug":616,"updatedAt":614},"2026-05-11T12:00:19.969Z","019e16e8-dc00-714d-911a-a5bf39238587","user-experience",{"articleCount":236,"color":6,"createdAt":618,"id":84,"name":85,"slug":85,"updatedAt":618},"2026-05-18T12:00:14.379Z",{"articleCount":241,"color":6,"createdAt":620,"id":621,"name":622,"slug":622,"updatedAt":620},"2026-04-30T04:19:02.228Z","019ddc9c-93d3-763e-ad3c-d3ad78642de7","vercel",{"articleCount":236,"color":6,"createdAt":624,"id":625,"name":626,"slug":626,"updatedAt":624},"2026-07-13T08:00:18.274Z","019f5a7d-bf61-746b-a44b-5c0a16ff57d3","video",{"articleCount":259,"color":6,"createdAt":628,"id":629,"name":630,"slug":630,"updatedAt":628},"2026-04-17T19:35:19.289Z","019d9cf0-c9f8-7411-beed-02265d8271db","vite",{"articleCount":236,"color":6,"createdAt":632,"id":633,"name":634,"slug":634,"updatedAt":632},"2026-04-25T18:08:02.132Z","019dc5d3-c054-70e2-bca4-08e2a2b9a096","vitest",{"articleCount":236,"color":6,"createdAt":636,"id":637,"name":638,"slug":638,"updatedAt":636},"2026-06-27T12:00:13.107Z","019f08f3-a532-7049-a02c-c17a4fd99306","vscode",{"articleCount":640,"color":6,"createdAt":641,"id":54,"name":55,"slug":55,"updatedAt":641},32,"2026-04-08T06:47:42.793Z",{"articleCount":236,"color":6,"createdAt":643,"id":644,"name":645,"slug":645,"updatedAt":643},"2026-04-18T12:00:12.007Z","019da076-78e6-76bd-b0d4-4e0597d36378","vue-router",{"articleCount":241,"color":6,"createdAt":647,"id":648,"name":649,"slug":649,"updatedAt":647},"2026-05-04T20:00:20.510Z","019df493-ce1d-7039-811a-ed57bf081d26","vulnerability",{"articleCount":651,"color":6,"createdAt":652,"id":653,"name":654,"slug":654,"updatedAt":652},3,"2026-05-05T12:00:17.078Z","019df802-a8b6-74d2-a0cd-b0509cf502fa","web-development",{"articleCount":651,"color":6,"createdAt":656,"id":657,"name":658,"slug":658,"updatedAt":656},"2026-05-10T16:00:18.576Z","019e129e-3490-7739-a218-5454a8c15d6e","workflow"]