Providers & App Router
Wire wallet and RPC providers in Next.js App Router without SSR crashes.
Recipe
// app/layout.tsx
import { SolanaProvider } from "./solana-provider";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SolanaProvider>{children}</SolanaProvider>
</body>
</html>
);
}// app/solana-provider.tsx
"use client";When to reach for this:
- Any Next.js 13+ App Router Solana dApp.
- Preventing
window is not definedfrom wallet SDKs. - Sharing connection state across routes.
Working Example
// app/solana-provider.tsx
"use client";
import { useMemo } from "react";
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { PhantomWalletAdapter, SolflareWalletAdapter } from "@solana/wallet-adapter-wallets";
import "@solana/wallet-adapter-react-ui/styles.css";
export function SolanaProvider({ children }: { children: React.ReactNode }) {
const endpoint = process.env.NEXT_PUBLIC_RPC_URL!;
const wallets = useMemo(
() => [new PhantomWalletAdapter(), new SolflareWalletAdapter()],
[]
);
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
}What this demonstrates:
- Client boundary wraps all wallet imports.
- Root layout stays a Server Component.
useMemostabilizes wallet adapter instances.
Deep Dive
How It Works
- Server Components render HTML without browser APIs.
- Client providers hydrate once at layout level.
- Nested routes inherit context automatically.
- Pair with kit
createSolanaRpcinside hooks for reads.
Gotchas
- Importing wallet in page.tsx without "use client" - build error. Fix: split presentational server shell + client island.
- Duplicate providers in nested layouts - double modals. Fix: single root SolanaProvider.
- Missing CSS import - broken modal styles.
- SSR prefetch calling wallet hooks - crash. Fix: dynamic import with ssr:false for heavy widgets if needed.
- Env not available at build - undefined RPC. Fix: validate in next.config or env schema.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| @wallet-ui/react provider | create-solana-dapp template | Existing adapter investment |
| Pages Router | Legacy app only | Greenfield App Router |
FAQs
App Router?
Providers in client layout.tsx; pages mix server + client components.
Secrets?
Server env only for keypairs; NEXT_PUBLIC_ for RPC URLs.
kit 7.0.0?
All examples use @solana/kit, not web3.js v1.
Wallet?
Signing only in client components.
Caching?
Revalidate on-chain reads carefully - chain is source of truth.
Actions?
HTTPS endpoints returning Solana Actions JSON.
Mobile?
Separate React Native app with MWA.
Testing?
Surfpool for integration; mock RPC in unit tests.
Mainnet?
Separate env files and program IDs.
Errors?
Decode simulation logs for user-facing messages.
Related
- dApp Integration Basics - structure
- Wallet Adapter - adapter setup
- Client-Side Signing - sign flows
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.