Next.js
Add VoiceInput to an existing Next.js App Router application. The server issues temporary credentials, and the browser streams audio directly to the provider.
Install
Section titled “Install”npm install @voiceinput/react @voiceinput/openaipnpm add @voiceinput/react @voiceinput/openaiAdd the provider key and exact application origin to .env.local:
OPENAI_API_KEY=your-openai-api-keyAPP_ORIGIN=http://localhost:3000Create the token route
Section titled “Create the token route”import { createOpenAITokenHandler } from "@voiceinput/openai/server";import { getCurrentUser } from "@/lib/auth"; // your existing session check
export const runtime = "nodejs";const appOrigin = new URL(process.env.APP_ORIGIN!).origin;
export const POST = createOpenAITokenHandler({ apiKey: process.env.OPENAI_API_KEY!, authorize: async (request) => { if (request.headers.get("origin") !== appOrigin) return null; const user = await getCurrentUser(request); return user ? { subject: user.id } : null; },});Returning null produces a 401. Use the
authentication and rate-limit recipes to connect
your session library, reject cross-site requests, and add a shared quota.
Add a field
Section titled “Add a field”Pass the provider directly for a self-contained field:
"use client";
import { useState } from "react";import { openai } from "@voiceinput/openai";import { getVoiceInputErrorMessage, useVoiceInput } from "@voiceinput/react";
const provider = openai({ tokenEndpoint: "/api/voice-token" });
export function Composer() { const [message, setMessage] = useState(""); const voice = useVoiceInput({ provider, value: message, onValueChange: setMessage, }); const active = voice.status !== "idle" && voice.status !== "error";
return ( <form> <textarea aria-label="Message" ref={voice.targetRef} value={message} onChange={(event) => setMessage(event.currentTarget.value)} /> <button {...voice.getTriggerProps()}>{active ? "Stop" : "Speak"}</button> {voice.error ? ( <p role="alert">{getVoiceInputErrorMessage(voice.error)}</p> ) : null} </form> );}Start the development server, visit the page, and press Speak. Check the troubleshooting guide if the field stays empty.
Share configuration across fields
Section titled “Share configuration across fields”Context is optional. Use it when several fields share a provider and need to coordinate microphone ownership:
"use client";
import { openai } from "@voiceinput/openai";import { VoiceInputProvider } from "@voiceinput/react";import type { ReactNode } from "react";
const provider = openai({ tokenEndpoint: "/api/voice-token" });
export function Providers({ children }: { children: ReactNode }) { return ( <VoiceInputProvider provider={provider}>{children}</VoiceInputProvider> );}Mount Providers from the root layout. Descendant hooks can then omit their
provider option. See the React API for
ready-made fields and shared recording options.
Deployment checklist
Section titled “Deployment checklist”- Serve the application over HTTPS and keep the token endpoint same-origin.
- Protect the route with your application session and an exact origin check.
- Store quota state in shared durable storage for multi-instance deployments.
- Keep provider keys server-only and import
/serverentries only on the server. - Allow the provider WebSocket origin in CSP
connect-src; see the CSP guide. - Avoid logging credentials, audio, transcripts, or token request bodies.
To switch transcription services, choose another adapter in the provider guide; the field code stays the same.