N
Nuxt3d ago
Single

Toggle useAsyncData's server option on SSG with SSR dynamically.

Hey! I am building a SSG with SSR. I use useAsyncData to fetch data from an api. After deploying my app to a webserver, i would like to use ?ssr=false query-string to (re)load the content from the api on the client site. Is this somehow possible to do that on demand?
12 Replies
kapa.ai
kapa.ai3d ago
Hi @Single. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Yes, you can dynamically toggle the server option of useAsyncData based on query parameters in your Nuxt application. Here's how you can implement this: Solution You can use the useRoute composable to access the query parameters and conditionally set the server option:
<script setup lang="ts">
const route = useRoute()
const shouldUseSSR = !route.query.ssr || route.query.ssr !== 'false'

const { data } = await useAsyncData(
'your-key',
() => $fetch('/your-api-endpoint'),
{
server: shouldUseSSR
}
)
</script>
<script setup lang="ts">
const route = useRoute()
const shouldUseSSR = !route.query.ssr || route.query.ssr !== 'false'

const { data } = await useAsyncData(
'your-key',
() => $fetch('/your-api-endpoint'),
{
server: shouldUseSSR
}
)
</script>
This approach allows you to control whether data fetching happens on the server or client side based on the URL query parameter. How it works 1. When a user visits your page with ?ssr=false in the URL, the data will only be fetched on the client side. 2. Without this parameter (or with any other value), the default SSR behavior will be used. Watching for query changes If you need to refetch data when the query parameter changes during navigation, you can use the watch option:
const route = useRoute()
const shouldUseSSR = computed(() => !route.query.ssr || route.query.ssr !== 'false')

const { data, refresh } = await useAsyncData(
'your-key',
() => $fetch('/your-api-endpoint'),
{
server: shouldUseSSR.value,
watch: [shouldUseSSR]
}
)
const route = useRoute()
const shouldUseSSR = computed(() => !route.query.ssr || route.query.ssr !== 'false')

const { data, refresh } = await useAsyncData(
'your-key',
() => $fetch('/your-api-endpoint'),
{
server: shouldUseSSR.value,
watch: [shouldUseSSR]
}
)
Alternatively, you can manually watch for query changes:
watch(() => route.query, () => refresh())
watch(() => route.query, () => refresh())
This approach is recommended in the Nuxt migration guide as a replacement for the watchQuery option from Nuxt 2 Migration > Component Options. Note that when using query parameters with SSG, you should be careful as they can cause issues during prerendering. You might need to exclude routes with query parameters from prerendering using the ignore option in your Nitro configuration:
export default defineNuxtConfig({
nitro: {
prerender: {
ignore: [/\?.*/], // Ignore all routes with query parameters
},
},
})
export default defineNuxtConfig({
nitro: {
prerender: {
ignore: [/\?.*/], // Ignore all routes with query parameters
},
},
})
This pattern is recommended for handling query parameters in SSG applications as mentioned in a GitHub issue. I hope this helps with your SSG with SSR implementation! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@176706136232296449> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Single
SingleOP3d ago
@kapa.ai #2 doesnt load the content on the client when setting ?ssr=false querystring
kapa.ai
kapa.ai3d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you're having an issue where the content isn't loading on the client side when using the ?ssr=false query parameter. Let's explore some possible solutions based on how Nuxt handles SSR toggling. Possible Solutions 1. Use Nuxt's built-in respectNoSSRHeader feature Nuxt has an experimental feature that allows disabling SSR responses using headers:
export default defineNuxtConfig({
experimental: {
respectNoSSRHeader: true
}
})
export default defineNuxtConfig({
experimental: {
respectNoSSRHeader: true
}
})
With this enabled, you can set the x-nuxt-no-ssr header to disable SSR for specific requests Docs > Api > Nuxt Configuration > experimental. 2. Ensure proper handling of client-side data fetching When using server: false with useAsyncData, the data won't be available until after hydration completes. This might explain why you're not seeing the content load:
// Make sure to handle the pending state
const { data, status } = useAsyncData('key', () => $fetch('/api/data'), {
server: route.query.ssr !== 'false',
lazy: true // Use lazy to avoid blocking navigation
})
// Make sure to handle the pending state
const { data, status } = useAsyncData('key', () => $fetch('/api/data'), {
server: route.query.ssr !== 'false',
lazy: true // Use lazy to avoid blocking navigation
})
Then in your template:
<template>
<div v-if="status === 'pending'">Loading...</div>
<div v-else-if="data">{{ data }}</div>
</template>
<template>
<div v-if="status === 'pending'">Loading...</div>
<div v-else-if="data">{{ data }}</div>
</template>
The lazy: true option is important here as it allows the page to render before the data is available Docs > Getting-started > Data Fetching > useFetch. 3. Use route rules for specific paths Another approach is to use Nuxt's route rules to disable SSR for specific routes:
export default defineNuxtConfig({
routeRules: {
'/your-path': { ssr: false }
}
})
export default defineNuxtConfig({
routeRules: {
'/your-path': { ssr: false }
}
})
This would completely disable SSR for that route Docs > Guide > Concepts > Rendering. 4. Check for hydration mismatches If you're experiencing hydration mismatches (content flashing or disappearing), this could be because the server and client are rendering different content. This is a common issue when toggling SSR dynamically Stack Overflow Post > Handling simple hydration mismatch after fetch in Nuxt 3 Part 1. Important Note When using useAsyncData with server: false, the data will not be available until after hydration completes, even if you await it. This is by design:
If you have not fetched data on the server (for example, with server: false), then the data will not be fetched until hydration completes. This means even if you await useAsyncData on the client side, data will remain null within <script setup>. Docs > Api > Composables
Let me know if you need more specific help with your implementation! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@176706136232296449> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Single
SingleOP3d ago
Thats not what i want. I want SSR with an option to load the data from the client in prod. @kapa.ai i am trying to do it like this but refresh seems not to load it from the remote but from the cache? <script lang="ts" setup> import { get } from '@vueuse/core' import { useRouteQuery } from '@vueuse/router' import { type News } from '~/types/directus/news' const { data: news, refresh } = useAsyncData<News[]>('news', async () => { return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter()) }) onMounted(() => { if (get(useRouteQuery('ssr')) === 'false') { refresh() } }) </script> <template> <div class="flex flex-col gap-y-24 justify-center items-center container"> <Welcome :title="$t('news.title')" :subTitle="$t('news.description')" /> <div v-if="news?.length" class="lg:w-4/5 sm:w-full grid sm:grid-cols-1 lg:grid-cols-3 gap-12"> <AppNews v-for="n in news" :key="n.id" :news="n" /> </div> </div> </template>
kapa.ai
kapa.ai3d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I see the issue with your implementation. When using refresh() with useAsyncData, it will by default use cached data if available rather than forcing a new fetch from the remote source. Solution: Force a refresh To force useAsyncData to ignore the cache and fetch fresh data from the API when the ?ssr=false query parameter is present, you need to use the force option with the refresh function:
onMounted(() => {
if (get(useRouteQuery('ssr')) === 'false') {
refresh({ force: true })
}
})
onMounted(() => {
if (get(useRouteQuery('ssr')) === 'false') {
refresh({ force: true })
}
})
The force option allows you to bypass the cache and force a new fetch from the API GitHub PR #25850. Alternative approach If you want more control over when server vs. client fetching happens, you could restructure your code to conditionally set the server option:
const ssr = useRouteQuery('ssr')
const shouldFetchOnServer = computed(() => ssr.value !== 'false')

const { data: news, refresh } = useAsyncData<News[]>(
'news',
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
},
{
server: shouldFetchOnServer.value
}
)
const ssr = useRouteQuery('ssr')
const shouldFetchOnServer = computed(() => ssr.value !== 'false')

const { data: news, refresh } = useAsyncData<News[]>(
'news',
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
},
{
server: shouldFetchOnServer.value
}
)
Additional considerations 1. Cache invalidation: If you need to globally invalidate cached data, you can use clearNuxtData() to clear all cached data or specific keys Docs > Api > Utils. 2. Unique key: If caching is still an issue, you could add a timestamp to the key to ensure uniqueness:
const { data: news } = useAsyncData<News[]>(
`news-${Date.now()}`,
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
}
)
const { data: news } = useAsyncData<News[]>(
`news-${Date.now()}`,
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
}
)
This approach is mentioned as a solution for similar caching issues Stack Overflow Post > Nuxt website shows old data when refreshing a page that uses useFetch. 3. Watch for query changes: If you want to automatically refresh when the query parameter changes, you can use the watch option:
const ssr = useRouteQuery('ssr')
const { data: news } = useAsyncData<News[]>(
'news',
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
},
{
watch: [ssr]
}
)
const ssr = useRouteQuery('ssr')
const { data: news } = useAsyncData<News[]>(
'news',
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
},
{
watch: [ssr]
}
)
Let me know if any of these approaches resolves your issue! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@176706136232296449> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Single
SingleOP3d ago
@kapa.ai refresh()options do not accept force
kapa.ai
kapa.ai3d ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I understand you're encountering an issue where the refresh() function doesn't accept the force option that I suggested in my previous answer. You're right, and I apologize for the confusion. The force option for refresh() is actually a newer feature that was proposed in a GitHub pull request (GitHub PR #25850) but may not be available in your current Nuxt version. Alternative solutions for refreshing data Here are some working alternatives: 1. Use refreshNuxtData to force a refresh
onMounted(() => {
if (get(useRouteQuery('ssr')) === 'false') {
refreshNuxtData('news')
}
})
onMounted(() => {
if (get(useRouteQuery('ssr')) === 'false') {
refreshNuxtData('news')
}
})
This will refetch the data for the specific key 'news' Docs > Api > Utils. 2. Add a unique key to prevent caching
const ssr = useRouteQuery('ssr')

onMounted(() => {
if (get(ssr) === 'false') {
const { data: freshNews } = useAsyncData<News[]>(
`news-${Date.now()}`, // Unique key
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
}
)
// Update your news data with the fresh data
news.value = freshNews.value
}
})
const ssr = useRouteQuery('ssr')

onMounted(() => {
if (get(ssr) === 'false') {
const { data: freshNews } = useAsyncData<News[]>(
`news-${Date.now()}`, // Unique key
async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
}
)
// Update your news data with the fresh data
news.value = freshNews.value
}
})
This approach uses a timestamp to create a unique key, which forces a new fetch Stack Overflow Post > Nuxt website shows old data when refreshing a page that uses useFetch. 3. Modify your component to use the updated pattern Based on the Stack Overflow solution for a similar issue:
<script setup>
const ssr = useRouteQuery('ssr')
const { pending, data: news } =
await useAsyncData('news', () => useDirectus().getItems('news', useDirectus().getLocaleFilter()))
const refresh = () => { refreshNuxtData('news') }
onMounted(() => {
if (get(ssr) === 'false') {
refresh()
}
})
</script>

<template>
<div class="flex flex-col gap-y-24 justify-center items-center container">
<Welcome :title="$t('news.title')" :subTitle="$t('news.description')" />

<div v-if="!pending && news?.length" class="lg:w-4/5 sm:w-full grid sm:grid-cols-1 lg:grid-cols-3 gap-12">
<AppNews v-for="n in news" :key="n.id" :news="n" />
</div>
</div>
</template>
<script setup>
const ssr = useRouteQuery('ssr')
const { pending, data: news } =
await useAsyncData('news', () => useDirectus().getItems('news', useDirectus().getLocaleFilter()))
const refresh = () => { refreshNuxtData('news') }
onMounted(() => {
if (get(ssr) === 'false') {
refresh()
}
})
</script>

<template>
<div class="flex flex-col gap-y-24 justify-center items-center container">
<Welcome :title="$t('news.title')" :subTitle="$t('news.description')" />

<div v-if="!pending && news?.length" class="lg:w-4/5 sm:w-full grid sm:grid-cols-1 lg:grid-cols-3 gap-12">
<AppNews v-for="n in news" :key="n.id" :news="n" />
</div>
</div>
</template>
This pattern uses refreshNuxtData to refresh the data and adds a pending check to handle loading states Stack Overflow Post > Updating a v-for component in Nuxt 3 Part 2. Let me know if any of these approaches work for your use case! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai3d ago
<@176706136232296449> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Single
SingleOP2d ago
I doubt this is the best approach but it works:
<script lang="ts" setup>
import { type News } from '~/types/directus/news'

const liveStore = useLiveStore()

const fetchNews = async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
}

const { data: news } = await useAsyncData<News[]>(async () => {
return await fetchNews()
})

onMounted(async () => {
if (liveStore.live) {
news.value = await fetchNews()
}
})
</script>

<template>
<div class="flex flex-col gap-y-24 justify-center items-center container">
<Welcome :title="$t('news.title')" :subTitle="$t('news.description')" />

<div v-if="news?.length" class="lg:w-4/5 sm:w-full grid sm:grid-cols-1 lg:grid-cols-3 gap-12">
<AppNews v-for="n in news" :key="n.id" :news="n" />
</div>
</div>
</template>
<script lang="ts" setup>
import { type News } from '~/types/directus/news'

const liveStore = useLiveStore()

const fetchNews = async () => {
return await useDirectus().getItems<News[]>('news', useDirectus().getLocaleFilter())
}

const { data: news } = await useAsyncData<News[]>(async () => {
return await fetchNews()
})

onMounted(async () => {
if (liveStore.live) {
news.value = await fetchNews()
}
})
</script>

<template>
<div class="flex flex-col gap-y-24 justify-center items-center container">
<Welcome :title="$t('news.title')" :subTitle="$t('news.description')" />

<div v-if="news?.length" class="lg:w-4/5 sm:w-full grid sm:grid-cols-1 lg:grid-cols-3 gap-12">
<AppNews v-for="n in news" :key="n.id" :news="n" />
</div>
</div>
</template>

Did you find this page helpful?