Skip to content

SSR / Nuxt Notes

Every component in @luxeanor/ui follows an SSR-safety discipline and works out of the box in Nuxt 3 / VitePress and other server-rendered environments (this docs site itself is built as SSG).

Guarantees inside the library

  • Browser APIs only appear in onMounted / event handlers / watch — module top-level and synchronous setup never touch window / document / localStorage
  • Overlay components (Modal / Drawer / Dropdown / Select / Tooltip / Toast / Confirm) use Teleport to body with a mounted guard (v-if="mounted"), so the server never renders overlay content — no hydration mismatch
  • useTheme returns defaults under SSR and never reads/writes DOM or storage

What consumers should handle

1. Server-side theme attribute consistency

If server-rendered HTML lacks data-theme / data-mode, the first frame flashes the default theme. Two options:

  • Fixed theme: hard-code the attributes in your server template / htmlAttrs (e.g. useHead({ htmlAttrs: { 'data-theme': 'ink', 'data-mode': 'light' } }))
  • Follow user preference: inline the themeBootScript in <head> so attributes land before CSS parses

2. How you consume the package

ScenarioModeExtra config
Regular project, npm i @luxeanor/uidist build (ESM + d.ts)none
Monorepo workspace:* sourceraw .vue/.ts sourceNuxt needs build.transpile: ['@luxeanor/ui']

3. Client-only logic

Visual enhancers like useMouseVars and useReveal rely on IntersectionObserver / pointer events — the library already guards the environment, so they're no-ops under SSR and need no <ClientOnly>. The only thing that genuinely needs <ClientOnly> is a component whose first frame depends on localStorage (e.g. LxThemeSwitcher placed in server-rendered layout).