Guides
Embed in Next.js
App Router client component + Next.js `<Script>` + workspace-to-npm portable copy-shim.
The runnable version of this guide lives at
examples/nextjs-embed.
App Router setup
npm i @gemmapod/embedAdd a predev / prebuild step that copies the IIFE into public/:
// scripts/copy-shim.mjs
import { copyFileSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const src = require.resolve("@gemmapod/embed/dist/gemmapod-shim.iife.js");
const destDir = join(process.cwd(), "public", "vendor");
mkdirSync(destDir, { recursive: true });
copyFileSync(src, join(destDir, "gemmapod-shim.iife.js"));// package.json
{
"scripts": {
"predev": "node scripts/copy-shim.mjs",
"prebuild": "node scripts/copy-shim.mjs"
}
}Mount in a client component
// app/Pod.tsx
"use client";
import Script from "next/script";
import { useEffect, useRef, useState } from "react";
export function Pod() {
const hostRef = useRef<HTMLDivElement | null>(null);
const [ready, setReady] = useState(false);
useEffect(() => {
if (!ready || !hostRef.current) return;
let killed = false;
let mounted: { destroy(): Promise<void> } | null = null;
(window as any).GemmaPod
.mountPod(hostRef.current, config)
.then((m: any) => {
if (killed) return void m.destroy();
mounted = m;
});
return () => {
killed = true;
mounted?.destroy();
};
}, [ready]);
return (
<>
<Script
src="/vendor/gemmapod-shim.iife.js"
strategy="afterInteractive"
onReady={() => setReady(true)}
/>
<div ref={hostRef} />
</>
);
}
const config = {
name: "Demo",
persona: "App Router pod.",
systemPrompt: "Be terse.",
model: "gemma4:e4b",
transport: {
dartc: { signalUrl: "wss://signal.gemmapod.com/signal", podId: "demo" },
fallback: { model: "onnx-community/gemma-4-E2B-it-ONNX" },
},
};Use it on a page
// app/page.tsx
import { Pod } from "./Pod";
export default function Home() {
return (
<main>
<h1>My agent</h1>
<Pod />
</main>
);
}Server components don't touch the runtime
The runtime is browser-only — it needs window, WebRTC, and
WebGPU. Always mount through a "use client" component. Server
components are safe to render around it.
CSP via next.config.mjs
export default {
async headers() {
return [
{
source: "/:path*",
headers: [
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'",
"connect-src 'self' wss: stun: https://huggingface.co https://*.hf.co https://cas-bridge.xethub.hf.co https://cdn.jsdelivr.net",
"img-src 'self' data: blob:",
"worker-src 'self' blob:",
"style-src 'self' 'unsafe-inline'",
].join("; "),
},
],
},
];
},
};What's next
- Headless mode — drop the Preact widget, render your own UI.
- Embed via
<script>tag — same config, no Next.js required.