Understanding best practices for SSR + CMS setups

I'm familiar with using Nuxt 3 as a static site generator using nuxt content, but I'm currently building a site that will need to be SSR'd. I have a Strapi instance setup and ready to go, and now that I'm connecting it to the Nuxt, I'm having a hard time wrapping my head around best practices and how to set this up. Here's an example page in my app:
<script setup lang="ts">
const { data } = await useFetch(process.env.NUXT_STRAPI_URL + '/api/blog-posts', {
headers: {
Authorization: 'Bearer ' + process.env.NUXT_STRAPI_TOKEN
}
});
</script>

<template>
<main>
{{ data }}
</main>
</template>
<script setup lang="ts">
const { data } = await useFetch(process.env.NUXT_STRAPI_URL + '/api/blog-posts', {
headers: {
Authorization: 'Bearer ' + process.env.NUXT_STRAPI_TOKEN
}
});
</script>

<template>
<main>
{{ data }}
</main>
</template>
My intention is that the data is fetched server side only, and is rendered on the page and delivered to the client. Looking through the docs for nuxt tells me that this request may also happen on the client? I guess I'm confused, but that should never happen in my ideal case. I also wasn't able to get this example working as the environment variable usage here wasn't available on the client, and causing issues. Do I wrap this in a conditional to ensure it only runs on the server? How, then, does the template access the data? I'd appreciate any insight or direction on where I can look to get a clearer answer on these things, as I have yet to see a complete and simple example of this in practice that isn't totally bloated with other things going on. I also am aware the strapi has an official nuxt plugin, but I am avoiding using it as it comes opinionated with ways that authentication should work and it is not configurable enough for my use case.
12 Replies
topherlicious
topherliciousOP9mo ago
After some experimentation, it looks like this might accomplish what I'm after?
<script setup lang="ts">
const blogs = useState('blogs', () => []);

if (process.server) {
const { data } = await useFetch<any>(process.env.NUXT_STRAPI_URL + '/api/blog-posts', {
headers: {
Authorization: 'Bearer ' + process.env.NUXT_STRAPI_TOKEN
}
});

blogs.value = data.value!.data;
}
</script>

<template>
<main>
count: {{ blogs.length }}
</main>
</template>
<script setup lang="ts">
const blogs = useState('blogs', () => []);

if (process.server) {
const { data } = await useFetch<any>(process.env.NUXT_STRAPI_URL + '/api/blog-posts', {
headers: {
Authorization: 'Bearer ' + process.env.NUXT_STRAPI_TOKEN
}
});

blogs.value = data.value!.data;
}
</script>

<template>
<main>
count: {{ blogs.length }}
</main>
</template>
But this does not seem like the right way to do this at all. Editing to say that this method indeed does not work during client navigation, but only on a full page load on this page specifically.
Patrity
Patrity9mo ago
You may want to review the strapi nuxt docs. You shouldn't need the server route you've called. Instead, you should use the composables provided by the strapi module and call it with useAsyncData https://strapi.nuxtjs.org/usage What you've done here won't fetch anything when a client loads the component, I think you may be overthinking the SSR side of things. When you call useAsyncData it'll fetch the data async, it also exposes a few methods for handling stuff like refresh and pending. Check it out here: https://nuxt.com/docs/getting-started/data-fetching#useasyncdata
topherlicious
topherliciousOP9mo ago
Thanks for the response. I mentioned in my first message that I am purposefully avoiding using the Nuxt Strapi module altogether. I would also just like to understand how this works without it. Strapi is not the only CMS out there, and I assume it MUST be possible to achieve what I'm looking to do... I have read the documentation that you have linked in pretty good detail but I'm still not understanding how to achieve my specific use case. This seems like it would be a common issue to solve with SSR. Make a fetch on the server, and then render the result. Unless I am just fundamentally misunderstanding how these things are built, that doesn't seem like an unusual setup? This is my first time going for SSR so it is totally possible that this is just, not how things are done haha. I was hoping I could avoid client side requests to the CMS API altogether.
Patrity
Patrity9mo ago
So can I ask why you're set on SSR then? I think maybe SSG is more what you're going for.
topherlicious
topherliciousOP9mo ago
Fair question, and SSG is what I'm used to. This website is going to have a blog managed by multiple users, and using Nuxt file-based SSG is not going to be a good choice in the long run I think.
Patrity
Patrity9mo ago
So this may be what you're looking for
//useState is an SSR-safe ref essentially
const blogs = useState('blogs')
blogs.value = await useFetch('/api/blog-posts', {
headers: {
Authorization: 'Bearer ' + process.env.NUXT_STRAPI_TOKEN,
fetchMode: 'headless'
},
//run on ssr
server: true,
//do not refresh result on CSR
watch: false
})
//useState is an SSR-safe ref essentially
const blogs = useState('blogs')
blogs.value = await useFetch('/api/blog-posts', {
headers: {
Authorization: 'Bearer ' + process.env.NUXT_STRAPI_TOKEN,
fetchMode: 'headless'
},
//run on ssr
server: true,
//do not refresh result on CSR
watch: false
})
the server var is by default, true, so not needed tbh
topherlicious
topherliciousOP9mo ago
Why do you want me to add a header to my request...? Setting watch to false seems like the opposite of what I'd want
Patrity
Patrity9mo ago
by default, useFetch will fetch once on the server, then check if it is changed on the client, if so, itll update. You're not wanting that behavior, correct? You only want it to fetch on the server. https://nuxt.com/docs/api/composables/use-fetch#params:~:text=function%20result-,watch,-%3A%20watch%20an%20array
topherlicious
topherliciousOP9mo ago
I see, and why would I add that header? The next issue I'd need to solve is that the fetch does not occur when navigating to the page, only on a fresh load directly on that page. Just to think out loud, I can just allow requests to happen client side and use a whitelist on my CMS server. That would solve my concerns here. But I still really want to know the proper way to do this, I can't be the first person to want to accomplish something like this. I wonder if it's just something that isn't suited for the Nuxt way of doing things.
Patrity
Patrity9mo ago
I'm going to leave this for someone hopefully more versed to tackle, but I really dont think this is necessary since itll only update if there are changes needed, so if you are still serving the same data from the backend, it wont update and its still rendered from the server. I guess I just don't understand why you are trying to accomplish this. Best of luck!
topherlicious
topherliciousOP9mo ago
Hmm okay, I think I also need to do some more reading myself if I'm not explaining myself clearly. I appreciate your help, thanks! Take care 👋
topherlicious
topherliciousOP9mo ago
Just to update for anyone that finds this later: The pattern I'm looking for is not a thing in Nuxt. If you want to force SSR for every page, you need to use SSG instead. I find it a bit odd that there's no standard accepted way to accomplish this, but I guess that just isn't Nuxt's thing. Instead, the pattern is to make an internal server route that then uses your API key to make another external request. More info and a solid example found here: https://github.com/nuxt/nuxt/discussions/20544#discussioncomment-5741868
GitHub
Using API tokens (private runtimeConfig) in client-side · nuxt nuxt...
nuxt.config.ts runtimeConfig: { public: { BASE_URL: process.env.BASE_URL, }, API_TOKEN: process.env.API_TOKEN }, I am using useFetch() like this: useFetch("url", { baseURL: config.public....
Want results from more Discord servers?
Add your server