Client only components with Vue 3
See original GitHub issueI wrote a little component that includes third party packages that cannot be rendered server side (midi-player-js
and soundfont-player
; they are using AudioContext and XMLHttpRequest). Following the docs the best way to solve this problem would be to use dynamic imports. So shouldn’t it be enough to just use defineAsyncComponent
because Vue wraps the component into a Promise?
<script setup>
// pages/song.page.vue
const MidiPlayer = defineAsyncComponent(() => import('../../components/MidiPlayer.vue'));
</script>
<template>
<MidiPlayer />
</template>
But if I use it like this the server still tries to render the component and throws XMLHttpRequest is not defined
when running npm run dev
. With a condition on the component to only render it in browser it still failes with AudioContext is not defined
since the component gets loaded in SSR even if it’s not displayed in the template.
<script setup>
// pages/song.page.vue
const isBrowser = typeof window !== 'undefined';
const MidiPlayer = defineAsyncComponent(() => import('../../components/MidiPlayer.vue'));
</script>
<template>
<MidiPlayer v-if="isBrowser" />
</template>
So I wrote a litte <ClientOnly>
component:
// components/ClientOnly.js
import { h } from 'vue';
const isBrowser = typeof window !== 'undefined';
export default {
setup(props, { slots }) {
const slot = slots.default ? slots.default() : [];
return () => (isBrowser ? h('div', {}, [slot]) : h('div'));
},
};
This seems to work better but I get an error: Hydration completed but contains mismatches.
Is there a way to prevent this from happening?
At the end the best way to do this for me was to add an additional component AsyncMidiPlayer
:
// components/AsyncMidiPlayer.js
import { defineAsyncComponent, h } from 'vue';
const isBrowser = typeof window !== 'undefined';
const MidiPlayer = defineAsyncComponent(() => import('./MidiPlayer.vue'));
export default {
setup(props, context) {
return () => isBrowser ? h(MidiPlayer, {...props,...context.attrs,}): h('div');
},
};
And to use it like this:
<script setup>
// pages/song.page.vue
import AsyncMidiPlayer from '../components/AsyncMidiPlayer.js';
</script>
<template>
<AsyncMidiPlayer />
</template>
But since I have multiple components that cannot be interpreted server side I would like to have a better solution than writing a custom wrapper for each of them. I was not able to create a generic version of this because the import source needs to be specific (I ran into The above dynamic import cannot be analyzed by vite.
). I’m sure there is a better solution for this. Did anyone find a better solution to this than me? It would be nice to extend the vite-plugin-ssr docs with a working vue 3 example.
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (5 by maintainers)
Top GitHub Comments
Updated:
Better?
I’ve used the ClientOnly component from vitepress with no issues ClientOnly.ts