Next.js Integration
Embed a tenant-scoped Emban dashboard in your Next.js app.
Integration split:
@emban/sdk stays server-side. In Next.js client components, prefer @emban/embed-helper/react for the signed iframe runtime. If you need native widget composition, return token, dashboard_id, and host from your route and use @emban/react.
Integration map
These four links cover the whole Next.js path: session minting on the server, runtime behavior in the browser, and a live proof surface.
Server
Signed session
Start with
/v1/embed-sessions and keep the admin key inside your route handler.
Browser
Embed Runtime
Understand the host-to-embed contract before you wire helper methods into a client component.
Proof
Live demo
Inspect the same signed session, command, and event flow against a real seeded dashboard.
Reference
API Reference
Keep endpoint payloads and browser command shapes nearby while you build the route and client pair.
1. API route: create embed session
This route should use an admin key on the server. Do not expose that key to the browser.
// app/api/analytics/route.ts
import { NextResponse } from 'next/server';
import { EmbanClient } from '@emban/sdk';
import { auth } from '@/lib/auth'; // your auth
const adminClient = new EmbanClient({
baseUrl: 'https://emban.sidelabs.dev',
apiKey: process.env.EMBAN_ADMIN_KEY!,
});
export async function GET() {
const session = await auth();
const dashboardId = process.env.EMBAN_DASHBOARD_ID!;
const embed = await adminClient.createEmbedSession({
tenantId: session.user.orgId,
dashboardId,
expiresIn: 3600,
permissions: {
allowedPeriods: ['24h', '7d', '30d'],
allowDrillDown: true,
},
});
return NextResponse.json({
embed_url: embed.embedUrl,
token: embed.token,
dashboard_id: dashboardId,
host: 'https://emban.sidelabs.dev',
});
}
2. Client component
// components/Analytics.tsx
'use client';
import { useEffect, useState } from 'react';
import { EmbanEmbedFrame } from '@emban/embed-helper/react';
export default function Analytics() {
const [url, setUrl] = useState('');
useEffect(() => {
fetch('/api/analytics').then(r => r.json()).then(d => setUrl(d.embed_url));
}, []);
if (!url) return <div>Loading analytics...</div>;
return (
<EmbanEmbedFrame
embedUrl={url}
containerStyle={{ minHeight: 600 }}
onFilter={event => console.log(event.period, event.filters)}
onPeriodChange={event => console.log(event.previousPeriod, event.period)}
onSessionSwapped={event => console.log(event.previousEmbedUrl, event.embedUrl)}
/>
);
}
3. Runtime controls in a client component
// components/AnalyticsRuntime.tsx
'use client';
import { useEmbanEmbed } from '@emban/embed-helper/react';
export function AnalyticsRuntime({ embedUrl }: { embedUrl: string }) {
const { containerRef, embed } = useEmbanEmbed({
embedUrl,
onReady: event => console.log('ready', event.dashboardId),
onDrill: event => console.log('drill', event.dimension, event.value, event.action),
onFilter: event => console.log('filter', event),
});
return (
<>
<div ref={containerRef} style={{ minHeight: 620 }} />
<button onClick={() => embed?.setFilters({ period: '7d' })}>Last 7 days</button>
<button onClick={() => embed?.reload()}>Reload</button>
</>
);
}
4. Commands and events
The helper methods above compile down to a small postMessage contract. If you want the browser-side model first, read Embed Runtime. This is the same payload shape exposed in the public live demo.
// Host page -> embed runtime
{
"_emban": true,
"type": "setFilters",
"filters": { "period": "7d" }
}
{
"_emban": true,
"type": "setTheme",
"theme": {
"primaryColor": "#1f4b99",
"backgroundColor": "#f6f8fc",
"cardBackground": "#ffffff",
"cardBorder": "#d7deea",
"textColor": "#111827",
"mutedColor": "#64748b"
}
}
{
"_emban": true,
"type": "reload"
}
// Embed runtime -> host page
{
"_emban": true,
"type": "ready",
"dashboardId": "dash_123"
}
{
"_emban": true,
"type": "filter",
"period": "7d",
"filters": { "endpoint": "/chat" },
"numericRanges": {},
"dateRanges": {}
}
{
"_emban": true,
"type": "period-change",
"previousPeriod": "30d",
"period": "7d"
}
{
"_emban": true,
"type": "drill",
"dimension": "endpoint",
"value": "/chat",
"action": "apply"
}
Host-side session rotation: the iframe runtime does not post
session-swapped by itself. That event is emitted by the helper when your Next.js client calls dash.setEmbedUrl(nextSignedUrl) after receiving a fresh tenant-scoped session from your route.5. Use in a page
// app/dashboard/page.tsx
import Analytics from '@/components/Analytics';
export default function DashboardPage() {
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Analytics</h1>
<Analytics />
</div>
);
}
6. Environment variables
# .env.local
EMBAN_ADMIN_KEY=eb_adm_your_key_here
EMBAN_DASHBOARD_ID=your_dashboard_id
Security: Never expose your API key to the browser. The embed session token is safe to expose — it's short-lived and scoped to one tenant.
Session refresh: if your app receives a new signed tenant-scoped session, keep the same helper instance and call
dash.setEmbedUrl(nextSignedUrl) instead of destroying and recreating the wrapper. The helper emits session-swapped when that happens.Native React path: if you need iframe-free composition, your route already has the values you need. Return
token, dashboard_id, and host to a client component and mount them with @emban/react.