Kit-squared: @solana/kit in SvelteKit, no React needed
@solana/kit is framework-agnostic. Here's how to wire it into a SvelteKit app — RPC store, wallet store, reactive subscriptions, no React-only assumptions.
Most Solana JS tutorials assume React. @solana/kit deliberately doesn't — it's framework-agnostic functional code, no React adapters, no hooks. That makes it a clean fit for SvelteKit, where the reactivity model is the framework's job, not the SDK's.
This article is the integration pattern.
Why kit is the right base for SvelteKit
- No React. Classic web3.js was OK in Svelte but you always felt the React shape in its class-based API. Kit is just functions.
- SSR-safe. No hidden globals, no
windowassumptions. The RPC client works in server load functions and inonMount. - Tree-shakeable. SvelteKit's build dead-code-eliminates aggressively; kit's per-program packages let you ship a ~25KB Solana bundle.
- Bigint-everywhere. Svelte stores hold bigints without complaint; no
BN.jsoverhead.
RPC as a Svelte store
// src/lib/solana/rpc.ts
import { writable, readable, type Readable } from "svelte/store"
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
type Address,
type Slot,
} from "@solana/kit"
const HTTP = import.meta.env.PUBLIC_SOLANA_RPC ?? "https://api.mainnet-beta.solana.com"
const WS = HTTP.replace(/^http/, "ws")
export const rpc = createSolanaRpc(HTTP)
export const subs = createSolanaRpcSubscriptions(WS)
// A reactive store backed by an RPC subscription
export function slotStore(): Readable<Slot | null> {
return readable<Slot | null>(null, (set) => {
const ac = new AbortController()
;(async () => {
const notifications = await subs.slotNotifications().subscribe({ abortSignal: ac.signal })
for await (const n of notifications) set(n.slot)
})()
return () => ac.abort()
})
}
export function accountStore(address: Address) {
return readable<{ lamports: bigint; data: Uint8Array } | null>(null, (set) => {
const ac = new AbortController()
;(async () => {
// initial fetch
const { value } = await rpc.getAccountInfo(address).send()
set(value as any)
// subscribe to changes
const notifications = await subs.accountNotifications(address).subscribe({
abortSignal: ac.signal,
})
for await (const n of notifications) set(n.value as any)
})()
return () => ac.abort()
})
}Each store wraps a kit subscription in Svelte's store contract. The cleanup function fires when the last subscriber unsubscribes, which aborts the RPC subscription. No leaks.
Wallet adapter for Svelte
For Phantom / Solflare / Backpack connections in a SvelteKit app, use @wallet-standard/appdirectly — wallet standard is framework-agnostic, no React adapter required:
// src/lib/solana/wallet.ts
import { writable } from "svelte/store"
import { getWallets, type Wallet } from "@wallet-standard/app"
export const wallets = writable<Wallet[]>([])
export const selected = writable<Wallet | null>(null)
export const account = writable<{ address: string; publicKey: Uint8Array } | null>(null)
if (typeof window !== "undefined") {
const { get, on } = getWallets()
// Snapshot
wallets.set(get().filter((w) => w.chains.includes("solana:mainnet")))
// Stay in sync with wallet installs/removals
on("register", () => wallets.set(get()))
on("unregister", () => wallets.set(get()))
}
export async function connect(wallet: Wallet) {
const standardFeature = wallet.features["standard:connect" as const]
if (!standardFeature) throw new Error("wallet doesn't implement standard:connect")
const result = await (standardFeature as any).connect()
selected.set(wallet)
account.set(result.accounts[0])
}SSR-safe load functions
// src/routes/+page.server.ts
import { rpc } from "$lib/solana/rpc"
import { address } from "@solana/kit"
export async function load({ params }) {
// Runs on the server — no window, no wallet, just RPC
const balance = await rpc.getBalance(address(params.addr)).send()
return { addressBalance: balance.value }
}<!-- src/routes/+page.svelte -->
<script lang="ts">
import { slotStore } from "$lib/solana/rpc"
export let data
const slot = slotStore() // reactive store of current slot
</script>
<h1>Balance: {data.addressBalance} lamports</h1>
<p>Current slot: {$slot ?? "loading"}</p>Sending a tx from Svelte
<script lang="ts">
import { account, selected } from "$lib/solana/wallet"
import { rpc } from "$lib/solana/rpc"
import {
pipe, createTransactionMessage, setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction,
address, lamports,
} from "@solana/kit"
import { getTransferSolInstruction } from "@solana-program/system"
async function send() {
if (!$account || !$selected) return
const ix = getTransferSolInstruction({
source: $account.address as any,
destination: address("RecipientPubkey…"),
amount: lamports(10_000_000n),
})
const { value: bh } = await rpc.getLatestBlockhash().send()
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer($account!.address as any, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(bh, m),
(m) => appendTransactionMessageInstruction(ix, m),
)
// Hand the encoded message to the wallet to sign + send
const signFeature = $selected.features["solana:signAndSendTransaction"]
const result = await (signFeature as any).signAndSendTransaction({
account: $account,
transaction: /* encode message to wire format */ ,
chain: "solana:mainnet",
})
console.log("sig:", result[0].signature)
}
</script>
<button on:click={send}>Send 0.01 SOL</button>The case for SvelteKit + Solana
SvelteKit has aggressive bundle optimisation, real SSR (load functions, +page.server.ts), and a smaller runtime than Next.js. Combined with kit's tree shaking, the result is a Solana dapp that ships dramatically less JavaScript than the Next.js/React equivalent. For consumer apps where time-to- interactive matters — gaming, social, retail trading — that gap is real.
References
Kit + SvelteKit is the lightest Solana stack you can ship in 2026. The framework does reactivity, kit does Solana, neither carries assumptions about the other.