> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes. # SSR & Next.js `@signalwire/js` and `@signalwire/web-components` are **browser-only**. They depend on `WebSocket`, `RTCPeerConnection`, `navigator.mediaDevices`, and `customElements` — APIs that don't exist in Node.js. Importing either package at the top of a server-rendered module will crash the server during build or during SSR. Two boundaries to hold across any server-rendered framework: load the SDK only on the client, and mint tokens only on the server. The patterns below are Next.js–specific, but the same shape applies in Nuxt, SvelteKit, Remix, Astro, or Gatsby — wrap the SDK in that framework's client-only escape hatch, and put token minting behind a server route. ## Next.js App Router ### Client components Wrap any code that touches the SDK in a `"use client"` component. The import won't execute on the server. ```tsx // app/_components/dialer.tsx "use client"; import { useEffect, useState } from "react"; import { SignalWire, StaticCredentialProvider } from "@signalwire/js"; export function Dialer({ token }: { token: string }) { const [client, setClient] = useState(null); useEffect(() => { const c = new SignalWire(new StaticCredentialProvider({ token })); setClient(c); return () => c.disconnect(); }, [token]); // ... } ``` A `"use client"` file's transitive imports are fine — they're bundled for the browser, not Node. You don't need `dynamic()` for ordinary SDK use. ### When `dynamic()` is needed `next/dynamic` with `ssr: false` is only required when an import has side effects that run at *module evaluation time* (custom element registration, top-level `new SignalWire(...)`, etc.). The web components package registers `customElements.define` on import — so in the App Router, import the components from inside an effect or use `next/dynamic`: ```tsx // app/_components/widget-mount.tsx "use client"; import dynamic from "next/dynamic"; import { useEffect } from "react"; const RegisterWebComponents = dynamic( () => import("@signalwire/web-components").then(() => () => null), { ssr: false } ); export function Widget({ token }: { token: string }) { return ( <> ); } ``` For the **embed bundle** (`signalwire-web-components-embed.iife.js`) loaded via `