P4sca1/nuxt-headlessui

Update to version 1.7.18 of healessui/vue

gkkirilov opened this issue · 17 comments

HeadlessUI deployed a fix for Nuxt hydration error for generated ids.

Reference: tailwindlabs/headlessui#2913

P4sca1 commented

This module uses ^1.0.0 syntax for the headless UI version, so that you can install whatever version you like and can update headless UI without updating this dependency. Just refresh your lock file, or install the latest version manually.

Remember that the fix requires you to add code to your app.vue file to provide the id generation function. This module can't do this for you due to limitations with the useId hook by nuxt.
Headless UI 2 will add this feature by default using Vue 3.5 builtin useId function.

@P4sca1

I just made a reproduction because I still had mismatches after using the recommended fix. Turns out it only happens if I am using the Components through your module. Importing the components explicitly fixes the mismatches. You can easily check by un-commenting the imports.

https://stackblitz.com/edit/github-bbxtfw?file=app.vue

i can confirm this issue!

P4sca1 commented

I can reproduce the issue, but did not yet find the reason for it. My guess right now is a symbol mismatch due to different imports, but I am not sure yet.
I will provide an update once I know more.

P4sca1 commented

I invested a few hours today, but was unable to find out why this is not working.
For some reason the provided useId function is called on the server, but not on the client. So on the client the IDs are still generated using -i suffixes, leading to hydration mismatches.
I also tried calling provideUseId in a plugin added by this module without success.

@P4sca1 yeah i also could the in the DOM that the ids with the -i suffixe where generated

adding this to app.vue fixed it for me, at least until we get a proper fix

import { provideUseId } from '@headlessui/vue'

provideUseId(() => useId())

Looking at the network logs, it looks like my import statement within app.vue is fetching Headless UI from _nuxt/node_modules/.cache/vite/client/deps/@headlessui_vue.js—which is fully bundled—but the component from /_nuxt/node_modules/@headlessui/vue/dist/components/menu/menu.js. Therefore, there are two separate, incompatible copies of the library included. I also noticed a Vite log that it was "optimizing dependencies" when I added that line to app.vue for the first time.

To fix it, exclude the library from being optimized by Vite:

vite: {
    optimizeDeps: {
        exclude: ['@headlessui/vue'],
    },
},

It is my understanding that dependencies are not pre-bundled during production at all (i.e. this is a dev only problem), so this should have no performance or size impact on the final build.

Interesting find. Because there are 2 different bundles, the symbols that get injected likely don't match, which is causing the issue. Thank you for posting a workaround. I will check if it makes sense to include it by default.

This issue is likely related vitejs/vite#3910 and mentions the same workaround.

nuxt-headssui version 1.2.0 exposes a new provideHeadlessUseId composable, which should fix the issue.
Add the following to your app.vue file in <script setup> (everything is auto-imported):

// Use SSR-safe IDs for Headless UI
provideHeadlessUseId(() => useId())

nuxt-headssui version 1.2.0 exposes a new provideHeadlessUseId composable, which should fix the issue. Add the following to your app.vue file in <script setup> (everything is auto-imported):

// Use SSR-safe IDs for Headless UI
provideHeadlessUseId(() => useId())

is this auto import ?
Screenshot 2024-06-02 at 08 31 07

@geofany Ensure you are using Nuxt 2.10 or newer. If you have auto imports disabled, import the composables manually.

can this be useful now: https://blog.vuejs.org/posts/vue-3-5#useid

Yes, but it needs to be integrated into @headlessui/vue directly.

If you upgrade vue to 3.5.0 or newer and @headlessui/vue to 1.7.23 or newer, nuxt-headlessui will automatically use the native useId function provided by vue. There is no need to update nuxt-headlessui. You can remove provideHeadlessUseId() from your app.vue function. If you keep it, it will just do nothing.