> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js App Router setup

> Use the JavaScript SDK in a Next.js App Router app: client boundaries, providers, and SSR.

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.

<Warning>
  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](/docs/javascript/overview) for details.
</Warning>

<Note>
  Before this: a Next.js App Router app, a Dynamic environment ID from the [Dynamic dashboard](https://app.dynamic.xyz), and the packages from the [React Quickstart (JS SDK)](/docs/javascript/reference/react-quickstart) (`@dynamic-labs-sdk/client`, `@dynamic-labs-sdk/evm`, `@dynamic-labs-sdk/react-hooks`, `@tanstack/react-query`).
</Note>

## 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.

```ts app/lib/dynamicClient.ts theme={"system"}
import { createDynamicClient, initializeClient } from '@dynamic-labs-sdk/client';
import { addEvmExtension } from '@dynamic-labs-sdk/evm';

const environmentId = process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID;

if (!environmentId) {
  throw new Error('NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID is not set');
}

export const dynamicClient = createDynamicClient({
  autoInitialize: false,
  environmentId,
  metadata: {
    name: 'My App',
    universalLink:
      typeof window !== 'undefined'
        ? window.location.origin
        : 'http://localhost:3000',
  },
});

addEvmExtension();

if (typeof window !== 'undefined') {
  void initializeClient();
}
```

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](/docs/javascript/reference/adding-extensions). For client options, see [Creating a Dynamic Client](/docs/javascript/reference/client/create-dynamic-client) and [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-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.

<Tabs>
  <Tab title="React hooks">
    ```tsx app/components/providers.tsx theme={"system"}
    'use client';

    import { useState } from 'react';
    import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
    import { DynamicProvider } from '@dynamic-labs-sdk/react-hooks';
    import { dynamicClient } from '@/lib/dynamicClient';

    export function Providers({ children }: { children: React.ReactNode }) {
      const [queryClient] = useState(() => new QueryClient());

      return (
        <QueryClientProvider client={queryClient}>
          <DynamicProvider client={dynamicClient}>{children}</DynamicProvider>
        </QueryClientProvider>
      );
    }
    ```
  </Tab>

  <Tab title="Client functions only">
    If you call `@dynamic-labs-sdk/client` functions directly and do not use hooks, you still need a client boundary so the module runs in the browser. You do not need `DynamicProvider` or `QueryClientProvider`.

    ```tsx app/components/providers.tsx theme={"system"}
    'use client';

    import '@/lib/dynamicClient';

    export function Providers({ children }: { children: React.ReactNode }) {
      return children;
    }
    ```
  </Tab>
</Tabs>

Import the wrapper from the root layout:

```tsx app/layout.tsx theme={"system"}
import { Providers } from './components/providers';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

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.

```tsx app/components/dashboard.tsx theme={"system"}
'use client';

import { useUser, useGetWalletAccounts, useInitStatus } from '@dynamic-labs-sdk/react-hooks';

export function Dashboard() {
  const { data: initStatus } = useInitStatus();
  const { data: user } = useUser();
  const { data: walletAccounts = [] } = useGetWalletAccounts();

  if (initStatus !== 'finished') {
    return <p>Loading…</p>;
  }

  if (!user) {
    return <p>Not signed in</p>;
  }

  return (
    <p>
      {user.email} · {walletAccounts[0]?.address}
    </p>
  );
}
```

Hooks used outside `DynamicProvider` throw `MissingProviderError`. Hooks used outside `QueryClientProvider` throw that TanStack Query is not set. See [React Hooks](/docs/javascript/reference/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:

```tsx theme={"system"}
'use client';

import { useEffect, useState } from 'react';

export function ClientOnly({ children }: { children: React.ReactNode }) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);
  if (!mounted) return null;
  return children;
}
```

See [Hydration failed](/docs/overview/troubleshooting/next/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](/docs/overview/troubleshooting/next/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

| Pitfall                                                     | What to do                                      |
| ----------------------------------------------------------- | ----------------------------------------------- |
| `createDynamicClient` inside a component or `useEffect`     | Keep a module-level singleton                   |
| `metadata.url`                                              | Use `metadata.universalLink`                    |
| `addEvmExtension(dynamicClient)`                            | Call `addEvmExtension()` with no arguments      |
| Hooks without both providers                                | `QueryClientProvider` outside `DynamicProvider` |
| Importing the client from `layout.tsx` (a Server Component) | Import it only from `"use client"` files        |
| Reading `user` / wallets before init finishes               | Wait for `useInitStatus().data === 'finished'`  |

## Related

* [React Quickstart (JS SDK)](/docs/javascript/reference/react-quickstart)
* [React Hooks](/docs/javascript/reference/react-hooks)
* [Creating a Dynamic Client](/docs/javascript/reference/client/create-dynamic-client)
* [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-dynamic-client)
* [Adding extensions](/docs/javascript/reference/adding-extensions)
* [Troubleshooting](/docs/overview/troubleshooting/general)
