Add a voice button to an existing Next.js textarea with OpenAI.
npm
npm install @voiceinput/react @voiceinput/openaipnpm
pnpm add @voiceinput/react @voiceinput/openaiCreate .env.local with your OpenAI key and the exact origin of your app:
OPENAI_API_KEY=your-openai-api-key
APP_ORIGIN=http://localhost:3000Keep OPENAI_API_KEY server-only. Never give it a NEXT_PUBLIC_ prefix.
import { createOpenAITokenHandler } from "@voiceinput/openai/server";
import { getCurrentUser } from "@/lib/auth"; // your existing session check
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 without issuing a credential. Replace
getCurrentUser with the session check already used by your app.
"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 (
<div>
<label htmlFor="message">Message</label>
<textarea
id="message"
ref={voice.targetRef}
value={message}
onChange={(event) => setMessage(event.currentTarget.value)}
/>
<button {...voice.getTriggerProps()}>{active ? "Stop" : "Speak"}</button>
<output aria-live="polite">{voice.status}</output>
{voice.error ? (
<p role="alert">{getVoiceInputErrorMessage(voice.error)}</p>
) : null}
</div>
);
}Render Composer from a page that your signed-in users can access.
Start the Next.js development server, open the app, and press Speak. Allow
microphone access and dictate into the textarea. Restart the server after
changing .env.local.
If the route returns 401, check the session and APP_ORIGIN. For other
failures, use the troubleshooting guide.
- Use the ready-made
VoiceTextarea. - Add your auth library with the authentication recipes.
- Protect the route with a durable rate limit.
- Run the full-stack examples.