From 9d51f735a44001b56a84f06fbdfd2c47e0f112da Mon Sep 17 00:00:00 2001 From: marinom2 Date: Wed, 29 Jul 2026 11:40:49 +0300 Subject: [PATCH] perf(wallet): load the wallet modal on demand, not on every page `createAppKit()` ran at module scope in components/providers.tsx, and app/layout.tsx renders that provider for every route. So anyone reading the landing page - or an operator on /onboard who has not gone near a wallet - downloaded, parsed and executed the entire wallet stack first: AppKit's UI, WalletConnect, and the Coinbase SDK that `enableCoinbase: false` disables at runtime but cannot remove from the bundle. Measured against production, that was a single 1,395 KB chunk - 53% of everything /onboard shipped. On a WebKitGTK desktop WebView at 3840x2160 it is several megabytes of JavaScript to compile before the page becomes interactive, which is what "the app feels heavy" turned out to mean. It was not the machine: hardware GL was fine and idle CPU was 0%. The modal now builds on the first openWallet() call (lib/appkit.ts), cached in a module-level promise so concurrent clicks share one download and createAppKit runs exactly once - calling it twice registers a second modal and the account state desynchronises. Connect CTAs prefetch on hover, so the chunk is usually warm before the click lands. Reading connection state no longer goes through AppKit. ConnectButton uses wagmi's useAccount/useChainId, which are already in the eager bundle because WagmiProvider still wraps the tree - so the button renders, and still shows a reconnected address after a reload, with AppKit absent. route before after /onboard 708 kB 316 kB -55% /build/bridge 752 kB 361 kB -52% /dashboard 313 kB 314 kB unchanged Verified: tsc clean, 673 tests pass, eslint clean, next build succeeds. NOT included: stubbing out @coinbase/wallet-sdk. It is reachable only through wagmi's dynamic connector loading, so a resolve.alias never intercepts it - the stub reached no chunk and total client JS did not move. It would have added risk to the connect path for no measurable gain. --- app/build/bridge/page.tsx | 7 +-- app/build/dao/voting-power-card.tsx | 5 +- components/connect-button.tsx | 19 +++--- components/providers.tsx | 38 ++++-------- lib/appkit.ts | 90 +++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 42 deletions(-) create mode 100644 lib/appkit.ts diff --git a/app/build/bridge/page.tsx b/app/build/bridge/page.tsx index 3b6d90e..88716be 100644 --- a/app/build/bridge/page.tsx +++ b/app/build/bridge/page.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from "react"; import Image from "next/image"; import { ChevronDown, ArrowLeftRight, CreditCard, Loader2, ExternalLink, CheckCircle2, AlertTriangle } from "lucide-react"; import { useAccount, useWalletClient, useSwitchChain, useChainId } from "wagmi"; -import { useAppKit } from "@reown/appkit/react"; +import { openWallet } from "@/lib/appkit"; import { createPublicClient, http, parseEther, type PublicClient } from "viem"; import { BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32 } from "lightnode-sdk"; import { CodeTabs } from "@/components/build/console/code-tabs"; @@ -134,7 +134,6 @@ async function resolveFeeParams(pub: PublicClient): Promise("eth-to-lc"); const [amount, setAmount] = useState(""); const [bal, setBal] = useState(null); @@ -245,7 +244,7 @@ export default function BridgePanel() { const executeBridge = async () => { if (!isConnected || !walletClient || !address) { - open(); + openWallet(); return; } const amt = validateAmount(); @@ -339,7 +338,7 @@ export default function BridgePanel() { {shortAddr(address)} ) : ( - )} diff --git a/app/build/dao/voting-power-card.tsx b/app/build/dao/voting-power-card.tsx index 32be3e1..0fd31f6 100644 --- a/app/build/dao/voting-power-card.tsx +++ b/app/build/dao/voting-power-card.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { ShieldCheck, AlertTriangle, ExternalLink, Wallet } from "lucide-react"; import { useAccount } from "wagmi"; -import { useAppKit } from "@reown/appkit/react"; +import { openWallet } from "@/lib/appkit"; import { short } from "@/components/build/console/panel-kit"; import { DAO_VOTE_UI, DELEGATION_UI, loadVotingPower, type DaoChain, type VotingPowerReads } from "./dao-chain"; import { delegationStatus, formatLcaiWei } from "./dao-math"; @@ -17,7 +17,6 @@ const SYMBOL: Record = { ethereum: "LCAIB", lightchain: "LCAI" */ export function VotingPowerCard({ chain }: { chain: DaoChain }) { const { address, isConnected } = useAccount(); - const { open } = useAppKit(); const [reads, setReads] = useState(null); const [loading, setLoading] = useState(false); // Stale-read guard: a slow read for the previous chain/account must not @@ -49,7 +48,7 @@ export function VotingPowerCard({ chain }: { chain: DaoChain }) { return ( ); @@ -53,7 +58,7 @@ export function ConnectButton({ size = "default" }: { size?: ButtonProps["size"] return (