N
Nuxt9mo ago
Ajility

Handling API Requests on pages from Client / Server Side (SSR)

New to Nuxt & SSR. In Nuxt3, I have noticed that I cannot call useAsyncQuery on the client side. I get console warnings, and the docs say not to do this (but don't say why).
17 Replies
Ajility
AjilityOP9mo ago
My pages/products/[product_slug].vue look like this:
<template>
<product-view :product="product"/>
</template>

<script setup lang="ts">
import { useRoute } from 'vue-router'
import { isEmpty } from 'lodash'
import { useCatalogStore } from '@/pinia/CatalogStore'

const route = useRoute()
const product: any = useState()
const productSlug: ComputedRef<string> = computed(() => {
return route.params.product_slug as string
})
const CatalogStore = useCatalogStore()

const setProductData = async(isServerSide: boolean = false) => {
if (productSlug.value) {
product.value = await CatalogStore.fetchProductBySlug({
is_server_side: isServerSide,
slug: productSlug.value
})
}
}

onMounted(async() => {
if (isEmpty(product.value)) {
await setProductData()
}
})

onServerPrefetch(async() => {
await setProductData(true)
})

watch(productSlug.value, async () => {
await setProductData()
})

definePageMeta({
key: route => 'view-product', // prevent newing up a component on each link click - reuse existing
})
</script>
<template>
<product-view :product="product"/>
</template>

<script setup lang="ts">
import { useRoute } from 'vue-router'
import { isEmpty } from 'lodash'
import { useCatalogStore } from '@/pinia/CatalogStore'

const route = useRoute()
const product: any = useState()
const productSlug: ComputedRef<string> = computed(() => {
return route.params.product_slug as string
})
const CatalogStore = useCatalogStore()

const setProductData = async(isServerSide: boolean = false) => {
if (productSlug.value) {
product.value = await CatalogStore.fetchProductBySlug({
is_server_side: isServerSide,
slug: productSlug.value
})
}
}

onMounted(async() => {
if (isEmpty(product.value)) {
await setProductData()
}
})

onServerPrefetch(async() => {
await setProductData(true)
})

watch(productSlug.value, async () => {
await setProductData()
})

definePageMeta({
key: route => 'view-product', // prevent newing up a component on each link click - reuse existing
})
</script>
You will notice that I am tracking if the request is originated from Server Side or Client side using an argument that is passed to setProductData().. my pinia CatalogStore.fetchProductBySlug action looks like this:
async fetchProductBySlug(params: { is_server_side: boolean, slug: string, set_active?: true }): Promise<NuxtProduct> {
const variables = {
slug: `/products/${params.slug}/`,
}

let response: any
if (params.is_server_side) {
response = await useAsyncQuery({
query: getProductBySlugQuery,
variables: variables,
})

} else {
response = await useApolloClient().client.query({
query: getProductBySlugQuery,
variables: variables,
})
}

const data = params.is_server_side ? response.data.value : response.data
const product = bigCommerceToNuxtProduct(data.site.route.node)
this.addProduct(product)
if (params.set_active) {
this.setActiveProduct(product)
}
return product
},
async fetchProductBySlug(params: { is_server_side: boolean, slug: string, set_active?: true }): Promise<NuxtProduct> {
const variables = {
slug: `/products/${params.slug}/`,
}

let response: any
if (params.is_server_side) {
response = await useAsyncQuery({
query: getProductBySlugQuery,
variables: variables,
})

} else {
response = await useApolloClient().client.query({
query: getProductBySlugQuery,
variables: variables,
})
}

const data = params.is_server_side ? response.data.value : response.data
const product = bigCommerceToNuxtProduct(data.site.route.node)
this.addProduct(product)
if (params.set_active) {
this.setActiveProduct(product)
}
return product
},
I check if params.is_server_side is true. If It is, I use useAsyncQuery, else useApolloClient(). This is wordy and doesn't seem right.. how should I be approaching this type of issue? I think I'm missing something fundamental..
Sandvich
Sandvich9mo ago
So I'm not sure on the specifics of the apollo client but could you provide some error logs because looking at the code for useAsyncQuery it looks like it shouldn't throw any errors as it's just a simple wrapper around Nuxt's useAsyncData Also you don't have to pass the is_server_side variable through, you can just do
if (import.meta.server) {
response = await useAsyncQuery({
query: getProductBySlugQuery,
variables: variables,
})

} else {
response = await useApolloClient().client.query({
query: getProductBySlugQuery,
variables: variables,
})
}
if (import.meta.server) {
response = await useAsyncQuery({
query: getProductBySlugQuery,
variables: variables,
})

} else {
response = await useApolloClient().client.query({
query: getProductBySlugQuery,
variables: variables,
})
}
Ajility
AjilityOP9mo ago
@Sandvich - that saves some code, thank you. If I just useAsyncData on client side / server side, I see the following:
Ajility
AjilityOP9mo ago
No description
Ajility
AjilityOP9mo ago
Everything seems to work fine, but I get that console warning..
Sandvich
Sandvich9mo ago
oh ok yeah so useAsyncData is supposed to be called during setup, the whole point of it is that it manages your async data and so calling it inside fetch means any data you put in it is immediately lost so if you just need one time data to fill a pinia store I'd use $fetch I'm sure there's something better for the apollo client but the documentation is lacking
Ajility
AjilityOP9mo ago
So a pattern similar to the one i'm following to differentiate between SSR / client would be required for my use case.. basically a customer can view different products during a session on my website.. as they navigate around, I keep information in the store cached incase they navigate to a product they've already viewed, they've already got that info loaded
Sandvich
Sandvich9mo ago
Why don't you use just useAsyncData? I'm pretty sure that caches data between page navigations
Sandvich
Sandvich9mo ago
ah ok I was wrong you have to add some extra things https://www.youtube.com/watch?v=aQPR0xn-MMk
Alexander Lichter
YouTube
Nuxt 3.8 - Client-side caching with getCachedData ✨
⚡️ Nuxt 3.8 was released just a day ago, packed with lots of amazing features. Among the updates, one change for useFetch and useAsyncData is pretty significant IMO - getCachedData. In this video, we will explore how that function can avoid superfluous calls to an API and cache data for visitors on the client, either until a hard-reload or...
Sandvich
Sandvich9mo ago
ok so just looking at the code for apollo it looks like useAsyncQuery does this caching for you (https://github.com/nuxt-modules/apollo/blob/v5/src/runtime/composables.ts#L171) I hope and from the documentation it says it's SSR friendly
GitHub
apollo/src/runtime/composables.ts at v5 · nuxt-modules/apollo
Nuxt.js module to use Vue-Apollo. The Apollo integration for GraphQL. - nuxt-modules/apollo
Ajility
AjilityOP9mo ago
I've been messing with issues related to SSR / client side for a while now.. there was some issue wirth useAsyncQuery Let me try it out again and recall what the warning was.. I've gone mad - sorry, so useAsyncQuery is what I'm currently trying to work with for SSR now ex1. Sometimes the request to the product API can initiate on the server (the user visit a direct URL to a product page) ex2. Other times, the request would originate from the client (the user clicks a related product on the product page they're currently on) the way I understand it, in ex2, it would not be SSR?
Sandvich
Sandvich9mo ago
<template>
<product-view :product="product"/>
</template>

<script setup lang="ts">
const route = useRoute()
const productSlug: ComputedRef<string> = computed(() => {
return route.params.product_slug as string
})

const { data: product } = await useAsyncQuery({
query: getProductBySlugQuery,
variables: productSlug,
}) // Not sure on the specifics here

definePageMeta({
key: route => 'view-product', // prevent newing up a component on each link click - reuse existing
})
</script>
<template>
<product-view :product="product"/>
</template>

<script setup lang="ts">
const route = useRoute()
const productSlug: ComputedRef<string> = computed(() => {
return route.params.product_slug as string
})

const { data: product } = await useAsyncQuery({
query: getProductBySlugQuery,
variables: productSlug,
}) // Not sure on the specifics here

definePageMeta({
key: route => 'view-product', // prevent newing up a component on each link click - reuse existing
})
</script>
yeah that's correct This is my guess at what you should be doing, using pinia ontop of a client that already manages state for you is absolutely bonkers overkill and more cumbersome to use This is pretty similar to what I would do in tanstack query so I'm hoping it's just as simple with this apollo stuff
Ajility
AjilityOP9mo ago
I'm working with a lot of new to me technologies like GraphQL & SSR, so maybe its time to take a step back and read more about what Apollo does I appreciate all of your help Sandvich.. I think I'm looking in the right direction now.. apollo is probably not what I want to use
Sandvich
Sandvich9mo ago
yeah imo graphql is bad and shouldn't be used for new projects, too many footguns
Ajility
AjilityOP9mo ago
yea I prefer RESTful APIs, but I'm using BigCommerce headless for my store & getting info about a product is maybe 4 different API calls in REST or 1 API call in GraphQL
Sandvich
Sandvich9mo ago
If you're looking to manage state asynchronously and call api endpoints I'd recommend @tanstack/vue-query But if you're learning learning and not just learning because you've taken over a project for work then I'd start with learning how to do things with just Nuxt works first, so you know fundamentals ah yeah I see. In that case take a step back and learn Apollo as you said good luck!
Ajility
AjilityOP9mo ago
thanks 🙂
Want results from more Discord servers?
Add your server