Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

beforeNuxtRender is called too soon #8620

Closed
MartinMalinda opened this issue Jan 11, 2021 · 11 comments · Fixed by #9332
Closed

beforeNuxtRender is called too soon #8620

MartinMalinda opened this issue Jan 11, 2021 · 11 comments · Fixed by #9332

Comments

@MartinMalinda
Copy link

MartinMalinda commented Jan 11, 2021

Versions

  • nuxt: v2.14.0
  • node: 10.16.0

Reproduction

https://github.com/MartinMalinda/before-nuxt-render

Steps to reproduce

npm install in the repo and open the dev server, open layouts/default.vue.

What is Expected?

When I change pinia state on the server, it is passed on beforeNuxtRender to nuxtState and then the state should be picked on the client.

There is an initialization of pinia store:

    const store = useDataStore();
    onServerPrefetch(async () => {
      await wait(400);
      store.state.name = "changed name";
    });

And a slight change to the state on the server side. Where initial name is changed to changed name after 400ms. Because pinia should transfer state from the server to the client these things should happen:

  1. Server waits 400ms ✅
  2. Server sends SSR HTML with changed name
  3. Client pinia should pick up state ❌
  4. Client should render changed name

What is actually happening?

changed name turns into initial name on the client.

The issue: beforeNuxtRender (probably)

To pass the state from server to client, pinia uses nuxtState together with beforeNuxtRender:

https://github.com/posva/pinia/blob/v1/nuxt/plugin.js#L17

When I put a bunch of console logs around, it seems like the context.beforeNuxtRender gets called almost immediately and not in fact, just before render.

Not only it does not wait the promise from useServerPrefetch to resolve, in this case it's called just right away so it's called even before the pinia stores initialize so not even default store state is passed.

I think something had to change on the Nuxt side, because I remember this working for me when I checked it some months ago, probably on Nuxt < 2.12.

@MartinMalinda
Copy link
Author

This is the order of logs:

pinia nuxt plugin
2021-01-11T15:50:54.481Z
context.beforeNuxtRender
2021-01-11T15:50:54.488Z
default component setup
2021-01-11T15:50:54.501Z
oServerPrefetch
2021-01-11T15:50:54.906Z

context.beforeNuxtRender should be last

@fabis94
Copy link

fabis94 commented Feb 9, 2021

I'm having the same issue. I put debugger; inside a Vue component's computed property and created() hook, and they both executed after beforeNuxtRender() did. How exactly is it supposed to work?

@fabis94
Copy link

fabis94 commented Feb 9, 2021

@MartinMalinda did you figure out some kind of workaround? i'm trying to set up Apollo Client SSR this way

@fabis94
Copy link

fabis94 commented Feb 9, 2021

Am I not understanding this hook properly? I want it to execute and fill the state object after all of the Vue components have rendered, initialized, executed all web requests etc. Is there supposed to be another hook for that?

@MartinMalinda
Copy link
Author

@fabis94 my workaround is here: vuejs/pinia#332 (comment)

Basically I created a wrap function that does onServerPrefetch and after the promise resolves it syncs the whole store state to ssrContext. I guess there could be an Apollo equivalent? If I use my usePiniaPrefetch instead of onServerPrefetch it works well for me.

@fabis94
Copy link

fabis94 commented Feb 9, 2021

@MartinMalinda Can you please elaborate on what onServerPrefetch is and where you're plugging in your wrapper function? Is this something Vue 3 related (i'm guessing from the Composition Api usage)?

@MartinMalinda
Copy link
Author

@fabis94

yes, this is composition API. onServerPrefetch comes with @vuejs/composition-api . It delays HTML response until the promise passed into it is resolved and it can be used within composition API.

If you're using Nuxt asyncData or fetch it also delays the SSR response.

So onServerPrefetch compes with Vue 2 composition API - I'm not sure what's the Vue 3 equivalent

@fabis94
Copy link

fabis94 commented Feb 9, 2021

@MartinMalinda I debugged through my build and it looks like beforeNuxtRender gets called before fetch & serverPrefetch on Vue 2.x as well. So it doesn't matter if I can put promises there, because beforeNuxtRender will already be called by that point.

Edit: I think I get it, instead of relying on beforeNuxtRender you build the state object incrementally w/ each Vue component that calls your hook. Not ideal, but at least it works until this gets fixed.

Copy link
Member

Regarding onServerPrefetch, note that this is just the Composition API version of the serverPrefetch() lifecycle hook in Vue 2 - see also this doc page.

I'm not sure that this is an issue with beforeNuxtRender, which happens right at the end of the createApp process: https://github.com/nuxt/nuxt.js/blob/119091c8d13b5f2ec9a7b7ac48f4e1b963019d8d/packages/vue-app/template/server.js#L348.

Moreover, you can still update the state within serverPrefetch - see this codesandbox for an example of how to do it.

@MartinMalinda
Copy link
Author

MartinMalinda commented Feb 15, 2021

I'm not sure that this is an issue with beforeNuxtRender, which happens right at the end of the createApp process:

I see. In that case I was mislead by the naming, I thought it's a hook to do something "last minute before render" which on server would mean "just before sending the HTML response"

And the docs say this:

Use this method to update NUXT variable rendered on client-side, the fn (can be asynchronous) is called with { Components, nuxtState }, see example.

If the method is called so soon like this, even before all components propagate, it is then not so useful to set state into __NUXT__.

So perhaps someone writing these docs got confused as well?

@fabis94
Copy link

fabis94 commented Feb 15, 2021

In that case we definitely need some sort of hook that gets called right after all rendering is done and is available through Nuxt.js plugins. 'vue-renderer:ssr:context' is a hook like that, but it's only available through Nuxt.js Modules and thus doesn't have access to any request objects that you could serialize into JS for client-side hydration (e.g. request's Apollo Client instance).

What I ended up doing was putting Apollo Client inside ctx.ssrContext.__apollo from my Nuxt.js plugin, and then using a separate Nuxt.js Module that hooked into 'vue-renderer:ssr:context', retrieved ssrContext.__apollo, collected the client's state from it and put it onto the Nuxt state object w/ ssrContext.nuxt.apollo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants