Back to blog
July 9, 2026Engineering8 min read

Embed an AI-Built Dashboard in Next.js

Two ways to ship a Vibedasher dashboard in a Next.js app: paste one iframe snippet today, or eject the dashboard into your own code and wire it to the SDK's client.query().

V
Vibedasher Team
Developer Relations

Embed an AI-Built Dashboard in Next.js

You describe a dashboard, our AI builds it against a real ETL + query engine, and then you ship it. This post is the developer-first version of "then you ship it" — specifically inside a Next.js App Router app.

There are two doors. One works today. One is a developer preview. We'll be honest about which is which.

Door 1 — the iframe (works today)

The zero-code path. Publish a dashboard in Vibedasher, and you get a public URL. Drop it into any page — a Next.js component, a plain HTML file, a CMS block:

<iframe
  src="https://cloud.vibedasher.com/v/YOUR-DASHBOARD"
  width="100%"
  height="600"
  frameborder="0"
></iframe>

That's it. We host it, render it, and keep it fresh. No build step, no SDK, no keys. This exact snippet is running in production right now.

For private data, you don't expose the raw dashboard — you mint a short-lived signed token on your backend and pass it in the URL. The embed secret never touches the browser:

// server-side only — the embed secret never reaches the browser
import { signEmbedToken } from "@vibedasher/client/embed";

const token = signEmbedToken({
  embedSecret: process.env.VIBEDASHER_EMBED_SECRET, // per-API-key secret
  keyId: process.env.VIBEDASHER_API_KEY_ID,         // -> kid header
  accountSlug: "acme",                              // -> iss
  vizId: 1275,                                       // the dashboard to render
  filters: { tenant_id: "acme" },                    // ANDed into every query
  viewerId: "user_8842",                             // per-seat metering
  ttlSeconds: 600,                                   // 10 min TTL, max 86400
});
// -> HS256 JWT, drop it into the iframe URL:
// https://cloud.vibedasher.com/v/YOUR-DASHBOARD?embed_token=${token}

The filters claim is ANDed into every query the embed runs, and the browser can't widen it. That's your multi-tenant boundary. Run signEmbedToken in a Next.js route handler or Server Action — never in a client component.

Door 2 — eject the dashboard into your own code (developer preview)

The iframe is great until you want the dashboard to be your app — your components, your design system, your interactions, your routing. That's what eject is for.

Eject is a developer preview — not generally available yet. The shape below is real and is what the sample app runs; treat it as the model, not a stable public API. A hosted quickstart is coming.

Here's the idea. Your AI (Claude Code, Cursor) talks to the Vibedasher MCP, pulls the dashboard's source, and rebuilds it natively in your app. The charts, layout, and filter logic land as ordinary React files that eject verbatim. The only thing wired to us is the data call:

// Ejected into your app by your AI via the Vibedasher MCP.
// You own this file; it renders from our engine through the SDK.
import { Vibedasher } from "@vibedasher/client";
import { DATASET_IDS } from "./config";   // plural — joins supported

const client = new Vibedasher({ apiKey: process.env.VIBEDASHER_API_KEY });

// The one call shape every ejected chart uses:
const { columns, rows } = await client.query({
  datasetIds: DATASET_IDS,   // optional — omitted, the engine infers them from the SQL's aliases
  sql,                        // alias-only SQL, composed from your controls
  params,                     // { column: value } filters, bound server-side
});
// Bind by column type: number -> measure, date -> time axis, string -> category.

One method carries the whole contract: client.query({ sql, params }) in (datasetIds is optional — the engine infers the set from the aliases the SQL references), typed { columns, rows } out. Everything underneath — transport, result fetching, decoding, caching — is hidden.

Keep the API key on the server

In Next.js, run the query in a Server Component or route handler and pass the serializable result down as props. The API key is read from process.env.VIBEDASHER_API_KEY (note: no NEXT_PUBLIC_ prefix) so it never enters the client bundle:

// lib/vibedasher.ts  —  import 'server-only'
import { Vibedasher, type QueryResult } from "@vibedasher/client";

const client = new Vibedasher({ apiKey: process.env.VIBEDASHER_API_KEY! });

export async function loadDashboard(): Promise<QueryResult> {
  return client.query({ sql, params });   // datasetIds inferred from the SQL
}
// app/page.tsx  —  a Server Component
export const dynamic = "force-dynamic";

export default async function Page() {
  const { columns, rows } = await loadDashboard();
  return <Dashboard columns={columns} rows={rows} />; // client component renders
}

If you have no backend — a pure frontend — mint a short-TTL scoped token on a tiny route and construct a VibedasherEmbedClient({ token }) in the browser instead. It exposes the identical query(), so the rendering code doesn't care which door it came through.

Bind charts by column type, not by guessing

The result is typed. columns[].type is one of string | number | boolean | date | timestamp | json, and rows are objects keyed by column name. Bind by type and the recreate stays faithful:

  • first string (or date/timestamp) column → category / x-axis
  • first number column → measure / y-value
  • a date/timestamp column → temporal axis, chronological order

That's the fidelity trick: your AI reads columns[].type instead of sniffing typeof row[k], so the ejected charts render correctly on the first pass.

Which door should you use?

  • Ship analytics into an existing site in minutes, non-developers involved → iframe. It works today.
  • You're a developer who wants the dashboard to live inside your app, in your code → eject (preview). You own the frontend; we run the ETL and query engine, pay-as-you-go.

Same engine, two front doors.

Next steps

The iframe path is live now — publish a dashboard and paste the snippet. The eject path is a developer preview; a full quickstart is coming.

Read the docs at docs.vibedasher.com, or start building at cloud.vibedasher.com.

Share this article