vue: serverPrefetch doesn't work as expected

Version

2.6.10

Steps to reproduce

Suppose we have a component:

export default {
    data(){
        return { foo: null}
    }
    async serverPrefetch(){
        this.foo = 'serverPrefetch'
    },
    created(){
        this.foo = 'created'
    },
}

foo is initially null, then created() updates it to created and this reflects in the html interpolitatiion, but when serverPrefetch() updates this.foo it is not reflected on the rendered html, despite being called afterwards.

What is expected?

serverPrefetch() should reflect rendered and updated data variables when rendered on SSR

What is actually happening?

serverPrefetch() doesn’t update the rendered view with new data after being called

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 36 (17 by maintainers)

Most upvoted comments

To conclude: your steps 2 and 4 in your diagram are not technically possible.

@DominusVilicus the code you originally posted is working as expected (per test case). By design, a component’s serverPrefetch should only affect data of its own (or child components via passed props). The only case that it “doesn’t work” is that when a component’s serverPrefetch is mutating state that doesn’t belong to itself.

- A
   |- B
   |- C

Ok, let’s say you have serverPrefetch on both A, B & C, and that A, B & C all rely on different data from your API to correctly render. For example, A needs an API result to decide whether to render B or C with a v-if. B & C needs some other APIs calls to display data in their template too, and their serverPrefetch relies on some props passed by A.

What you are proposing will happen:

  • A is created
  • A renders
  • Neither B nor C are created because A doesn’t have the necessary data from the API to decide which one to render
  • A’s serverPrefetch is collected
  • Await all serverPrefetchs, which means the one from A
  • Generate the string representation of the app

There is a big issue here: neither B nor C where created, but you expected one of them to be created.

How to fix this? The only way is to render A again after the first serverPrefetchs awaiting, so that the VNode for B or C is created. So let’s say we add a new rule: after the serverPrefetch is awaited, we re-render the component.

Here is what would happen:

  • A is created
  • A renders
  • Neither B nor C are created because A doesn’t have the necessary data from the API to decide which one to render
  • A’s serverPrefetch is collected
  • Await all queued serverPrefetchs, which means the one from A
  • A renders again
  • B or C is created, let’s say B (v-if was true)
  • B renders
  • B’s serverPrefech is collected
  • Await all queued serverPrefetchs, which means the one from B
  • B renders again
  • Generate the string representation of the app

We can already see we did multiple renders of the same component, which will affect performance. But it can get worse from there: what if B serverPrefetch actually mutated data that A depends on?

  • A renders again
  • A’s serverPrefetch is collected
  • Await all queued serverPrefetchs, which means the one from A
  • A renders again
  • B is updated (prop)
  • B renders again
  • etc

So now we actually rendered A four times and B three times! And this can go on and on and on…


Takeaways

1 - It’s impossible to have one and only one step with all the possible serverPrefetch in the app and call/await them all at once. The code can’t guess if B or C would have been rendered and if we need to call their serverPrefetch. Also, if, like in the above example, B serverPrefetch relies on data passed via props by A, it can’t work without having a intermediate step where A re-renders to pass the value via props to B. 2 - We can’t render all the components in the app at once either after waiting for all the possible serverPrefetch (which is impossible, see takeaway 1). Also, component instances are not created until the parent component is rendered, so their serverPrefetchs don’t even exist yet. 3 - Mutating state that a parent component depends on is a very bad idea when doing SSR since it can lead to multiple cascading updates. You don’t want the HTML page to take ages to be generated and sent to the client. 4 - We can’t call/await serverPrefetch in a component instance after rendering it, otherwise we have to render it multiple times (and even call its serverPrefetch again).

is this expected behaviour @yyx990803 @Akryum, wouldn’t it be more practical and expected for all the serverPrefetchs to be called before the render functions? especially when in the ssr vue examples state that the vuex store can be updated with serverPrefetch

https://ssr.vuejs.org/guide/data.html#data-store https://ssr.vuejs.org/api/#serverprefetch

otherwise the client app would render using different state and the hydration would fail.

while the hydration isn’t failing, the server is rendering a different state (because of the reasons described in the above comment). Is this what you’d expect serverPrefetch to function like?

In my use case, index.vue is using layout.vue as a component (wrapping index.vue in app.vue’s tag,s since app.vue is the “parent” component, it generates all the necessary global state mixins (as to prevent code duplication). app.vue has it’s own component, header.vue (which is rendered after app.vue and receives the correct state.

What’s happening in order is: import index import app import header index.serverPrefetch() index.render() app.serverPrefetch() app.render() header.serverPretch() header.render() (this recieves correct global state from app.vue as it is rendered afterwards)

In my opinion, what should be happening is: import index import app import header index.serverPrefetch() app.serverPrefetch() header.serverPretch() index.render() //recieves same global state as all components app.render() header.render()

@DominusVilicus You should update the global state in a component which is a parent of all the components requiring it.

While i’m not well versed in SSR(it all seems like magic to me) I will interject this:

I believe any data collection SHOULD occur prior to a component being rendered to me, both server side and client side…the model should be exactly the same as soon as you provide inconsistencies, you provide surface area for bugs

Consider a simple example where the component conditionally renders one component or another depending on its state:

<template>
  <MyComponentA v-if="showA"/>
  <MyComponentB v-else/>
</template>

Now you can see we can’t create the component instances of MyComponentA nor MyComponentB because we don’t know which one will be created before actually rendering the current component.

It seems the render function for each component is being called before other components serverPrefetch is.

Shouldn’t all serverPrefetchs for all components in the components: {...} be called before any component is actually rendered?

Reactivity is entirely disabled during SSR, so there will be no “updates” - the state can be mutated, but it must happen before the render function is called.

I figured out the issue. Vue.observeable doesn’t trigger a re-render on server components when they import a child component which created the Vue.observeable

It’s pretty complex, but can someone clarify whether components are re-rendered after serverPrefetch updates a Vue.observable that multiple components rely on. It seems not to be happening in my case