Self is not defined error using UI toolkit in NextJS

Im a using the Zoom Video SDK ui toolkit in my Next.js 14.2.4, that is using the app router and typescript.
I tried to completely copy the code from https://github.com/zoom/videosdk-ui-toolkit-react-sample , it worked in development server meaning I would be able to join calls and get the proper UI but I would get errors in terminal saying, this would stop me from pushing to github as Vercel would raise errors for self is not defined as well:

 ⨯ ReferenceError: self is not defined
    at __webpack_require__ (/Users/edison/notes/nextjs-dashboard/.next/server/webpack-runtime.js:33:43)
    at __webpack_require__ (/Users/edison/notes/nextjs-dashboard/.next/server/webpack-runtime.js:33:43)
    at eval (./app/telehealth/call/page.tsx:7:83)
    at (ssr)/./app/telehealth/call/page.tsx (/Users/edison/notes/nextjs-dashboard/.next/server/app/telehealth/call/page.js:348:1)
    at Object.__webpack_require__ [as require] (/Users/edison/notes/nextjs-dashboard/.next/server/webpack-runtime.js:33:43)
    at JSON.parse (<anonymous>)
digest: "3611744330"
 GET /telehealth/call 500 in 82ms

here is my code:

'use client';
import uitoolkit from "@zoom/videosdk-ui-toolkit";
import "@zoom/videosdk-ui-toolkit/dist/videosdk-ui-toolkit.css";

function App() {
  let sessionContainer: HTMLDivElement | null = null;
  // set your auth endpoint here 
  // a sample is available here: https://github.com/zoom/videosdk-auth-endpoint-sample
  const authEndpoint = "/api/getToken"; // http://localhost:4000
  const config = {
    videoSDKJWT: "",
    sessionName: "test",
    userName: "React",
    sessionPasscode: "123",
    features: ["video", "audio", "settings", "users", "chat", "share"],
    options: { init: {}, audio: {}, video: {}, share: {} },
    virtualBackground: {
      allowVirtualBackground: true,
      allowVirtualBackgroundUpload: true,
      virtualBackgrounds: ['https://images.unsplash.com/photo-1715490187538-30a365fa05bd?q=80&w=1945&auto=format&fit=crop']
    }
  };
  const role = 1;

  function getVideoSDKJWT() {
    sessionContainer = document.getElementById("sessionContainer") as HTMLDivElement;
    document.getElementById("join-flow")!.style.display = "none";
    fetch(authEndpoint, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ sessionName: config.sessionName, role: role, }),
    }).then((response) => {
      return response.json();
    }).then((data) => {
      if (data.signature) {
        console.log(data.signature);
        config.videoSDKJWT = data.signature;
        joinSession();
      } else {
        console.log(data);
      }
    }).catch((error) => {
      console.log(error);
    });
  }

  function joinSession() {
    console.log(config);
    if (sessionContainer) {
      uitoolkit.joinSession(sessionContainer, config);
      sessionContainer && uitoolkit.onSessionClosed(sessionClosed);
    }
  }

  const sessionClosed = () => {
    console.log("session closed");
    sessionContainer && uitoolkit.closeSession(sessionContainer);
    document.getElementById("join-flow")!.style.display = "block";
  };

  return (
    <div className="App">
      <main>
        <div id="join-flow">
          <h1>Zoom Video SDK Sample React</h1>
          <p>User interface offered by the Video SDK UI Toolkit</p>
          <button onClick={getVideoSDKJWT}>Join Session</button>
        </div>
        <div id="sessionContainer"></div>
      </main>
    </div>
  );
}

export default App;

I tried using dynamic from next/dynamic to import uitoolkit from “@zoom/videosdk-ui-toolkit” on the client side, that did remove the error from the terminal but also made all functionality stop working and I was no longer able to use that actual ui toolkit. So it was pointless.

The ReferenceError: self is not defined error is occurring because the Zoom Video SDK UI Toolkit is likely trying to access self, which is a browser-specific object not available in the server environment where Next.js also tries to execute the code. Next.js uses server-side rendering (SSR) by default, and libraries like the Zoom Video SDK often depend on browser-only APIs.

Here’s how you can adjust your code to make sure it only executes in the client environment:

  • Use next/dynamic with ssr: false: This ensures that the code only runs on the client side, skipping SSR entirely for this component.

  • Separate Toolkit Initialization Logic: Make sure to handle initialization logic only when the component is fully loaded on the client.

1 Like

This is the correct answer - even though it’s a client component it is rendered on the server, so the UI toolkit will need to be rendered after the page has loaded in the DOM.

Hey @edisonfreire14, here’s how you can import the SDK:

import dynamic from "next/dynamic";

const Videocall = dynamic<{ slug: string; JWT: string }>(
  () => import("../../../components/Videocall"),
  { ssr: false },
);

export default async function Page() {
  return (
      <Videocall slug={params.slug} JWT={jwt} />
  );
}

You can then use the SDK/UIToolkit in the Videocall component. You can find a real world Next.js example using the app router here.