Bundle size and loading
Konekt separates the provider, chain adapters, read transport, authentication, and React UI into public entry points. Your app can ship only the parts it uses—and can delay the whole wallet stack until someone opens the connect flow.
Measured sizes
Section titled “Measured sizes”These are production bundle measurements from the repository’s pnpm size check:
| Import | Minified + gzip |
|---|---|
Provider + evm initial chunk |
10.00 kB |
| ChaCha20-Poly1305 lazy chunk | 4.84 kB |
| Ed25519/X25519 compatibility chunk | 13.80 kB |
| SHA-256/HKDF compatibility chunk | 2.96 kB |
http |
253 B |
siwe + cacaosOf |
844 B |
verifyCacao + checkClaims |
17.36 kB |
solana + solanaMainnet |
752 B |
WalletModal + useProviderPairing |
10.30 kB |
wagmi ConnectButton |
11.89 kB |
konekt-ui/styles.css |
2.98 kB |
The provider uses Web Crypto for Ed25519, X25519, SHA-256, and HKDF. Its initial row excludes the Noble compatibility chunks, which are loaded automatically only when a platform operation is unavailable. WalletConnect encryption uses a lazy ChaCha20-Poly1305 chunk because browsers do not standardize that cipher in Web Crypto; it loads on the first encrypted protocol message.
Current native secure-curve support starts with Chrome 137, Firefox 130, and Safari 18.4. Older runtimes continue to work through the compatibility chunk. Web Crypto requires a secure browser context such as HTTPS or localhost.
The check bundles the listed exports and their runtime dependencies with esbuild, minifies the result, and reports gzip transfer size. The UI rows include the QR encoder but exclude peer dependencies such as React, viem, and wagmi; those libraries may already be shared by the application. Each row is measured independently, so do not add the rows to predict an application bundle—your bundler can share and deduplicate modules.
Exact output varies with dependency and bundler versions. The committed lockfile and size configuration make the repository result reproducible and enforce limits:
pnpm sizeCompared in a real Vite app
Section titled “Compared in a real Vite app”The table above is each Konekt import on its own, without React. The headless path through the first encrypted message is 14.84 kB, the wallet modal and styles are 13.28 kB, and together they are a 28.11 kB connect stack. The numbers that show up in a browser are larger, and so is the gap versus the official stack, because @walletconnect/ethereum-provider and AppKit emit many extra chunks that package-main-bundle tools omit.
Four matched React apps in this repository each connect Ethereum and show an address. They share Vite, React 19, and the same tiny shell. react and react-dom are marked external, so the totals are the wallet stack. The only other difference is which wallet library each app imports:
| App | First load | Overall |
|---|---|---|
| WalletConnect | 145.74 kB | 538.06 kB |
| WalletConnect + AppKit | 721.26 kB | 1079.28 kB |
| Konekt | 11.03 kB | 33.76 kB |
| Konekt + UI | 19.06 kB | 45.52 kB |
packages/size-walletconnect—@walletconnect/ethereum-provider@2.23.10,showQrModal: falsepackages/size-appkit—@reown/appkit@1.8.23with the ethers adapter, email, socials, swaps, on-ramp, and analytics turned offpackages/size-konekt—Provider+evmpackages/size-konekt-ui— the same provider plusWalletModal
First load is the JavaScript and CSS the production index.html requests: the entry script, stylesheets, and modulepreloads. Overall is every JS, CSS, WASM, and font file Vite emitted. Each file is minified, gzipped at level 9, then summed.
Headless Konekt is 92.4% smaller on first load and 93.7% smaller overall than the official Ethereum Provider. Konekt with UI is 97.4% smaller on first load and 95.8% smaller overall than AppKit.
The Ethereum Provider still emits AppKit modal chunks as dynamic imports even with showQrModal: false, which is why its overall size is far above its first load. AppKit’s first load stays large because createAppKit() module-preloads wallet lists, email inputs, and related UI even when those features are disabled. The Konekt apps leave Noble compatibility chunks off the first load, the same way a modern-browser session would.
pnpm size:appsSee Why Konekt is better for the architectural comparison and Konekt UI for the direct UI feature comparison.
Let tree-shaking work
Section titled “Let tree-shaking work”The konekt package declares that its modules have no top-level side effects. konekt-ui marks only its CSS as side-effectful. A production ESM bundler can therefore remove exports and modules that are not reachable from your application.
Import from the narrow public entry point:
import { Provider } from "konekt";import { evm } from "konekt/eip155";Then add optional code only where it is needed:
import { http } from "konekt/http"; // Browser JSON-RPC reads through Providerimport { siwe } from "konekt/siwe"; // Authentication during pairingKeep these boundaries in mind:
- Do not import every adapter through a local “export everything” barrel.
- If viem or wagmi already handles public reads, omit
konekt/http. - Keep
konekt/cacaoon the server that verifies authentication. Importing it in browser code adds signature-verification code without creating a trustworthy browser-side check. - Import
konekt-ui/styles.cssonly when using the styled React components. - Check a production build. Development module counts and source-file sizes are not bundle sizes.
Lazy-load the provider
Section titled “Lazy-load the provider”If wallet state is not needed during the first render, load Konekt when the user opens the connect flow:
import type { Provider } from "konekt";
let providerPromise: Promise<Provider> | undefined;
async function initializeProvider() { const [{ Provider }, { ethereumMainnet }] = await Promise.all([ import("konekt"), import("konekt/eip155"), ]);
return Provider.init({ projectId: "YOUR_PROJECT_ID", metadata: { name: "My app", description: "Connect to My app", url: window.location.origin, icons: [new URL("/icon.png", window.location.origin).href], }, chains: [ethereumMainnet], });}
export function getProvider() { if (!providerPromise) { providerPromise = initializeProvider().catch((error) => { providerPromise = undefined; throw error; }); } return providerPromise;}The type-only import is erased from the browser output. Caching the promise prevents two quick clicks from creating competing initialization work, while clearing a rejected promise lets the user retry.
This changes behavior as well as loading time: a saved session is not restored until getProvider() runs. Initialize eagerly when the page must show the connected account immediately.
Chains and features must be present on the first Provider.init() call. The provider is a process singleton and later calls do not add options. To lazy-load SIWE, for example, import it inside initializeProvider() and pass it in that same call; do not try to attach it after initialization.
Lazy-load the React wallet UI
Section titled “Lazy-load the React wallet UI”Put wallet-only imports in their own component:
import type { Provider } from "konekt";import { useProviderPairing, WalletModal } from "konekt-ui";import "konekt-ui/styles.css";
export default function WalletDialog(props: { open: boolean; provider: Provider; onClose: () => void;}) { const { provider, ...modalProps } = props; const pairing = useProviderPairing(provider);
return <WalletModal {...modalProps} pairing={pairing} />;}Load that component only while it is visible:
import type { Provider } from "konekt";import { lazy, Suspense, useState } from "react";
const WalletDialog = lazy(() => import("./WalletDialog"));
export function WalletArea(props: { provider: Provider }) { const [open, setOpen] = useState(false);
return ( <> <button type="button" onClick={() => setOpen(true)}> Connect wallet </button> {open && ( <Suspense fallback={<p role="status">Loading wallet options…</p>}> <WalletDialog open provider={props.provider} onClose={() => setOpen(false)} /> </Suspense> )} </> );}Bundlers such as Vite can place the component JavaScript and CSS in lazy chunks. Keep a visible loading state: downloading code after a click without feedback makes the interface appear broken.
For wagmi, statically registering the konekt-ui/wagmi connector is still the recommended path. The connector dynamically imports Konekt inside getProvider(), so registration itself does not load the provider or open a relay socket. See the wagmi guide.
What to optimize first
Section titled “What to optimize first”- Keep server verification out of the browser.
- Avoid duplicate read clients: use either Konekt’s
readtransport or the viem/wagmi HTTP path when one is sufficient. - Lazy-load the connect flow when the initial page does not need restored wallet state.
- Measure the application’s production output, including shared React, viem, and wagmi chunks.
Lazy loading moves bytes to a later request; it does not reduce the total bytes needed after the user opens the wallet flow. Tree-shaking removes code that the application never uses.