Vue

Nuxt

Use the Vue component in a Nuxt app — client-only rendering, the stylesheet, and avoiding layout shift.

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
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:

nuxt.config.ts
export default defineNuxtConfig({
  css: ['@fft-visualizer/vue/style.css'],
})

Quick start

app.vue
<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.

Give the wrapper a height outside<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>
The canvas is a constructor argument, not a later init() call — the class has no such method, and connect() runs automatically on construction. See the API reference for the full surface.
Copyright © 2026