I am creating a medium-size webshop, with a lot of dynamic data using micro services.
This is the stack I am using:
- Nuxt 3 SSR
- Strapi CMS (headless node-based CMS) -> linkedin with Crowdin for translations
- Checkout, Cart, ... micro services
How can I efficiently fetch dynamic content based on user-visited URLs (slugs) in my Nuxt 3 frontend utilizing a Strapi headless backend with GraphQL for data retrieval, while maintaining clean code and optimal performance? I know how to fetch data based on slug (with GraphQL and Rest), but the way it is going right now, just doesn't feel right.
The general idea is that we work the way Apple does. Meaning, they have almost no URL depth:
| URL | Content type | Language |
|---|---|---|
https://www.apple.com/ipad/ |
category page | /uk/ |
https://www.apple.com/ipad-pro/ |
product page | /uk/ |
https://www.apple.com/iphone/ |
category page | /uk/ |
https://www.apple.com/iphone-15-pro/ |
product page | /uk/ |
https://www.apple.com/shop/buy-iphone/ |
shop page | /uk/ |
https://www.apple.com/apple-one/ |
product page | /uk/ |
| .... | .... | .... |
They are fetching multiple types of "content" from the 'root'. We would like to take that idea even further:
| URL | Content type | Language |
|---|---|---|
https://www.ourwebshop.com/uk/:some-category/ |
category page | /uk/ |
https://www.ourwebshop.com/uk/:some-subcategory/ |
subcategory page | /uk/ |
https://www.ourwebshop.com/uk/:some-bundle/ |
bundle page | /uk/ |
https://www.ourwebshop.com/uk/:some-product/ |
product page | /uk/ |
https://www.ourwebshop.com/uk/:some-legal/ |
legal page | /uk/ |
https://www.ourwebshop.com/uk/:some-blog-category/ |
blog category page | /uk/ |
https://www.ourwebshop.com/uk/:some-blog-category/:blog-post |
blog page | /uk/ |
https://www.ourwebshop.com/uk/:some-product/:specifications/ |
specifications page | /uk/ |
https://www.ourwebshop.com/uk/shop/:some-product/ |
shop page | /uk/ |
https://www.ourwebshop.com/uk/compare/ |
comparison page | /uk/ |
https://www.ourwebshop.com/nl-be/:some-product/ |
product page | /nl-be/ |
https://www.ourwebshop.com/nl-be/shoppen/:some-product/ |
shop page | /nl-be/ |
| .... | .... | .... |
This webshop needs to me made in at least 8 languages.
Our current approach is:
[slug]/index.vue:
<script setup lang="ts">
import CategoryPage from '~/apollo/queries/pages/categories/CategoryPage.gql'
import ProdyctPage from '~/apollo/queries/pages/products/ProductPage.gql'
import BundlePage from '~/apollo/queries/pages/bundles/BundlePage.gql'
import BlogCategoryPage from '~/apollo/queries/pages/blog/category/BlogCategoryPage.gql'
import BlogItemPage from '~/apollo/queries/pages/blog/item/BlogItemPage.gql'
//..the list goes on
const { localeProperties } = useI18n()
const slug = useRoute().params.slug;
const { data } = await useAsyncQuery(CategoryPage, {
slug: slug,
locale: localeProperties.value.locale_strapi,
market: localeProperties.value.country
})
if(data == null) {
const { data } = await useAsyncQuery(ProdyctPage, {
slug: slug,
locale: localeProperties.value.locale_strapi,
market: localeProperties.value.country
})
if(data == null) {
const { data } = await useAsyncQuery(BundlePage, {
slug: slug,
locale: localeProperties.value.locale_strapi,
market: localeProperties.value.country
})
//... and so on...
}
}
</script>
<template>
<div class="w-screen">
<!--Here are all the layout components-->
<component
v-for="component in data.attributes.layout"
:key="component.id"
:is="collection[component.__typename]"
v-bind:="component" />
</div>
</template>
So many calls just to get the right content from Strapi, this can't be the right way of doing this. And so I thought, what if we could do one query that just asks for all the data? Here is another option:
Have a "dynamic GQL query for all possible content".
DynamicPageQuery.gql:
query SomeDynamicPagequery($locale: I18NLocaleCode, $market: String, $slug: String) {
Products(locale: $locale, filters: { slug: { eq: $slug } }) {
data {
attributes {
title
layout {
__typename
...CategorySectionDisplay
...HottubSectionDisplay
...AccessorySectionDisplay
...StorySectionDisplay
}
seo {
meta_title
meta_description
keywords
social_title
social_description
indexation
}
}
}
}
Categories(locale: $locale, filters: { slug: { eq: $slug } }) {
data {
attributes {
title
layout {
__typename
...CategorySectionDisplay
...JustASectionDisplay
...AccessorySectionDisplay
...StorySectionDisplay
}
seo {
meta_title
meta_description
keywords
social_title
social_description
indexation
}
}
}
}
Bundles(locale: $locale, filters: { slug: { eq: $slug } }) {
data {
attributes {
title
layout {
__typename
...CategorySectionDisplay
...HottubSectionDisplay
...StorySectionDisplay
}
seo {
meta_title
meta_description
keywords
social_title
social_description
indexation
}
}
}
}
...... and keep on querying for other dynamic data
}
[slug]/index.vue
<script setup lang="ts">
import DynamicPage from '~/apollo/queries/pages/DynamicPage.gql'
const { localeProperties } = useI18n()
const slug = useRoute().params.slug;
const { data } = await useAsyncQuery(DynamicPage, {
slug: slug,
locale: localeProperties.value.locale_strapi,
market: localeProperties.value.country
})
</script>
<template>
<div class="w-screen">
<!--Here are all the layout components-->
<component
v-for="component in data.attributes.layout"
:key="component.id"
:is="collection[component.__typename]"
v-bind:="component" />
</div>
</template>
but than again, I am querying for so many different content types. The GQL file is going to be massive, even when using fragments. And we are not talking about the duration of the call. We want the pagespeed to be as fast as possible, so this doesn't seem like an option either.
The goal is simple. Keep the frontend super clean, while maintaining performance. It would be great to just work with 1 call to Strapi. I thought about creating an endpoint in Strapi as well that matches the slug with the right content-type, but I'm not that experienced in backend yet to do it right. And since we are doing some complex querying, I just don't want to mess with the Strapi Core.
What do you think?! Is there a solution I am just not seeing yet?