---
title: Next.js + Kit
seoTitle: Solana wallet integration with Next.js and @solana/kit
description:
  Set up Solana wallet integration in Next.js with @solana/kit and @solana/react
  to send SOL transfers.
---

Set up a minimal Solana wallet integration in Next.js (App Router) with
`@solana/kit`, the kit plugins, and `@solana/react`. You'll create a connect
wallet dropdown and a SOL transfer component.

![Next.js Kit App](/assets/docs/frontend/01-hero-wallet.webp)

## Resources

- [Solana Kit](https://github.com/anza-xyz/kit)
- [Kit plugins](https://github.com/anza-xyz/kit-plugins)
- [Solana JSON RPC](/docs/rpc)

## Prerequisites

- Node 20+
- npm

## Create Next.js Project

```terminal
$ npx create-next-app@latest my-app
$ cd my-app
```

When prompted, accept all defaults (the starter includes Tailwind, which this
tutorial uses for simple utility styles).

<Callout type="info" title="Two scaffold tweaks">
The default `create-next-app` output needs two small changes for this tutorial:

- In `tsconfig.json`, set `"target": "ES2020"` (or later). The transfer amounts
  use `bigint` literals such as `1_000_000_000n`, which require ES2020.
- In `app/globals.css`, remove the generated
  `@media (prefers-color-scheme: dark)` block and the `body` background rule.
  This UI is light-only; the dark defaults otherwise override the `bg-white`
  utility and render the page unreadable in dark mode.

</Callout>

### Install Solana dependencies

```terminal
$ npm install @solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-wallet @solana/react @solana-program/system
```

This tutorial targets Kit 7+, the `@solana/kit-plugin-*` packages at 0.13+
(0.14+ for `@solana/kit-plugin-wallet`), and `@solana/react` 7+.

<ScrollyCoding>

## !!steps 1. Create Solana Provider

`app/providers.tsx` builds one client at module scope and publishes it with
`ClientProvider`. The `walletSigner` plugin makes the connected wallet the fee
payer and identity; export the client type so components can type `useClient`.

```tsx !! title="app/providers.tsx"
"use client";

import { createClient } from "@solana/kit";
import { solanaRpc } from "@solana/kit-plugin-rpc";
import { walletSigner } from "@solana/kit-plugin-wallet";
import { ClientProvider } from "@solana/react";

const rpcUrl =
  process.env.NEXT_PUBLIC_SOLANA_RPC_URL ?? "https://api.devnet.solana.com";

export const client = createClient()
  .use(walletSigner({ chain: "solana:devnet" }))
  .use(solanaRpc({ rpcUrl }));

export type AppClient = Awaited<typeof client>;

export default function Providers({ children }: { children: React.ReactNode }) {
  return <ClientProvider client={client}>{children}</ClientProvider>;
}
```

## !!steps 2. Update Layout

Wrap your application with the `Providers` component. Update `app/layout.tsx` to
import and use it:

```tsx !! title="app/layout.tsx"
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Providers from "./providers";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"]
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"]
});

export const metadata: Metadata = {
  title: "Solana App",
  description: "Solana wallet integration with Next.js"
};

export default function RootLayout({
  children
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased bg-white`}
      >
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

This wraps the entire app with the Solana context so all child components can
access the client and wallet hooks.

## !!steps 3. Wallet Connect Button

Dropdown to connect/disconnect Wallet Standard wallets. `useWallets` returns the
wallets discovered in the browser; `useConnect` / `useDisconnect` return an
`ActionResult` — this component uses its `dispatch`, `isRunning`, and `error`
fields. Every wallet hook takes the client as its first argument, so pull it
from `useClient<AppClient>()` first.

```tsx !! title="app/components/wallet-connect-button.tsx"
"use client";

import { useState } from "react";
import {
  useConnect,
  useConnectedWallet,
  useDisconnect,
  useWallets,
  useWalletStatus
} from "@solana/kit-plugin-wallet/react";
import { useClient } from "@solana/react";
import type { AppClient } from "../providers";

function truncate(address: string) {
  return `${address.slice(0, 4)}…${address.slice(-4)}`;
}

export function WalletConnectButton() {
  const client = useClient<AppClient>();
  const status = useWalletStatus(client);
  const wallets = useWallets(client);
  const connected = useConnectedWallet(client);
  const connect = useConnect(client);
  const disconnect = useDisconnect(client);
  const [open, setOpen] = useState(false);

  const address = connected ? String(connected.account.address) : null;
  const error = connect.error ?? disconnect.error;

  return (
    <div className="relative">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="inline-flex w-full items-center justify-between gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-semibold text-slate-900 shadow-sm transition"
      >
        {address ? (
          <span className="font-mono">{truncate(address)}</span>
        ) : (
          <span>Connect wallet</span>
        )}
        <span className="text-xs text-slate-500">{open ? "▲" : "▼"}</span>
      </button>

      {open ? (
        <div className="absolute z-10 mt-2 w-full min-w-[240px] rounded-xl border border-slate-200 bg-white p-3 shadow-lg">
          {connected ? (
            <div className="space-y-3">
              <div className="rounded border border-slate-100 bg-slate-50 px-3 py-2">
                <p className="text-xs font-semibold uppercase tracking-wide text-slate-600">
                  Connected
                </p>
                <p
                  className="font-mono text-sm text-slate-900"
                  title={address ?? ""}
                >
                  {address ? truncate(address) : ""}
                </p>
              </div>
              <button
                type="button"
                onClick={() => {
                  void disconnect.dispatch();
                  setOpen(false);
                }}
                className="w-full rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50"
              >
                Disconnect
              </button>
            </div>
          ) : (
            <div className="space-y-2">
              <p className="text-xs font-semibold uppercase tracking-wide text-slate-600">
                Wallet Standard
              </p>
              <div className="space-y-1.5">
                {wallets.length === 0 ? (
                  <p className="text-sm text-slate-500">No wallets detected.</p>
                ) : (
                  wallets.map((wallet) => (
                    <button
                      key={wallet.name}
                      type="button"
                      disabled={connect.isRunning || status === "pending"}
                      onClick={() => {
                        void connect.dispatch(wallet);
                        setOpen(false);
                      }}
                      className="flex w-full items-center justify-between rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50 disabled:opacity-50"
                    >
                      <span>{wallet.name}</span>
                      <span className="text-xs text-slate-500">Connect</span>
                    </button>
                  ))
                )}
              </div>
            </div>
          )}
          {error ? (
            <p className="mt-2 text-sm font-semibold text-red-600">
              {error instanceof Error ? error.message : String(error)}
            </p>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}
```

![Wallet dropdown options](/assets/docs/frontend/02-wallet-dropdown-options.webp)

![Wallet dropdown connected](/assets/docs/frontend/03-wallet-dropdown-connected.webp)

## !!steps 4. SOL Transfer

Build a transfer instruction with `@solana-program/system` and send it through
`client.sendTransaction`. The connected wallet signs and pays.

```tsx !! title="app/components/sol-transfer-card.tsx"
"use client";

import { address, lamports } from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import { useClient } from "@solana/react";
import { useConnectedWallet } from "@solana/kit-plugin-wallet/react";
import { useState } from "react";
import type { AppClient } from "../providers";

const LAMPORTS_PER_SOL = 1_000_000_000n;

function parseLamports(input: string) {
  const sol = Number(input);
  if (!Number.isFinite(sol) || sol <= 0) return null;
  const whole = Math.trunc(sol);
  const frac = sol - whole;
  const amount =
    BigInt(whole) * LAMPORTS_PER_SOL +
    BigInt(Math.round(frac * Number(LAMPORTS_PER_SOL)));
  return amount > 0n ? amount : null;
}

export function SolTransferCard() {
  const client = useClient<AppClient>();
  const connected = useConnectedWallet(client);
  const [destination, setDestination] = useState("");
  const [amount, setAmount] = useState("0.001");
  const [signature, setSignature] = useState<string | null>(null);
  const [isSending, setIsSending] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const statusText = connected ? "Wallet connected" : "Wallet disconnected";

  async function sendSol() {
    if (!connected?.signer) {
      setError("Connect a wallet first.");
      return;
    }
    const amountLamports = parseLamports(amount);
    if (!amountLamports) {
      setError("Enter an amount greater than 0.");
      return;
    }
    const dest = destination.trim();
    if (!dest) {
      setError("Enter a destination address.");
      return;
    }
    setError(null);
    setIsSending(true);
    try {
      const transfer = getTransferSolInstruction({
        source: connected.signer,
        destination: address(dest),
        amount: lamports(amountLamports)
      });
      const result = await client.sendTransaction([transfer]);
      setSignature(result.context.signature);
      setAmount("0.001");
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to send SOL");
    } finally {
      setIsSending(false);
    }
  }

  return (
    <section className="space-y-4 rounded-xl border border-slate-200 bg-white p-5 shadow-sm">
      <div className="space-y-1">
        <p className="text-xs font-semibold uppercase tracking-wide text-slate-600">
          SOL Transfer
        </p>
        <h2 className="text-xl font-semibold text-slate-900">
          Send SOL with the connected wallet
        </h2>
        <p className="text-sm text-slate-600">
          Uses the connected signer as the fee payer and authorizes the
          transfer.
        </p>
      </div>
      <div className="space-y-2">
        <label
          className="text-sm font-semibold text-slate-800"
          htmlFor="destination"
        >
          Destination address
        </label>
        <input
          id="destination"
          value={destination}
          onChange={(event) => setDestination(event.target.value)}
          placeholder="Destination wallet address"
          className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
        />
      </div>
      <div className="space-y-2">
        <label
          className="text-sm font-semibold text-slate-800"
          htmlFor="amount"
        >
          Amount (SOL)
        </label>
        <input
          id="amount"
          value={amount}
          onChange={(event) => setAmount(event.target.value)}
          type="number"
          min="0"
          step="0.001"
          className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
        />
      </div>
      <div className="flex flex-wrap items-center justify-between gap-3">
        <p className="text-sm text-slate-600">Status: {statusText}</p>
        <button
          type="button"
          onClick={() => void sendSol()}
          disabled={!connected?.signer || isSending}
          className="rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700 disabled:cursor-not-allowed disabled:opacity-50"
        >
          {isSending ? "Sending…" : "Send SOL"}
        </button>
      </div>
      {signature ? (
        <div className="rounded border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-900">
          <p className="font-semibold">Transfer sent</p>
          <a
            className="text-sky-700 underline"
            href={`https://explorer.solana.com/tx/${signature}?cluster=devnet`}
            target="_blank"
            rel="noreferrer"
          >
            View on Solana Explorer →
          </a>
        </div>
      ) : null}
      {error ? (
        <p className="text-sm font-semibold text-red-600">{error}</p>
      ) : null}
    </section>
  );
}
```

![SOL transfer form filled](/assets/docs/frontend/04-sol-transfer-filled.webp)

![SOL transfer success](/assets/docs/frontend/05-sol-transfer-success.webp)

Wallets come from Wallet Standard discovery; once connected, the SOL transfer
uses the connected signer as the fee payer.

## !!steps 5. Page

`app/page.tsx` renders the wallet connect and SOL transfer components:

```tsx !! title="app/page.tsx"
import { SolTransferCard } from "./components/sol-transfer-card";
import { WalletConnectButton } from "./components/wallet-connect-button";

export default function HomePage() {
  return (
    <main className="mx-auto flex max-w-5xl flex-col gap-6 px-5 py-10">
      <header className="space-y-3">
        <p className="text-xs font-semibold uppercase tracking-wide text-slate-600">
          @solana/kit + Next.js
        </p>
        <h1 className="text-3xl font-bold text-slate-900">
          Solana wallet + SOL transfer
        </h1>
        <p className="max-w-3xl text-base text-slate-700">
          Connect a Wallet Standard wallet and send a SOL transfer using the
          connected signer.
        </p>
      </header>
      <section className="space-y-3 rounded border border-slate-200 bg-white p-4">
        <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
          <div className="space-y-1">
            <p className="text-xs font-semibold uppercase tracking-wide text-slate-600">
              Wallet
            </p>
            <p className="text-sm text-slate-700">
              Pick a Wallet Standard connector.
            </p>
          </div>
          <div className="sm:min-w-[240px]">
            <WalletConnectButton />
          </div>
        </div>
      </section>
      <SolTransferCard />
    </main>
  );
}
```

## !!steps 6. Run the Application

```terminal
$ npm run dev
```

Open http://localhost:3000, connect a Devnet wallet, and send a SOL transfer.

</ScrollyCoding>
