Nuxt
The Vue component works in Nuxt with no module and no plugin. It is SSR-safe — every
browser API it touches (window, navigator, WebGL, AudioContext) is behind onMounted
— so importing it never breaks a server render. But it draws nothing on the server, so it
belongs inside <ClientOnly>.
Prefer to see it running first? The Nuxt playground is this exact setup on a prerendered page.
Install
pnpm add @fft-visualizer/vue
Then register the stylesheet in nuxt.config.ts — it sizes the canvas and styles the stats
overlay, and the component renders unstyled without it:
export default defineNuxtConfig({
css: ['@fft-visualizer/vue/style.css'],
})
Quick start
<script setup>
import { FFTVisualizer } from '@fft-visualizer/vue'
</script>
<template>
<div class="viz">
<ClientOnly>
<FFTVisualizer mode="local" :bands="40" gradient="aurora" />
</ClientOnly>
</div>
</template>
<style>
/* The component fills its container — give it a height */
.viz { width: 100%; height: 240px; }
</style>
mode="local" captures the microphone and computes the FFT in the browser, so this needs no
backend. The browser prompts for permission on mount.
<ClientOnly>, as above. If the sized element only
exists after hydration, the page reflows the moment the visualizer appears.Avoiding layout shift
<ClientOnly> renders nothing on the server, which leaves a gap until hydration. Its
#fallback slot fills that gap with server-rendered markup:
<ClientOnly>
<FFTVisualizer mode="local" :bands="40" />
<template #fallback>
<div class="viz-placeholder" />
</template>
</ClientOnly>
Hydration replaces the entire <ClientOnly> subtree, so anything you want to survive that
swap — a heading, a toolbar — belongs outside it, or in both branches.
Using the core directly
If you skip the component and drive @fft-visualizer/core yourself, construct it
in onMounted and guard any module-scope browser access with import.meta.client:
<script setup>
import { FFTVisualizer } from '@fft-visualizer/core'
const canvas = useTemplateRef('canvas')
let viz
onMounted(() => {
viz = new FFTVisualizer(canvas.value, { mode: 'local', bands: 40 })
})
onBeforeUnmount(() => viz?.destroy())
</script>
<template>
<canvas ref="canvas" />
</template>
init() call — the class has no such
method, and connect() runs automatically on construction. See the
API reference for the full surface.