E EMBAN / Docs

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.

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.