Next.js
Where each key lives in an App Router project, and how to keep the secret server-side.
Next.js makes the key boundary easy to get right and easy to get wrong: anything a client component reads must be NEXT_PUBLIC_, and anything else must never be imported from one.
Widget in a client component
"use client";
import { useEffect } from "react";
import { mountWidget, type WidgetHandle } from "@oprag/sdk/widget";
export function Chat() {
useEffect(() => {
let handle: WidgetHandle | undefined;
let cancelled = false;
void mountWidget({
projectId: process.env.NEXT_PUBLIC_OPRAG_PROJECT_ID!,
embedKey: process.env.NEXT_PUBLIC_OPRAG_EMBED_KEY!,
apiUrl: process.env.NEXT_PUBLIC_OPRAG_API_URL!,
}).then((widget) => {
if (cancelled) return widget.destroy();
handle = widget;
});
return () => {
cancelled = true;
handle?.destroy();
};
}, []);
return null;
} Integration key in a route handler
import { createClient, OpragError } from "@oprag/sdk";
// This module is never bundled for the browser, so the secret stays put.
const oprag = createClient({
apiUrl: process.env.OPRAG_API_URL!,
apiKey: process.env.OPRAG_SECRET_KEY!,
});
export async function POST(request: Request) {
const { question, sessionId, visitorId } = await request.json();
try {
const res = await oprag.chat.ask({ question, sessionId, visitorId });
return Response.json(res);
} catch (err) {
if (err instanceof OpragError) {
return Response.json({ error: err.message }, { status: err.status });
}
throw err;
}
} Fronting the widget with your own route handler is the backend-for-frontend recipe.
Ready to ship?
Get started free