@vidtreo/recorder-nuxt
@vidtreo/recorder-nuxt
The fastest way to ship browser-based video recording in a Nuxt 3 app. One line in nuxt.config.ts and you get a fully styled <VidtreoRecorder> component auto-imported, registered client-only (no SSR drama), with the recorder CSS injected into your global stylesheet. It's a thin, opinionated wrapper around @vidtreo/recorder-vue — same component, same composables, zero boilerplate.
What it does for you
- 🪄 Auto-imports
<VidtreoRecorder>(and the routing variants) globally — no manual import, no plugin file. - 📱 Client-only by default — recording is 100% a browser concern, so the components are registered with
mode: "client". No hydration errors, nowindow is not defined. - 🎨 CSS auto-injected — the recorder stylesheet is pushed into
nuxt.options.cssso it ships with your app's regular CSS bundle. - 🧩 Configurable prefix — rename the components if you already have something called
<VidtreoRecorder>in your code (rare, but supported). - 🛠️ Composables work too — the underlying
useVidtreoRecorder/useMobileWebRecorder/useNativeCamera/usePermissionFlow/useRecorderTranslationscomposables are importable from@vidtreo/recorder-vueexactly as they are outside Nuxt.
Installation
npm install @vidtreo/recorder-nuxt @vidtreo/recorder-vue @vidtreo/recorder
Peer dependencies:
nuxt >=3.0.0@vidtreo/recorder-vue >=1.9.0
Configure
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@vidtreo/recorder-nuxt"],
vidtreoRecorder: {
prefix: "Vidtreo", // default
injectCss: true, // default
},
});
| Option | Type | Default | Effect |
|---|---|---|---|
prefix |
string |
"Vidtreo" |
Prefix added to the auto-imported component names (VidtreoRecorder, VidtreoStandardRecorder, VidtreoMobileWebRecorderUI, VidtreoNativeCameraUI). |
injectCss |
boolean |
true |
Whether to push @vidtreo/recorder-vue/styles.css into nuxt.options.css. Disable if you want to inject the styles manually. |
Usage
Once the module is active, the components are globally available everywhere in your app — no imports needed in <script setup>. Wrap the component in <ClientOnly> only if you also render fallback content on the server.
<script setup lang="ts">
import type {
RecordingUploadResult,
VidtreoRecorderProps,
} from "@vidtreo/recorder-vue";
const recorderProps: VidtreoRecorderProps = {
apiKey: useRuntimeConfig().public.vidtreoApiKey,
lang: "en",
countdownDuration: 3,
enableSourceSwitching: true,
enableMute: true,
enablePause: true,
};
function onComplete(result: RecordingUploadResult) {
console.log("Uploaded:", result.recordingId, result.publicId);
}
</script>
<template>
<VidtreoRecorder
:recorder-props="recorderProps"
@upload-complete="onComplete"
@error="(err) => console.error(err)"
/>
</template>
Audio-only recording
Set audioOnly: true in recorder-props to record with the microphone only (requires audio recording enabled for the environment in the backend):
<template>
<VidtreoRecorder
:recorder-props="{ apiKey: useRuntimeConfig().public.vidtreoApiKey, audioOnly: true }"
/>
</template>
upload-complete then receives mediaType: "audio" and audioId. If audio recording is disabled for the environment, the compact audio bar shows an "Audio recording is not enabled for this environment" message and emits error with code recording.audio-disabled. See the @vidtreo/recorder-vue audio-only docs.
Runtime config (recommended for API keys)
Keep the key out of the client bundle by reading it from Nuxt's runtime config:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@vidtreo/recorder-nuxt"],
runtimeConfig: {
public: {
vidtreoApiKey: process.env.NUXT_PUBLIC_VIDTREO_API_KEY,
},
},
});
<script setup lang="ts">
const { vidtreoApiKey } = useRuntimeConfig().public;
</script>
<template>
<VidtreoRecorder :recorder-props="{ apiKey: vidtreoApiKey }" />
</template>
@upload-complete receives a RecordingUploadResult: every field of the backend upload result (id, publicId, filename, fileSize, mimeType, duration, status, transferStatus, …) plus the backward-compatible recordingId and uploadUrl. The payload is the same in desktop, mobile overlay and native camera mode.
Temporary access tokens
Instead of an API key, pass getAccessToken — a function that returns a short-lived token minted by your server (exactly one of apiKey / getAccessToken):
<script setup lang="ts">
async function getAccessToken(): Promise<string> {
const { token } = await $fetch<{ token: string }>("/api/vidtreo-token");
return token;
}
</script>
<template>
<VidtreoRecorder :recorder-props="{ getAccessToken }" />
</template>
The token is never stored. If an upload is interrupted (reload, lost connection), it is queued in IndexedDB and resumed the next time the recorder mounts with the same credentials; getAccessToken is called again at that point. This also applies to native camera mode.
Passing both apiKey and getAccessToken renders the configuration error "Provide either apiKey or getAccessToken, not both" and emits @error once with an error whose code is config.credential-conflict (check it with isCredentialConflictError from @vidtreo/recorder-vue).
Upload errors
When an upload fails because the account has no credits left, the recorder shows a dialog and @upload-error receives an error with the code upload.insufficient-credits:
<script setup lang="ts">
import {
getUploadErrorCode,
UPLOAD_ERROR_INSUFFICIENT_CREDITS,
} from "@vidtreo/recorder-vue";
function onUploadError(error: Error) {
if (getUploadErrorCode(error) === UPLOAD_ERROR_INSUFFICIENT_CREDITS) {
notifyAdmin();
}
}
</script>
<template>
<VidtreoRecorder
:recorder-props="{ apiKey: 'your-api-key' }"
@upload-error="onUploadError"
/>
</template>
The credit-rejected upload stays queued and is retried every 5 minutes, so @upload-error fires on each rejected attempt until the upload succeeds. The dialog and @error come only with the first rejection of each queued upload per page session. getUploadErrorCode returns null for other errors. Network errors are retried automatically, and the progress bar shows "Retrying upload..." meanwhile.
Demo mode
No backend, no API key — record locally and offer a download button:
<template>
<VidtreoRecorder :recorder-props="{ demo: true }" />
</template>
Custom UI via composables
Auto-imports don't include the composables (they're TS, not components). Import them explicitly from @vidtreo/recorder-vue:
<script setup lang="ts">
import { useVidtreoRecorder } from "@vidtreo/recorder-vue";
const { state, actions, audioLevel } = useVidtreoRecorder({
apiKey: useRuntimeConfig().public.vidtreoApiKey,
});
</script>
<template>
<ClientOnly>
<div>
<video v-if="state.stream" :srcObject="state.stream" autoplay muted playsinline />
<button @click="actions.startRecording()">Start</button>
<button @click="actions.stopRecording()">Stop</button>
<p>{{ state.timer }}</p>
</div>
</ClientOnly>
</template>
See the @vidtreo/recorder-vue README for the full composable API, state shape (including uploadPhase, uploadRetryAttempt, uploadErrorCode), actions (including clearUploadError), and i18n options — everything works identically inside Nuxt.
i18n
Pass lang ("en" or "es") and optional texts overrides in recorder-props. Progress keys include finishing, uploading, finalizing and retryingUpload; upload error keys include uploadInsufficientCreditsTitle, uploadInsufficientCredits and uploadErrorClose; mobile prompt keys include mobileHideRecordingPrompt and mobileShowRecordingPrompt; native camera keys include nativeCameraSelectVideo, nativeCameraRecordVideo, nativeCameraProcessing, nativeCameraConfigLoadError and nativeCameraPreviewAlt. The full table (identical to React and the Web Component) is in the @vidtreo/recorder-vue README, and the type is TranslationKeys exported by @vidtreo/recorder-vue.
<template>
<VidtreoRecorder
:recorder-props="{
apiKey,
lang: 'es',
texts: { retryingUpload: 'Reintentando…' },
}"
/>
</template>
Components registered by the module
| Auto-import name (default prefix) | Underlying Vue component |
|---|---|
<VidtreoRecorder> |
VidtreoRecorder (the routing entry — picks desktop / mobile-overlay / native automatically) |
<VidtreoStandardRecorder> |
StandardRecorder (desktop / embed UI) |
<VidtreoMobileWebRecorderUI> |
MobileWebRecorderUI (mobile overlay modal) |
<VidtreoNativeCameraUI> |
NativeCameraUI (file input + transcoding) |
With a custom prefix: "Foo" you'd get <FooRecorder>, <FooStandardRecorder>, etc.
SSR & <ClientOnly>
The components are registered with mode: "client", so Nuxt skips them on the server automatically — no need to wrap them in <ClientOnly> unless you also want to render placeholder content during SSR:
<template>
<ClientOnly>
<VidtreoRecorder :recorder-props="{ apiKey }" />
<template #fallback>
<div class="recorder-skeleton">Loading recorder…</div>
</template>
</ClientOnly>
</template>
Playground
The package ships with a Nuxt playground for local development:
cd packages/recorder-nuxt
bun install
bun run dev:prepare # generate types
bun run dev # starts the playground at http://localhost:3000
License
MIT
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.13.0 | ^3.0 | MIT | |
| 1.12.1 | ^3.0 | MIT | |
| 1.12.0 | ^3.0 | MIT | |
| 1.11.0 | ^3.0 | MIT | |
| 1.10.0 | ^3.0 | MIT | |
| 1.9.0 | ^3.0 | MIT | |
| 1.8.0 | ^3.0 | MIT | |
| 1.6.5 | ^3.0 | MIT | |
| 1.6.4 | ^3.0 | MIT | |
| 1.6.3 | ^3.0 | MIT | |
| 1.6.2 | ^3.0 | MIT | |
| 1.6.1 | ^3.0 | MIT | |
| 1.6.0 | ^3.0 | MIT |