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

onServerPrefetch with useId cause hydration issue if treeshaken client-side #12591

Open
huang-julien opened this issue Dec 21, 2024 · 6 comments

Comments

@huang-julien
Copy link
Contributor

huang-julien commented Dec 21, 2024

Vue version

3.5.13

Link to minimal reproduction

https://stackblitz.com/edit/vue-ssr-example-bsqzmafk?file=app.js,client.js,server.js

Steps to reproduce

open your console and see there's a hydration mismatch.

Comp.vue's useId is not retturning the same value server side and client side

What is expected?

no hydration issue due to useId

What is actually happening?

The cause of this issue is that we run onServerPrefetch only server-side.

This happen in Nuxt because we treeshake onServerPrefetch in client-bundle and we also wrap onServerPrefetch with import.meta.server flag in useAsyncData composable.

In runtime, this results into the component being marked as async boudary only server side with markAsyncBoundary but not client side --> resulting to a hydration issue because useId returns different strings.

System Info

No response

Any additional comments?

Nuxt issue nuxt/nuxt#30289

@edison1105 edison1105 added scope: ssr 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Dec 23, 2024
@edison1105
Copy link
Member

edison1105 commented Dec 23, 2024

When rendering on the CSR, it checks if the component contains onServerPrefetch hook at

if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) {
// async setup / serverPrefetch, mark as async boundary for useId()
markAsyncBoundary(instance)
}
If it does, it calls markAsynvBoundary to make it consistent with SSR behavior.

@edison1105 edison1105 added need discussion and removed 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Dec 23, 2024
@huang-julien
Copy link
Contributor Author

huang-julien commented Dec 23, 2024

In Nuxt side, we could either treeshake only the callback of onServerPrefetch in client side bundle or add instance.sp = instance.sp || [].

But i'm not really in favour of the second one tho.

Or maybe can we leave it to vue: we remove the treeshaking of onServerPrefetch and maybe can the vue compiler do the treeshaking for client-side bundle ? WDYT ?

cc @danielroe

@cernymatej
Copy link

imho, it would be nice to tree-shake it on the Vue side

@danielroe
Copy link
Member

danielroe commented Dec 23, 2024

I'm happy to remove treeshaking of onServerPrefetch, more generally, and leave it up to the vue compiler - but it's not quite that straightforward. In this case, the call to onServerPrefetch is in an entire separate if/then fork (because data fetching on the server and in the hydration process in the browser run differently).

  // Server side
  if (import.meta.server && fetchOnServer && options.immediate) {
    const promise = initialFetch()
    if (getCurrentInstance()) {
      onServerPrefetch(() => promise)
    } else {
      nuxtApp.hook('app:created', async () => { await promise })
    }
  }

https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/asyncData.ts#L338-L347

In other words, we'd have to artificially add an 'onServerPrefetch' which awaits an empty promise in order to 'match'. More than happy to do that, but is there another way?

@huang-julien
Copy link
Contributor Author

huang-julien commented Dec 23, 2024

Yes, useAsyncData is a too specific case to be handled by vue-compiler. In the meantime, we can try the second option using an empty instance.sp = instance.sp || []
Or fully remove the treeshaking of onServerPrefetch and we try treeshaking the callback in Nuxt side ?

@edison1105
Copy link
Member

Vue compiler cannot find all onServerPrefetch calls during compilation. Apart from the scenario mentioned by @danielroe, users may also call an externally imported function, which internally calls onServerPrefetch.

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

No branches or pull requests

4 participants