Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What Changes with Nuxt 4's Singleton Data Fetch...

What Changes with Nuxt 4's Singleton Data Fetching Layer

Avatar for Naoki Haba

Naoki Haba

October 25, 2025
Tweet

More Decks by Naoki Haba

Other Decks in Programming

Transcript

  1. What Changes with Nuxt 4's Singleton Data Fetching Layer 2025.10.25

    Vue Fes Japan 2025 View slides: naokihaba/talks
  2. 3 Months Since Nuxt 4 Release 🙋 Have you started

    using Nuxt 4 yet? Today, I’ll talk about the improved data fetching mechanism in Nuxt 4
  3. Before that, have you experienced this? ✗ Multiple requests fired

    even with the same key ✗ Need to manually write watch even when key changes ✗ Cannot precisely control when to use cache ✗ Unnecessary data remains, risking memory leaks ✅ Solved by Singleton Data Fetching Layer
  4. Let's see the behavior until Nuxt 3 ComponentA.vue ComponentB.vue Using

    the same key 'user' but… const { data } = useAsyncData('user', async () => { <script setup lang="ts"> console.log(' 🔵 ComponentA: Request started') const result = await $fetch('/api/users') return result }, { server: false }) </script> const { data } = useAsyncData('user', async () => { <script setup lang="ts"> console.log(' 🟢 ComponentB: Request started') const result = await $fetch('/api/users') return result }, { server: false }) </script> What do you think happens?
  5. Result: Requests executed twice 😱 Console Logs from both 🔵

    ComponentA and 🟢 ComponentB are output Network Two requests fired with the same key: 'user'
  6. Why executed twice? Issue: Each component copies asyncData and creates

    a new refresh → handler is executed separately in ComponentA and ComponentB Nuxt 3 Implementation const asyncData = { ...nuxtApp._asyncData[key] } // Copy the object asyncData.refresh = () => handler(nuxtApp) // Create new refresh function if (options.immediate) { initialFetch() // ← Fetch is executed for each component here // Processing in each component // Execute immediately if immediate: true (default) }
  7. Improvement 1: Share ref instance with the same key Before

    (Nuxt 3) After (Nuxt 4) users1 !== users2 // Separate refs // handler executed 2 times 😢 const { data: users1 } = useAsyncData( 'users', () => $fetch('/api/users') ) const { data: users2 } = useAsyncData( 'users', () => $fetch('/api/users') ) users1 === users2 // Same ref // handler executed only once ✨ const { data: users1 } = useAsyncData( 'users', () => $fetch('/api/users') ) const { data: users2 } = useAsyncData( 'users', () => $fetch('/api/users') )
  8. Implementation: Strict management with _init flag Key Points _init flag

    ensures one-time initialization Uses existing instance if already initialized → Same ref instance shared for the same key if (!nuxtApp._asyncData[key.value]?._init) { initialFetchOptions.cachedData = options.getCachedData(key.value, nuxtApp, ...); nuxtApp._asyncData[key.value] = createAsyncData(nuxtApp, key.value, _handler, options, ...); } const initialFetch = () => nuxtApp._asyncData[key.value].execute(initialFetchOptions);
  9. Additional Note Gradual implementation started from Nuxt 3.17.2 The Singleton

    Data Fetching Layer features weren’t all introduced at once Gradual implementation started from Nuxt 3.17.2 and reached completion in Nuxt 4 During this period, ref sharing, reactive key support, and automatic cleanup were added sequentially About Gradual Implementation
  10. Other Improvements 1. Extended getCachedData Control Called on all data

    fetches Not just initial, but also on watch and refresh Flexible cache strategy control 2. Reactive Key Support Can use computed or ref as key. Automatically refetch when reactive key changes getCachedData: (key, nuxtApp, ctx) => { // Determine caller with ctx.cause // 'initial' | 'watch' | 'refresh:manual' if (ctx.cause === 'initial') { return nuxtApp.payload.data[key] const { data } = useAsyncData('user', () => $fetch('/api/user'), { } return nuxtApp.static.data[key] } }) const userId = ref('1') const key = computed(() => `user-${userId.value}`) const { data } = useAsyncData(key, () => $fetch(`/api/users/${userId.value}`) userId.value = '2' // No need to write watch! // Automatically refetch when key changes ) // Automatically refetch when userId changes
  11. 3. Automatic Data Cleanup Data that's no longer used is

    automatically deleted asyncData._deps++ if (data?._deps) { data._deps-- // Cleanup if no one is referencing if (data._deps === 0) { data._off() // Automatically delete data } // Track with reference count // When component is unmounted const unregister = (key) => { const data = nuxtApp._asyncData[key] } } onScopeDispose(() => { unregister(key.value) })
  12. Summary ✅ Share ref instances with the same key ✅

    Reactive key support ✅ Extended getCachedData control ✅ Automatic data cleanup ✨ Performance improvement & Better memory management Nuxt 4 Singleton Data Fetching Layer