Fetch only once on initial page load

I have an endpoint from my backend, with important data like seo, settings etc. I fetch it server side but I notice on navigation it refetch on client side, is there a way to prevent this? I want to fetch it only on first load.
11 Replies
kapa.ai
kapa.ai2w ago
Hi @salomon_.. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Yes, there are several ways to prevent refetching data on client-side navigation in Nuxt. Let me explain your options: Using callOnce The callOnce utility (available since Nuxt v3.9) is designed specifically for your use case - executing code only once during the initial render:
const websiteConfig = useState('config')

await callOnce(async () => {
websiteConfig.value = await $fetch('<https://my-backend.com/api/settings'>)
})
const websiteConfig = useState('config')

await callOnce(async () => {
websiteConfig.value = await $fetch('<https://my-backend.com/api/settings'>)
})
This will only run once during server-side rendering and won't run again during hydration or client-side navigation Call Once. Using useFetch with a key When using useFetch, you can control caching behavior by providing a specific key:
const { data: settings } = await useFetch('/api/settings', {
key: 'global-settings'
})
const { data: settings } = await useFetch('/api/settings', {
key: 'global-settings'
})
The data will be cached with this key, and subsequent navigations will use the cached data instead of refetching Docs > Getting Started > Data Fetching. Using a plugin with useState Another approach is to fetch the data in a plugin and store it in a global state:
// plugins/settings.js
export default defineNuxtPlugin(async () => {
const settings = useState('settings', () => null)

if (!settings.value) {
const { data } = await useFetch('/api/settings')
settings.value = data.value
}
})
// plugins/settings.js
export default defineNuxtPlugin(async () => {
const settings = useState('settings', () => null)

if (!settings.value) {
const { data } = await useFetch('/api/settings')
settings.value = data.value
}
})
Then you can access this state anywhere in your application without refetching. Preventing cache invalidation If you're still experiencing refetching, make sure you're not inadvertently invalidating the cache. Some users have reported issues with useFetch caching that were resolved by: 1. Ensuring consistent key usage 2. Not using dynamic values in the key that change on navigation 3. Avoiding the refresh function unless explicitly needed If you're still having issues, you might need to check if something in your code is calling refreshNuxtData or clearNuxtData which would invalidate the cache Stack Overflow Post. Would you like me to elaborate on any of these approaches? ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@1029431292170874910> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
salomon_.
salomon_.OP2w ago
@kapa.ai but I already use key 'global-settings' and it still refetch it on the client side on navigation
import type { Settings } from './types';

export const getSettings = () => {
// We call this api method in app.vue which is not re-render on locale switch
// so we manually watch changing this cookie to trigger re-fetch with new locale
const locale = useCookie('i18n_redirected');
return useAsyncData<{ data: { settings: Settings } }>(
'global-settings',
() => useNuxtApp().$apiFetch('/settings'),
{
watch: [locale],
lazy: true,
server: true,
},
);
};
import type { Settings } from './types';

export const getSettings = () => {
// We call this api method in app.vue which is not re-render on locale switch
// so we manually watch changing this cookie to trigger re-fetch with new locale
const locale = useCookie('i18n_redirected');
return useAsyncData<{ data: { settings: Settings } }>(
'global-settings',
() => useNuxtApp().$apiFetch('/settings'),
{
watch: [locale],
lazy: true,
server: true,
},
);
};
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I see the issue with your implementation. Let me explain why your data is being refetched on client-side navigation and how to fix it. The problem is likely related to the watch option in your useAsyncData call. When you watch the locale cookie, it will trigger a refetch whenever that value changes, including during client-side navigation. Here are a few approaches to solve this: 1. Use getCachedData option You can use the getCachedData option to control when data is fetched:
import type { Settings } from './types';

export const getSettings = () => {
const locale = useCookie('i18n_redirected');
const nuxtApp = useNuxtApp();

return useAsyncData<{ data: { settings: Settings } }>(
'global-settings',
() => nuxtApp.$apiFetch('/settings'),
{
watch: [locale],
lazy: true,
server: true,
getCachedData: (key) => {
// Only return null (triggering a fetch) when locale changes
const cachedData = nuxtApp.payload.data[key] || nuxtApp.static.data[key];
return cachedData;
}
},
);
};
import type { Settings } from './types';

export const getSettings = () => {
const locale = useCookie('i18n_redirected');
const nuxtApp = useNuxtApp();

return useAsyncData<{ data: { settings: Settings } }>(
'global-settings',
() => nuxtApp.$apiFetch('/settings'),
{
watch: [locale],
lazy: true,
server: true,
getCachedData: (key) => {
// Only return null (triggering a fetch) when locale changes
const cachedData = nuxtApp.payload.data[key] || nuxtApp.static.data[key];
return cachedData;
}
},
);
};
2. Use useLazyAsyncData with conditional refresh Another approach is to use useLazyAsyncData and only refresh when the locale changes:
import type { Settings } from './types';

export const getSettings = () => {
const locale = useCookie('i18n_redirected');
const previousLocale = ref(locale.value);

const result = useLazyAsyncData<{ data: { settings: Settings } }>(
'global-settings',
() => useNuxtApp().$apiFetch('/settings'),
{ server: true }
);

watch(locale, (newLocale) => {
if (newLocale !== previousLocale.value) {
previousLocale.value = newLocale;
result.refresh();
}
});

return result;
};
import type { Settings } from './types';

export const getSettings = () => {
const locale = useCookie('i18n_redirected');
const previousLocale = ref(locale.value);

const result = useLazyAsyncData<{ data: { settings: Settings } }>(
'global-settings',
() => useNuxtApp().$apiFetch('/settings'),
{ server: true }
);

watch(locale, (newLocale) => {
if (newLocale !== previousLocale.value) {
previousLocale.value = newLocale;
result.refresh();
}
});

return result;
};
3. Access cached data with useNuxtData You can also use useNuxtData to access the cached data without triggering a refetch:
// First fetch in a plugin or app.vue
const { data } = await useAsyncData(
'global-settings',
() => useNuxtApp().$apiFetch('/settings')
);

// Then in your components or pages
const { data: settings } = useNuxtData('global-settings');
// First fetch in a plugin or app.vue
const { data } = await useAsyncData(
'global-settings',
() => useNuxtApp().$apiFetch('/settings')
);

// Then in your components or pages
const { data: settings } = useNuxtData('global-settings');
This approach lets you access the cached data without refetching it Docs > Api > Composables. The key issue is that the watch option is causing refetches during navigation. By controlling when refreshes happen or by accessing cached data directly, you can prevent unnecessary refetches while still updating when the locale changes. Would you like me to explain any of these approaches in more detail? ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@1029431292170874910> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
salomon_.
salomon_.OP2w ago
@kapa.ai I need to fetch data for hero section on server side and refetch data on locale change but also cache data between pages
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
const locale = useCookie('i18n_redirected');
const nuxtApp = useNuxtApp();

return useAsyncData<{ data: HeroSlide[] }>(`hero-sliders`, () => nuxtApp.$apiFetch('/sliders'), {
watch: [locale],
// lazy: true,
server: true,
getCachedData: (key) => {
return nuxtApp.payload.data[key] || nuxtApp.static.data[key];
},
});
};
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
const locale = useCookie('i18n_redirected');
const nuxtApp = useNuxtApp();

return useAsyncData<{ data: HeroSlide[] }>(`hero-sliders`, () => nuxtApp.$apiFetch('/sliders'), {
watch: [locale],
// lazy: true,
server: true,
getCachedData: (key) => {
return nuxtApp.payload.data[key] || nuxtApp.static.data[key];
},
});
};
and on page where get data
const { data: heroSliders } = await getHeroSliders();
const { data: heroSliders } = await getHeroSliders();
but nuxt loading indicator always shows on locale change and data is not change for new locale
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I see the issue you're facing with your hero slider data fetching. Let's address both problems: refetching on locale change while maintaining caching between pages, and fixing the loading indicator issue. The main issue is that your current implementation doesn't properly handle locale changes. Let me explain how to fix it: The Problem 1. Your watch on the locale cookie is triggering refetches, but the data isn't updating for the new locale 2. The Nuxt loading indicator shows during locale changes, suggesting a navigation/refetch is happening Solution Here's an improved implementation:
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
const locale = useCookie('i18n_redirected');
const route = useRoute();
const localeQuery = computed(() => locale.value);

return useAsyncData<{ data: HeroSlide[] }>(
() => `hero-sliders-${locale.value}`, // Dynamic key based on locale
() => useNuxtApp().$apiFetch('/sliders'),
{
watch: [localeQuery], // Watch the computed property
server: true,
dedupe: 'cancel', // Cancel any pending requests when a new one is made
}
);
};
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
const locale = useCookie('i18n_redirected');
const route = useRoute();
const localeQuery = computed(() => locale.value);

return useAsyncData<{ data: HeroSlide[] }>(
() => `hero-sliders-${locale.value}`, // Dynamic key based on locale
() => useNuxtApp().$apiFetch('/sliders'),
{
watch: [localeQuery], // Watch the computed property
server: true,
dedupe: 'cancel', // Cancel any pending requests when a new one is made
}
);
};
Key changes: 1. Dynamic key with locale: By including the locale in the key (hero-sliders-${locale.value}), you ensure each locale has its own cached data 2. Using a computed property: This ensures proper reactivity when watching the locale 3. Using dedupe option: This helps manage concurrent requests during locale changes Alternative Approach If you still face issues, you can try this more explicit approach:
import type { HeroSlide } from './types';
export const getHeroSliders = () => {
const locale = useCookie('i18n_redirected');
const nuxtApp = useNuxtApp();

const { data, refresh } = useAsyncData<{ data: HeroSlide[] }>(
() => `hero-sliders-${locale.value}`,
() => nuxtApp.$apiFetch('/sliders'),
{ server: true }
);

// Explicitly watch locale changes and refresh data
watch(() => locale.value, () => {
refresh();
});

return { data, refresh };
};
import type { HeroSlide } from './types';
export const getHeroSliders = () => {
const locale = useCookie('i18n_redirected');
const nuxtApp = useNuxtApp();

const { data, refresh } = useAsyncData<{ data: HeroSlide[] }>(
() => `hero-sliders-${locale.value}`,
() => nuxtApp.$apiFetch('/sliders'),
{ server: true }
);

// Explicitly watch locale changes and refresh data
watch(() => locale.value, () => {
refresh();
});

return { data, refresh };
};
This approach gives you more control over when refreshes happen Docs > Getting-started > Data Fetching. Regarding the Loading Indicator The loading indicator appears because Nuxt is detecting a data fetching operation during navigation. To prevent this, you could: 1. Use lazy: true to avoid blocking navigation 2. Consider implementing a custom loading state for just the hero section instead of the global loading indicator If you need to keep the data fetching behavior but hide the loading indicator specifically during locale changes, you could conditionally disable the loading indicator when detecting a locale change. Let me know if you need further clarification on any of these approaches! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@1029431292170874910> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
salomon_.
salomon_.OP2w ago
@kapa.ai is it possible to avoid fetch on client side? import type { HeroSlide } from './types'; export const getHeroSliders = () => { return useAsyncData<{ data: HeroSlide[] }>( hero-sliders, () => useNuxtApp().$apiFetch('/sliders'), { lazy: true, server: true, }, ); };
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Yes, it's possible to avoid fetching on the client side while still getting the data from the server. Let me explain how to achieve this with your code. Your current implementation uses server: true, which is good, but there are a few issues that might be causing client-side fetches: 1. The lazy: true option means the fetch won't block navigation, but it doesn't prevent client-side fetching 2. You're not explicitly preventing client-side execution Here's how you can modify your code to ensure it only fetches on the server side:
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
return useAsyncData<{ data: HeroSlide[] }>(
`hero-sliders`,
() => useNuxtApp().$apiFetch('/sliders'),
{
server: true,
lazy: false, // Make it blocking to ensure server-side fetch completes
client: false // This explicitly prevents client-side fetching
},
);
};
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
return useAsyncData<{ data: HeroSlide[] }>(
`hero-sliders`,
() => useNuxtApp().$apiFetch('/sliders'),
{
server: true,
lazy: false, // Make it blocking to ensure server-side fetch completes
client: false // This explicitly prevents client-side fetching
},
);
};
The key addition here is the client: false option, which tells Nuxt to never execute this fetch on the client side Docs > Getting-started > Data Fetching. Alternatively, if you want to keep the non-blocking behavior of lazy: true, you can use this approach:
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
// Only execute on the server
if (process.server) {
return useAsyncData<{ data: HeroSlide[] }>(
`hero-sliders`,
() => useNuxtApp().$apiFetch('/sliders'),
{
lazy: true,
server: true
},
);
}

// On client, just return the cached data
return useNuxtData<{ data: HeroSlide[] }>(`hero-sliders`);
};
import type { HeroSlide } from './types';

export const getHeroSliders = () => {
// Only execute on the server
if (process.server) {
return useAsyncData<{ data: HeroSlide[] }>(
`hero-sliders`,
() => useNuxtApp().$apiFetch('/sliders'),
{
lazy: true,
server: true
},
);
}

// On client, just return the cached data
return useNuxtData<{ data: HeroSlide[] }>(`hero-sliders`);
};
This approach uses process.server to conditionally execute the fetch only on the server, and on the client it uses useNuxtData to access the cached data without triggering a new fetch Docs > Api > Composables > use-fetch. Remember that when using useAsyncData with proper caching, the data fetched on the server will be automatically transferred to the client in the payload, so you shouldn't need to fetch it again on the client side. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@1029431292170874910> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?