Overview
Nuxt UI is built with server-side rendering (SSR) in mind and works seamlessly with Nuxt’s SSR capabilities. All components are designed to render correctly on both server and client.
SSR Compatibility
Nuxt UI components are fully compatible with SSR:
Components render on the server without hydration mismatches
Styles are extracted and delivered with the initial HTML
Icons are rendered server-side via @nuxt/icon
Color mode preferences are respected without flash
Localization works on both server and client
Color Mode SSR
The color mode module handles SSR gracefully:
< script setup >
const colorMode = useColorMode ()
// Safe to use in SSR
const isDark = computed (() => colorMode . value === 'dark' )
</ script >
Preventing Flash
The @nuxtjs/color-mode module prevents the “flash of incorrect theme” by:
Injecting a script in the HTML head
Reading preference from localStorage before render
Applying the correct class before hydration
The initial server render uses the fallback mode (default: 'light'). The correct mode is applied immediately during client-side hydration.
Icons in SSR
The @nuxt/icon module handles server-side icon rendering:
< template >
<!-- Renders correctly on both server and client -->
< UIcon name = "i-heroicons-check" />
</ template >
Icons are:
Rendered as SVG on both server and client
Cached for performance
Automatically optimized during build
Client-Only Components
Some features require client-side JavaScript. Use Nuxt’s <ClientOnly> wrapper:
< template >
< div >
<!-- Server-rendered content -->
< UCard title = "Welcome" >
< p > This renders on the server </ p >
</ UCard >
<!-- Client-only component -->
< ClientOnly >
< UModal v-model = " isOpen " >
<!-- Modals only render on client -->
</ UModal >
</ ClientOnly >
</ div >
</ template >
When to Use ClientOnly
Use <ClientOnly> for:
Components using browser APIs (window, document, etc.)
Third-party libraries without SSR support
Features that rely on client state
Client-Only Modal
Client-Only Command Palette
< ClientOnly >
<UModal v-model="isOpen">
<p>Modal content</p>
</UModal>
</ ClientOnly >
Composables in SSR
Nuxt UI composables work in SSR:
useToast
Toasts are client-only but the composable is SSR-safe:
< script setup >
const toast = useToast ()
// Safe to call in SSR (no-op on server)
function showNotification () {
toast . add ({
title: 'Success' ,
description: 'Operation completed'
})
}
</ script >
useLocale
Locale system works on both server and client:
< script setup >
const { t } = useLocale ()
// Translations work in SSR
const closeLabel = t ( 'modal.close' )
</ script >
useColorMode
Color mode composable is SSR-safe:
< script setup >
const colorMode = useColorMode ()
// Returns fallback value on server
const preference = colorMode . preference
</ script >
Forms work correctly in SSR:
< script setup >
import { z } from 'zod'
const schema = z . object ({
email: z . string (). email ()
})
const state = reactive ({
email: ''
})
// Form submission only happens on client
async function onSubmit ( event ) {
console . log ( 'Submit:' , event . data )
}
</ script >
< template >
<!-- Form renders on server, submission on client -->
< UForm : schema = " schema " : state = " state " @ submit = " onSubmit " >
< UFormField name = "email" label = "Email" >
< UInput v-model = " state . email " />
</ UFormField >
< UButton type = "submit" > Subscribe </ UButton >
</ UForm >
</ template >
Tailwind CSS in SSR
Tailwind CSS v4 (used by Nuxt UI) works seamlessly with SSR:
Styles are generated at build time
CSS is extracted and included in the HTML
No Flash of Unstyled Content (FOUC)
< template >
<!-- Styles apply immediately on server render -->
< div class = "bg-elevated text-foreground p-4 rounded-lg" >
Server-rendered styled content
</ div >
</ template >
Portal Components
Components using Teleport/Portal are client-only:
< template >
< div >
<!-- These components use portals internally -->
< ClientOnly >
< UModal v-model = " isOpen " />
< UDrawer v-model = " isDrawerOpen " />
< USlideover v-model = " isSlideoverOpen " />
</ ClientOnly >
</ div >
</ template >
Components that render in portals (Modal, Drawer, Slideover, Toast, etc.) should be wrapped in <ClientOnly> to prevent hydration mismatches.
Static Site Generation
Nuxt UI works with static site generation (SSG):
export default defineNuxtConfig ({
modules: [ '@nuxt/ui' ] ,
// Enable SSG
ssr: true ,
nitro: {
preset: 'static'
}
})
All components are pre-rendered at build time:
Hybrid Rendering
Use route rules for hybrid rendering:
export default defineNuxtConfig ({
modules: [ '@nuxt/ui' ] ,
routeRules: {
// Static pages
'/' : { prerender: true },
'/about' : { prerender: true },
// Server-rendered pages
'/dashboard/**' : { ssr: true },
// Client-only pages
'/admin/**' : { ssr: false }
}
})
Code Splitting
Nuxt automatically code-splits components:
< script setup >
// Lazy-load heavy components
const UEditor = defineAsyncComponent (() =>
import ( '@nuxt/ui/components/Editor.vue' )
)
</ script >
Tree Shaking
Only used components are included in the bundle:
export default defineNuxtConfig ({
modules: [ '@nuxt/ui' ] ,
ui: {
experimental: {
// Enable component detection for tree-shaking
componentDetection: true
}
}
})
Hydration Errors
Common Issues
Server/client content mismatch
<!-- ❌ Don't use Date.now() directly -->
< div > {{ Date.now() }} </ div >
<!-- ✅ Use onMounted or ClientOnly -->
< ClientOnly >
<div>{{ Date.now() }}</div>
</ ClientOnly >
Browser-only APIs
< script setup >
// ❌ Don't use window during setup
const width = window . innerWidth
// ✅ Use onMounted
const width = ref ( 0 )
onMounted (() => {
width . value = window . innerWidth
})
</ script >
Random values
<!-- ❌ Server and client generate different IDs -->
< div : id = " Math . random () " > Content </ div >
<!-- ✅ Use useId() composable -->
< div : id = " useId () " > Content </ div >
Best Practices
Wrap portals in ClientOnly
Always wrap Modal, Drawer, Slideover, and Toast in <ClientOnly>.
Use onMounted for browser APIs
Access window, document, etc. only in onMounted hooks.
Avoid dynamic values in SSR
Don’t use Date.now(), Math.random(), or other non-deterministic values in templates.
Test SSR builds
Run npm run build && npm run preview to test SSR behavior.
Debugging SSR Issues
Enable SSR debugging:
export default defineNuxtConfig ({
debug: true ,
vite: {
logLevel: 'info'
}
})
Check for hydration mismatches in browser console:
Hydration node mismatch:
- rendered on server: <div>
- expected on client: <span>
Learn More