How do I add embedded analytics to a Next.js App Router app?

In a Next.js App Router app, put the ThoughtSpot Visual Embed SDK in a Client Component: a 'use client' file that calls init() once at the top, behind a typeof window check, and renders the embed. Mint authentication tokens in a Route Handler, and render the component from your Server Component page. Calling init() from a Server Component fails the build.

Checked end to end with Visual Embed SDK 1.53.1 on Next.js 16.4.2 and 15.5.28 (React 19.3.1), October 2026.

The Next.js App Router renders components on the server by default, where browser globals like window don't exist. A 'use client' file is a Client Component, which also runs in the browser. The ThoughtSpot Visual Embed SDK draws its iframe in the browser, so it belongs in a Client Component.

What should you check when you compare tools?

For a Next.js app, the test is how much glue code you'll own. With ThoughtSpot it's three files: a Client Component, a token route and your page. React components ship with the SDK (@thoughtspot/visual-embed-sdk/react), and in our tests, importing SDK 1.44.3 or later during server rendering doesn't throw.

Ask any vendor:

  • Does the SDK ship React components that work with server rendering?

  • How many files and services does it add to my app?

  • Can I run it in my own stack before I talk to sales?

What does the error look like?

Calling init() in a Server Component fails the build:

Error: Attempted to call init() from the server but init is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.

The same mistake also shows up as:

  • Error: Event handlers cannot be passed to Client Component props.

  • Error: `ssr: false` is not allowed with `next/dynamic` in Server Components.

  • ReferenceError: window is not defined on SDK 1.36.4 to 1.44.2, even inside a 'use client' component.

  • No iframe, and Error parsing ThoughtSpot host. Please provide a valid URL. in the console.

We reproduced each of these on Next.js 16.4.2 with React 19.3.0.

Why does it happen?

The SDK is a browser library, and since 1.37.0 it marks itself 'use client'. Inside a Server Component its exports are client references, so calling init() throws. The host error is about order: React runs a child's effects before its parent's, so if init() sits in a useEffect in the component that renders the embed, the embed starts first. The SDK's useInit hook fails the same way when you call it in the same component as the embed.

How do I fix it?

1. Create a Client Component that calls init() at module level.

// components/ThoughtSpotLiveboard.tsx
'use client';

import { init, AuthType } from '@thoughtspot/visual-embed-sdk';
import { LiveboardEmbed } from '@thoughtspot/visual-embed-sdk/react';

// Runs once, in the browser only, before any embed mounts.
if (typeof window !== 'undefined') {
  init({
    thoughtSpotHost: process.env.NEXT_PUBLIC_TS_HOST!, // e.g. https://your-instance.thoughtspot.cloud
    authType: AuthType.TrustedAuthTokenCookieless,
    autoLogin: true,
    getAuthToken: async () => {
      const r = await fetch('/api/ts-token');
      if (!r.ok) throw new Error(`Token request failed: ${r.status}`);
      const { token } = await r.json();
      return token;
    },
  });
}

export default function ThoughtSpotLiveboard({ liveboardId }: { liveboardId: string }) {
  return (
    <LiveboardEmbed
      liveboardId={liveboardId}
      frameParams={{ height: 800 }}
      onLiveboardRendered={() => console.log('Liveboard rendered')}
    />
  );
}

2. Mint the token in a Route Handler, so secret_key stays on the server:

// app/api/ts-token/route.ts
import { cookies } from 'next/headers';

// Server only: TS_SECRET_KEY never reaches the browser.
export async function GET() {
  const session = (await cookies()).get('session')?.value;
  if (!session) return new Response('Not signed in', { status: 401 });
  const username = await lookUpThoughtSpotUsername(session); // your own session lookup

  const res = await fetch(`${process.env.NEXT_PUBLIC_TS_HOST}/api/rest/2.0/auth/token/full`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({
      username,
      secret_key: process.env.TS_SECRET_KEY,
      validity_time_in_sec: 1800,
    }),
  });
  if (!res.ok) return new Response('Token request failed', { status: 502 });

  const { token } = await res.json();
  return Response.json({ token });
}

3. Render it from your page, which stays a Server Component:

// app/dashboard/page.tsx
import ThoughtSpotLiveboard from '@/components/ThoughtSpotLiveboard';

export default function DashboardPage() {
  return <ThoughtSpotLiveboard liveboardId="<LIVEBOARD_GUID>" />;
}

On SDK 1.36.4 to 1.44.2? Upgrade. If you can't, wrap the component with next/dynamic and ssr: false inside a Client Component, then import the wrapper in your page (Next.js lazy loading):

// components/ThoughtSpotLiveboardNoSSR.tsx
'use client';

import dynamic from 'next/dynamic';

export default dynamic(() => import('@/components/ThoughtSpotLiveboard'), { ssr: false });

How do I check it worked?

npm run build completes. In the browser, the Liveboard renders in an <iframe> pointing at your ThoughtSpot host, the Network tab shows /api/ts-token returning 200, and the console logs "Liveboard rendered". For local development, add your exact origin, such as https://localhost:3000, to both allowlists (Blog 2): any port that isn't listed is blocked by frame-ancestors.

Still failing?

  • "Event handlers cannot be passed to Client Component props". Move the on… handlers into the Client Component.

  • "Error parsing ThoughtSpot host". init() ran after the embed mounted, or NEXT_PUBLIC_TS_HOST was empty at build time.

  • "Received invalid token from getAuthToken callback or authToken endpoint". Check your token route's response in the Network tab.

  • "[vesdk-1.53.1] SSR environment detected" in the build log. Harmless: the SDK logs it while Next.js prerenders the page.

How do I explain this to my team?

Who

What they'll ask

What you can tell them

Engineering lead

How much code is this?

Three files: a Client Component, a token route and the page. The React components come with the SDK.

Security

Is any secret exposed to the browser?

No. The token route runs on the server; the browser only gets a short-lived token.

Proof you can forward: "With the ThoughtSpot software toolkit, we experienced a far less buggy embedding process compared to other tools. We finalized the infrastructure in a month, and the actual development only took six weeks." Aria Moshari, Director of Software Engineering, Verisk (Verisk case study).

How can I try this before I commit?

Start a free ThoughtSpot Embedded trial.

Related