> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/sdk/guides/nextjs-ssr.md).

# Next.js SSR

The SDK relies on browser APIs -- Web Workers, IndexedDB, and WebAssembly -- that are not available during server-side rendering. This guide covers the patterns you need to keep FHE operations on the client while still using Next.js App Router and SSR layouts.

## Steps

### 1. Understand the constraint

The FHE relayer runs encryption and decryption inside a Web Worker backed by a WASM binary. IndexedDB stores encrypted transport key pairs. None of these APIs exist in Node.js or during SSR.

This means:

* You cannot import `RelayerWeb`, `ZamaProvider`, or any SDK hook in a Server Component
* You cannot create the relayer or signer at module level in a file that runs on the server

### 2. Mark SDK components with `"use client"`

Any component that imports from `@zama-fhe/react-sdk` must be a Client Component:

```tsx
"use client";

import { useConfidentialBalance } from "@zama-fhe/react-sdk";
import { useAccount } from "wagmi";

export function TokenBalance({ tokenAddress }: { tokenAddress: string }) {
  const { address } = useAccount();
  const { data: balance, isLoading } = useConfidentialBalance({
    address: tokenAddress,
    account: address,
  });

  if (isLoading) return <span>Loading...</span>;
  return <span>{balance?.toString()}</span>;
}
```

### 3. Place `ZamaProvider` inside a client component

Create a dedicated client component that sets up the SDK providers. This keeps the relayer and signer initialization off the server.

```tsx
// app/providers.tsx
"use client";

import { WagmiProvider, createConfig, http } from "wagmi";
import { sepolia } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ZamaProvider } from "@zama-fhe/react-sdk";
import { web } from "@zama-fhe/sdk/web";
import { createConfig as createZamaConfig } from "@zama-fhe/react-sdk/wagmi";
import { sepolia as sepoliaFhe, type FheChain } from "@zama-fhe/sdk/chains";

const wagmiConfig = createConfig({ chains: [sepolia], transports: { [sepolia.id]: http() } });

const mySepolia = {
  ...sepoliaFhe,
  relayerUrl: "/api/relayer/11155111",
} as const satisfies FheChain;

const zamaConfig = createZamaConfig({
  chains: [mySepolia],
  wagmiConfig,
  relayers: { [mySepolia.id]: web() },
});

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <ZamaProvider config={zamaConfig}>{children}</ZamaProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}
```

### 4. Use the provider in your layout

The root layout is a Server Component by default. Import the client `Providers` wrapper and nest your pages inside it:

```tsx
// app/layout.tsx
import { Providers } from "./providers";

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

The layout file itself does not need `"use client"` -- it only imports a component that is already marked as a Client Component.

### 5. Avoid creating SDK objects in server components

A common mistake is initializing the relayer or signer in a shared module that gets imported by both server and client code:

```ts
// lib/sdk.ts — DO NOT do this
import { web } from "@zama-fhe/sdk/web";
import { createConfig } from "@zama-fhe/sdk/viem";

// This runs during SSR and crashes — Web Worker is not available
export const config = createConfig({ ... });
```

Instead, keep all SDK initialization inside a `"use client"` file (like the `Providers` component above), or gate it behind a dynamic import:

```ts
// lib/sdk.ts — safe alternative
export async function getConfig() {
  const { web } = await import("@zama-fhe/sdk");
  const { createConfig } = await import("@zama-fhe/sdk/viem");
  return createConfig({ ... });
}
```

### 6. Example: page with a confidential balance

Putting it all together -- a Next.js page that displays a confidential token balance:

```tsx
// app/portfolio/page.tsx (Server Component — no SDK imports here)
import { TokenBalance } from "@/components/token-balance";

export default function PortfolioPage() {
  return (
    <div>
      <h1>My Portfolio</h1>
      <TokenBalance tokenAddress="0xEncryptedERC20" />
    </div>
  );
}
```

```tsx
// components/token-balance.tsx (Client Component)
"use client";

import { useConfidentialBalance } from "@zama-fhe/react-sdk";
import { useAccount } from "wagmi";

export function TokenBalance({ tokenAddress }: { tokenAddress: string }) {
  const { address } = useAccount();
  const { data: balance, isLoading } = useConfidentialBalance({
    address: tokenAddress,
    account: address,
  });

  if (isLoading) return <span>Decrypting...</span>;
  return <span>{balance?.toString()}</span>;
}
```

The server renders the page shell, and the `TokenBalance` client component hydrates on the browser where FHE APIs are available.

## Next steps

* [ZamaProvider](/protocol/sdk/api-references/react/zamaprovider.md) -- all provider props and configuration
* [useConfidentialBalance](/protocol/sdk/api-references/react/useconfidentialbalance.md) -- balance hook API reference
* [Provider Setup](/protocol/sdk/guides/configuration.md) -- full examples for wagmi, viem, and ethers setups


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zama.org/protocol/sdk/guides/nextjs-ssr.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
