You are viewing Pre-Alpha documentation.
Vuetify0 Logo

Nuxt 3

v0 integrates with Nuxt 3 through standard Vue plugin registration. This guide covers SSR considerations, auto-imports, and theme persistence.

Edit this page
Report a Bug
Copy Page as Markdown

Basic Setup

See #Getting Started for the minimal plugin setup.

Auto-Imports

Configure Nuxt to auto-import v0 composables:

nuxt.config.ts
export default defineNuxtConfig({
  build: {
    transpile: ['@vuetify/v0'],
  },
  imports: {
    imports: [
      { from: '@vuetify/v0', name: 'useTheme' },
      { from: '@vuetify/v0', name: 'useSelection' },
      { from: '@vuetify/v0', name: 'useGroup' },
      { from: '@vuetify/v0', name: 'useSingle' },
      { from: '@vuetify/v0', name: 'useStep' },
      { from: '@vuetify/v0', name: 'usePagination' },
      { from: '@vuetify/v0', name: 'useForm' },
      { from: '@vuetify/v0', name: 'useHydration' },
      { from: '@vuetify/v0', name: 'useBreakpoints' },
      { from: '@vuetify/v0', name: 'IN_BROWSER' },
    ],
  },
})

SSR Considerations

The IN_BROWSER Constant

Guard browser-only code with IN_BROWSER:

ts
import { IN_BROWSER } from '@vuetify/v0'

if (IN_BROWSER) {
  localStorage.setItem('key', 'value')
  window.addEventListener('resize', handler)
}

Hydration State

Use useHydration to defer browser-only rendering:

vue
<script lang="ts" setup>
  import { useHydration } from '@vuetify/v0'

  const { isHydrated } = useHydration()
</script>

<template>
  <div v-if="isHydrated">
    <BrowserOnlyComponent />
  </div>
</template>

The hydration plugin:

  • Sets isHydrated to false during SSR
  • Flips to true after the root component mounts on client
  • Falls back gracefully if not installed

Theme SSR Integration

The theme plugin automatically integrates with Nuxt’s @unhead/vue to inject styles during SSR. No additional configuration required.

Theme Persistence

For theme preference to persist across SSR requests, use cookies instead of localStorage:

plugins/v0.ts
import { createHydrationPlugin, createThemePlugin, IN_BROWSER } from '@vuetify/v0'

export default defineNuxtPlugin((nuxtApp) => {
  const themeCookie = useCookie<'light' | 'dark'>('theme', {
    default: () => 'light',
  })

  function resolveTheme(): 'light' | 'dark' {
    if (themeCookie.value) return themeCookie.value
    if (!IN_BROWSER) return 'light'
    return window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark'
      : 'light'
  }

  nuxtApp.vueApp.use(createHydrationPlugin())
  nuxtApp.vueApp.use(
    createThemePlugin({
      default: resolveTheme(),
      themes: {
        light: {
          dark: false,
          colors: {
            primary: '#3b82f6',
            surface: '#ffffff',
            'on-primary': '#ffffff',
            'on-surface': '#212121',
          },
        },
        dark: {
          dark: true,
          colors: {
            primary: '#60a5fa',
            surface: '#1e1e1e',
            'on-primary': '#1a1a1a',
            'on-surface': '#e0e0e0',
          },
        },
      },
    }),
  )
})

To sync theme changes back to the cookie:

vue
<script setup lang="ts">
  import { useTheme } from '@vuetify/v0'
  import { watch } from 'vue'

  const theme = useTheme()
  const themeCookie = useCookie('theme')

  watch(() => theme.selectedId.value, (id) => {
    themeCookie.value = id
  })
</script>

SSR Compatibility Reference

FeatureSSR SupportNotes
ComponentsFullAll compound components work in SSR
useThemeFullAuto-injects styles via Nuxt head manager
useHydrationFullDesigned for SSR/client state sync
useBreakpointsPartialReturns defaults on server, measures on client
useStoragePartialUses memory adapter on server
usePaginationFullWidth-based calculation defers to client
Observer composablesPartialNo-op on server, activate on client

Common Patterns

Client-Only Components

For components that can’t render on the server:

vue
<template>
  <ClientOnly>
    <ComplexVisualization />

    <template #fallback>
      <div class="skeleton" />
    </template>
  </ClientOnly>
</template>

Avoiding Hydration Mismatch

Common causes:

  • Timestamps or random values during render
  • Conditional rendering based on browser state
  • Dynamic IDs without SSR-safe generation
vue
<script setup lang="ts">
  import { useHydration } from '@vuetify/v0'

  const { isHydrated } = useHydration()

  // Bad: causes mismatch
  const time = new Date().toLocaleTimeString()

  // Good: defer to client
  const time = computed(() => isHydrated.value ? new Date().toLocaleTimeString() : '')
</script>

© 2016-2025 Vuetify, LLC
Ctrl+/