Skip to main content
Next.js App Router renders Server Components by default. The JavaScript SDK runs in the browser, so you create the Dynamic client in a module, wrap the tree from a "use client" provider, and keep SDK hooks in client components.
The JavaScript SDK is headless. There is no built-in modal, wallet picker, or login form. You build all UI yourself. See the JavaScript SDK Overview for details.
Before this: a Next.js App Router app, a Dynamic environment ID from the Dynamic dashboard, and the packages from the React Quickstart (JS SDK) (@dynamic-labs-sdk/client, @dynamic-labs-sdk/evm, @dynamic-labs-sdk/react-hooks, @tanstack/react-query).

1. Create the Dynamic client module

Create the client once in a module you import from client components. Register extensions immediately after createDynamicClient, before initialization completes. Extension functions take no arguments. Do not pass the client instance. Use universalLink in metadata, not url. Guard window so the module can evaluate during SSR without throwing.
app/lib/dynamicClient.ts
Set NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID in .env.local. Add your app origin to Allowed Origins in the dashboard. For other chains, see Adding extensions. For client options, see Creating a Dynamic Client and Initializing the Dynamic Client.

2. Wrap the tree in client providers

layout.tsx can stay a Server Component. Providers that hold React state must live in a "use client" file. Mount QueryClientProvider outside DynamicProvider. Create the QueryClient with useState so it is not shared across server requests.
app/components/providers.tsx
Import the wrapper from the root layout:
app/layout.tsx
Do not import dynamicClient.ts from a Server Component. That evaluates the SDK on the server.

3. Use the SDK in client components

Any file that calls SDK hooks or client functions that need the browser must start with "use client". Gate UI on init before you read the user or wallets.
app/components/dashboard.tsx
Hooks used outside DynamicProvider throw MissingProviderError. Hooks used outside QueryClientProvider throw that TanStack Query is not set. See React Hooks.

4. Hydration

SDK session and wallet state exist only in the browser. If a component renders different markup on the server and on the first client render, React reports a hydration mismatch. Prefer gating on useInitStatus as in the dashboard example. If a subtree still mismatches, render it only after mount:
See Hydration failed for the general error. That page’s IsBrowser helper is from the legacy React SDK (@dynamic-labs/sdk-react-core). Do not import it in a JavaScript SDK app.

5. Bundler notes

You do not need asyncWebAssembly or other webpack WASM flags for @dynamic-labs-sdk/*. Embedded wallets load in a browser container, not as a WASM module you bundle yourself. Turbopack (next dev with Turbopack enabled) works with the same "use client" and provider setup. No extra WASM config. If you add WalletConnect and see Can't resolve 'encoding' (or pino-pretty / lokijs), follow Module not found. If another package in the same app requires WASM (for example a DeFi SDK), configure that package as its docs describe. Enabling asyncWebAssembly for those packages can break WaaS attestation modules in the Dynamic bundle. Prefer the other package’s recommended workaround rather than turning WASM on globally without checking.

Common pitfalls

Last modified on September 14, 2026