Skip to content

SSR & Nuxt

@borstihd/vue-custom-tooltip is fully server-side rendering (SSR) safe. The package can be imported and installed in a universal (server + client) context without touching document, window or any other browser-only global.

How it works

Tooltips are inherently interactive: they only become visible after a hover, focus or click. There is therefore nothing meaningful to render on the server, and the library takes advantage of that:

  • The trigger is server-rendered. In component mode the .tooltip-wrapper / .tooltip-trigger markup and your default slot are part of the SSR output, so layout and content stay intact.
  • The tooltip itself is client-only. The <Teleport to="body"> is rendered only after onMounted, which avoids both the document.body lookup on the server and hydration mismatches.
  • IDs are hydration-safe. ARIA IDs come from Vue's useId(), so server and client agree on the same value.
  • Theme CSS injection is a no-op on the server. injectThemeStyles() returns early outside the browser; the stylesheet is injected once the client takes over.
  • The directive ships getSSRProps. v-tooltip adds no attributes to the server-rendered markup and initializes its shared app lazily in the mounted hook.

You can query the environment yourself via the exported isClient flag:

ts
import { isClient } from '@borstihd/vue-custom-tooltip'

Nuxt setup

Create a universal plugin (no .client suffix needed):

ts
// plugins/tooltip.ts
import { VueCustomTooltip } from '@borstihd/vue-custom-tooltip'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(VueCustomTooltip, {
    theme: 'default',
    globalConfig: {
      position: 'auto',
      trigger: 'both',
      showDelay: 100,
    },
  })
})

Register the stylesheet globally in nuxt.config.ts:

ts
export default defineNuxtConfig({
  css: ['@borstihd/vue-custom-tooltip/style.css'],
})

Older path still works

@borstihd/vue-custom-tooltip/dist/style.css remains available for backwards compatibility.

That's it — use the component and the directive anywhere, including inside server-rendered pages:

vue
<template>
  <Tooltip content="Rendered on the server, shown on the client">
    <button>Hover me</button>
  </Tooltip>

  <button v-tooltip.top="'Works too'">
    Hover me
  </button>
</template>

Troubleshooting

document is not defined

Make sure you are on version 2.0.0 or newer. Earlier releases injected theme styles during app.use() without guarding for the server.

Nuxt cannot resolve the package

If your Nitro build complains about the ESM output, add the package to build.transpile:

ts
export default defineNuxtConfig({
  build: {
    transpile: ['@borstihd/vue-custom-tooltip'],
  },
})

<ClientOnly> is not required

You do not need to wrap tooltips in <ClientOnly>. Doing so would also remove the trigger element from the server-rendered HTML, which hurts SEO and causes layout shift.

Other SSR frameworks

The same guarantees apply to any Vue SSR setup (Vite SSR, Quasar SSR, custom renderToString servers). Install the plugin on the app instance you pass to renderToString():

ts
import { VueCustomTooltip } from '@borstihd/vue-custom-tooltip'
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
import App from './App.vue'

const app = createSSRApp(App)
app.use(VueCustomTooltip)

const html = await renderToString(app)