0

I followed the vite guide for Vue SSR and was able to migrate my Vue compositional API app to SSR and it works fine.

The only open issue that data fetching is still done on the client. I'm using the onMounted() hooks to fetch the data and it's coming from a rest api.

Right now components who need to load data look something like this:

<script setup lang="ts">
...
const loaded = ref(false);
const data = ref([]);

// Client side hook. Could also be onMounted()
onBeforeMount(async () => {
  data.value = await api.getSomething();
  loaded.value = true;
});
</script>

The problem with these hooks is, that it is executed on the client side and not on the server. So the page is not delivered fully rendered but has the same behavior like without SSR.

I can only think of a kind of server side hook.

Does anybody know how to load the data on the server BEFORE the page is sent to the client?

Update 1

As @Estus Flask indicated, there is a <suspense/> component which allows "orchestrating async dependencies", so right now I have following code:

<template>
    <RouterView v-slot="{ Component }">
      <template v-if="Component">
        <suspense>
          <!-- main content -->
          <component :is="Component"></component>

          <!-- loading state -->
          <template #fallback>
              <span>Loading...</span>
          </template>
        </suspense>
      </template>
    </RouterView>
    ...
</template>

A page component now run the code above (reduced example):

<template>
    <ul>
        <li v-for="item in activeItems">{{ item.title }}</li>
    </ul>
</template>

<script setup lang="ts">
...
// The data is stored in a central vuex store.
import { useStore } from '../store';

const api = createClient();
const store = useStore();

// Top-level await to block page execution(??)
const res = await api.getItems();
res.data.forEach(item => store.commit('addItem', item));

const activeItems = computed(() => store.state.items.slice().filter(x => x.active));

...
</script>

This renders fine, the problem is that the page load is not blocked but the page is delivered and then later updated when the data has arrived from the await api.getItems(). So the behavior is as if I had not used SSR at all.

How do I block the page load so that the page is blocked and fully rendered served to the client?

4
  • The question lacks stackoverflow.com/help/mcve . But that you use onMounted makes sense, it runs only on client side. Fetch data in setup with suspense instead Commented Sep 3, 2024 at 5:26
  • @Estus Flask This is not a matter of MRE but conceptual. The entire application is like a regular Vue app otherwise. Commented Sep 3, 2024 at 8:20
  • The concept is hard to describe in a way that's enough for a question to be answered. This is the reason why code is required by rules for the questions that have some existing code behind it. It's unknown what else could be wrong on your side. But for what you described (onMounted) the comment above applies. Just use suspense for SSR, this is one of the reasons it exists. Commented Sep 3, 2024 at 11:36
  • @Estus Flask Thank you for the feedback, I try to improve the question Commented Sep 3, 2024 at 11:54

1 Answer 1

1

Some lifecycle hooks like beforeMount and mounted are client-side only and can deliberately be used for this purpose. While setup function is executed on both server and client side. The component should use suspense in order for asynchronous actions such as data fetch to be used in SSR. So it should be:

const data = ref();
data.value = await api.getSomething();

Or if data is not reactive besides the initial fetch:

const data = await api.getSomething();
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you! This sort of works but does not deliver a fully rendered page. I actually have currently the same behavior with a different process. The page is deliverered before the data has been rendered and then the page is updated when the data has arrived. Can I block the entire page delivery somehow until all data has been loaded?
Yes, this is what await inside setup does. A component cannot be rendered on server side until getSomething is finished, it cannot work other way around if you did everything correctly. Make sure there is no<suspense> element in templates, as it would prevent the renderer from waiting for async comp.
E.g. here I'd expect it to work as intended github.com/bluwy/create-vite-extra/tree/master/template-ssr-vue . As I already noted, this is specific to your case, as it's in most code-related questions. If the problem persists, please, provide a way to reproduce, so it could be addressed. As for "conceptual" part, this is precisely what the answer shows
I'm not sure to open a new issue or to update this question. I'm using in a component a top-level await within the script setup block - exactly like in the docs and your example - but the page is not blocked until the data is loaded. The page is delivered and then the content for that specific section of the page updated. I have only a <suspense> block in the main template within the <RouterView> like in the documentation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.