Wagmi Hooks: Are Pre-Built React Wrappers Worth the Bloat?
The performance question behind wagmi hooks vs custom Viem integration is not whether one library is “faster.” It is where your application wants to pay for complexity: in JavaScript shipped to the…

The performance question behind wagmi hooks vs custom Viem integration is not whether one library is “faster.” It is where your application wants to pay for complexity: in JavaScript shipped to the browser, in runtime state management, or in engineering time spent rebuilding wallet and chain infrastructure.
A base React setup using Wagmi, Viem, and TanStack React Query has been measured at roughly 92.3 kB of gzipped First Load JS. That is not a catastrophic number for most applications, but it is also not the complete cost of a polished Web3 frontend. Add a wallet UI layer such as RainbowKit or Web3Modal, and the additional payload can become several times larger than the original integration.
Conversely, removing Wagmi does not make those responsibilities disappear. Wallet connection state, chain switching, transaction lifecycle tracking, cache invalidation, reconnect behavior, and multi-chain configuration still have to be implemented somewhere. The architectural trade-off is therefore more precise: do you want a stateful React abstraction that solves these problems consistently, or a smaller Viem-based integration whose control surface you own?
The architectural divide: stateful hooks versus stateless primitives
Wagmi and Viem occupy different layers of the stack. Treating them as interchangeable libraries leads to the wrong optimization decision.
Viem is a low-level, stateless TypeScript interface for Ethereum. Its primitives are designed to execute actions against a configured client: read contract data, simulate a transaction, prepare a write, retrieve logs, or submit a signed request. The library is tree-shakable and has a bundle size of approximately 35 kB.
That model is attractive when the application has a narrow execution path. A dashboard that reads a few contracts, a static product page with one transaction flow, or a specialized internal tool may not need a broad React state layer. The frontend can create a public client, obtain a wallet client when the user connects, call Viem actions directly, and keep the data lifecycle explicit.
Wagmi is a higher-level React wrapper around Viem Actions. Its hooks are stateful and connector-aware. Instead of calling a low-level action and then deciding how to represent loading, error, success, reconnection, and cache states, the application consumes hooks such as useAccount, useReadContract, useWriteContract, or useWaitForTransactionReceipt.
That abstraction has real value. It also has a cost.
The difference is easiest to see in the responsibilities each layer takes on:
| Responsibility | Custom Viem integration | Wagmi |
|---|---|---|
| RPC and wallet clients | Configured directly by the application | Centralized through Wagmi config and Viem clients |
| Wallet connection state | Managed with custom React state and event listeners | Exposed through connector-aware hooks |
| Chain switching | Implemented explicitly, including error states | Covered by hooks and connector integration |
| Contract reads | Direct Viem actions | React hooks with query lifecycle |
| Caching and refetching | Application-owned or manually added | Integrated with TanStack React Query |
| Transaction lifecycle | Custom state machine or local abstractions | Standardized through mutation and receipt hooks |
| Multi-chain support | Flexible, but manually coordinated | A first-class configuration concern |
| Bundle control | Maximum control over imported modules | More dependencies and framework conventions |
| Maintenance burden | Higher for the application team | Shifted toward the library and its ecosystem |
The important point is that Wagmi is not merely a collection of convenience functions. It is an opinionated application architecture for Web3 state.
Wagmi’s overhead is not just unused code. Part of it is the price of having wallet, chain, query, and transaction state represented consistently across the application.
This is why the claim that Wagmi is simply “bloated” is too crude to be useful. A large part of the package’s value comes from the state transitions that a custom Viem setup would otherwise need to model manually.
The hidden complexity of a wallet connection
A wallet button appears simple until the application has to handle production behavior.
The user may reject the connection request. The selected account can change while the page is open. The wallet may be connected to the wrong chain. A connector can become unavailable after a browser extension update. A transaction can be submitted successfully but remain pending for several blocks. The user can switch accounts in another tab, leaving the application with stale assumptions.
With a custom Viem client integration, these are not exceptional cases. They are part of the application’s state machine. The frontend needs to subscribe to provider events, reconcile the active chain with the configured chain, expose connector errors, and decide which actions are invalid during each transition.
Wagmi gives that state a common vocabulary. This is particularly useful when several screens depend on the same account and network context. The engineering team can reason about connection state through shared hooks instead of maintaining a collection of local event handlers that behave slightly differently from page to page.
That consistency matters more as the application grows. A one-screen integration can absorb manual state management. A trading interface, governance application, or multi-chain portfolio product usually cannot do so without creating its own internal framework.
Quantifying the overhead: Viem, Wagmi, and UI layers
The bundle-size argument is still legitimate. The question is how to interpret it.
Viem’s approximate 35 kB bundle size reflects its position as a low-level, tree-shakable interface. Ethers.js v6, by comparison, is around 200 kB in the cited measurements. Wagmi v2 is approximately 65 kB, while a base React setup combining Wagmi, Viem, and TanStack React Query reaches about 92.3 kB of gzipped First Load JS.
These figures are useful as directional indicators, not as universal performance guarantees. Actual output depends on the bundler, import patterns, route boundaries, framework, compression, server rendering strategy, and whether the relevant code is included in the initial route or loaded after interaction.
The more important observation is that the integration’s peripheral UI dependencies can dominate the core library cost.
RainbowKit adds approximately 266 kB to First Load JS in the cited setup. Web3Modal, now commonly associated with AppKit, adds approximately 494 kB. In both cases, the wallet connection interface can outweigh the size of Wagmi itself by a considerable margin.
| Integration path | Approximate reported size | Architectural implication |
|---|---|---|
| Viem alone | ~35 kB | Low-level control with application-owned state |
| Wagmi v2 | ~65 kB | React hooks, configuration, and connector-aware abstractions |
| Wagmi + Viem + TanStack React Query | ~92.3 kB First Load JS | Integrated Web3 state and caching model |
| Add RainbowKit | +~266 kB First Load JS | Rich wallet UI with a substantial client-side payload |
| Add Web3Modal/AppKit | +~494 kB First Load JS | Broad wallet and interaction layer with a much larger payload |
| Ethers.js v6 | ~200 kB | Larger general-purpose client surface in the cited comparison |
The table changes the nature of the optimization discussion. If the frontend’s first route includes a half-megabyte wallet modal, replacing Wagmi with custom Viem hooks may produce a smaller improvement than removing, deferring, or redesigning the wallet UI layer.
In practice, bundle analysis should distinguish at least three categories:
1. Protocol client code. Viem, Wagmi, or Ethers.js.
2. State and caching infrastructure. TanStack React Query or an equivalent custom system.
3. Wallet presentation and connector SDKs. RainbowKit, Web3Modal/AppKit, MetaMask SDK, WalletConnect dependencies, and related packages.
Lumping these together as “the Web3 bundle” obscures the bottleneck. The largest dependency is not always the one controlling the architecture.
What Wagmi v3 changes
Wagmi v3 moved third-party wallet SDK connectors, including MetaMask, WalletConnect, Porto, and Safe, to optional peer dependencies. That change allows applications to exclude connector code they do not use rather than receiving every integration through the core installation path.
This improves the dependency boundary, but it does not eliminate the design trade-off. An application still pays for the connectors it installs, the UI system it chooses, and the state model it adopts. The improvement is that the package can be composed more deliberately.
Wagmi v3 also raises the minimum supported TypeScript version to 5.9.3, compared with 5.0.4 in Wagmi v2. That is not a browser performance issue, but it affects upgrade planning, especially in monorepos with shared packages, older build tooling, or libraries that have not yet moved to the newer TypeScript baseline.
The right reading is not that v3 makes Wagmi lightweight by default. It gives teams more control over which integration surface enters the application.
When bypassing Wagmi makes architectural sense
A custom Viem client integration is most compelling when the application’s Web3 surface is narrow, the performance budget is strict, and the team is prepared to own the missing abstractions.
There are several common cases where this path is rational.
A read-heavy application with limited wallet interaction
A public analytics page may query balances, token metadata, and protocol events without requiring a wallet connection on the initial route. In that situation, a Viem public client can handle the read path with a small import surface. Wallet functionality can be loaded only when the user reaches a transaction-specific flow.
This separation is often better than making every route depend on the full connector and React query stack. A landing page should not necessarily carry the same client-side machinery as an administration console.
A controlled transaction flow
Some applications have one or two carefully defined transaction paths: minting an asset, claiming a reward, or depositing into a specific contract. If the product does not need a generic wallet dashboard, the team may prefer to implement a small transaction state machine around Viem.
That state machine still needs to represent at least:
- wallet availability and connection status;
- active account and chain;
- transaction preparation and simulation;
- user rejection;
- broadcast success or failure;
- receipt confirmation;
- replacement or dropped transactions;
- refetching of affected contract state.
The implementation can remain small if the product genuinely has a small state surface. It becomes risky when the team assumes that one isPending boolean is enough to describe a transaction lifecycle.
A framework with its own data layer
Next.js server components, Remix loaders, or another application architecture may already define how remote data is fetched, cached, and invalidated. In that environment, adding TanStack React Query through Wagmi can create overlapping ownership.
For example, server-rendered contract reads may be handled through a request-level Viem client, while browser-only wallet actions use a separate wallet client. A custom integration can preserve that separation. Wagmi may still be appropriate for the interactive portion, but it should not be introduced simply because the project uses React.
A highly constrained mobile or embedded environment
On low-end devices, the absolute size of the JavaScript bundle can affect parse and execution time, not only network transfer. The exact CPU and memory difference between a custom Viem hook layer and Wagmi hooks depends on the application and has not been established by a single reliable benchmark. Still, reducing dependencies can be valuable where the initial route is performance-sensitive and the Web3 interaction is optional.
This is a case for measurement rather than ideology. A 60 kB saving may matter on one route and be irrelevant beside a 500 kB wallet UI on another.
Building a custom Viem client without creating a second Wagmi
The danger in bypassing Wagmi is not that custom code cannot work. It is that teams often recreate a less-tested version of the same abstraction after discovering how many states they need to support.
A practical custom Viem React setup should start with clearly separated clients.
The public client is responsible for chain reads and transport configuration. The wallet client is created from the connected provider when a browser wallet is available. These clients should not be mixed casually: a public client does not represent the user’s signer, while a wallet client should not be treated as a universal replacement for read infrastructure.
A minimal internal structure usually has four layers:
1. Chain and transport configuration. Keep RPC URLs, chain identifiers, and fallback behavior in one module.
2. Wallet session state. Represent connector availability, account, chain, and provider events explicitly.
3. Protocol actions. Wrap Viem reads, simulations, writes, and receipt queries in domain-specific functions.
4. React presentation state. Convert asynchronous protocol events into states that the UI can render consistently.
The third layer is where many custom integrations either become maintainable or begin to drift. Instead of scattering raw readContract and writeContract calls across components, define actions around the protocol’s domain:
getVaultPosition(account)simulateDeposit(account, amount)submitDeposit(account, amount)waitForDepositReceipt(hash)
These functions can still use Viem internally, but they provide a stable boundary for error handling, ABI changes, and test coverage.
Custom hooks can then be thin wrappers around those actions. Viem actions can be imported directly, including actions from viem/actions, and a client returned by Wagmi’s useClient can also be used when an application wants low-level control without abandoning Wagmi’s configuration layer entirely.
That hybrid path is often overlooked. The choice is not binary.
A team can use Wagmi for account and connector state, then call Viem actions directly for an operation not yet wrapped by a Wagmi hook. Conversely, it can use a custom public-client read layer while retaining Wagmi for wallet connection and transaction state. This can reduce the amount of framework behavior used without forcing a full migration.
The cleanest optimization is often not “remove Wagmi.” It is to decide which layer owns each responsibility and stop paying for two competing state models.
Do not hide chain switching behind a boolean
Chain switching is a frequent source of fragile custom integrations. The application needs to distinguish between:
- the wallet being disconnected;
- the wallet being connected to an unsupported chain;
- the target chain being available to the connector;
- the switch request being rejected;
- the switch succeeding but the application state not yet being reconciled.
A single wrongNetwork flag does not capture those transitions. The UI may display a switch button, but the action behind it still needs explicit error classification and a re-read of the active chain after the provider reports success.
Wagmi’s advantage here is not magical blockchain behavior. It is that the connector-aware state and common transitions have already been shaped into a reusable interface. A custom implementation should be judged against that full responsibility, not against the code required to render a connect button.
Managing state and caching without TanStack React Query
The strongest argument for Wagmi is often not the hooks themselves. It is the way they integrate Web3 reads and mutations with a cache.
Contract reads are remote data. They can become stale after a transaction, change when the block advances, or depend on the active account and chain. A caching layer can deduplicate requests, coordinate refetches, retain previous data during transitions, and prevent every component from independently polling the same contract.
Wagmi’s React hooks use TanStack React Query as part of this state model. For a multi-screen application, that can be a substantial productivity gain. Developers receive familiar query and mutation semantics instead of building cache keys, invalidation rules, retry policies, and lifecycle handling from scratch.
A custom Viem integration has three realistic alternatives.
Keep reads server-side where possible
If a read does not depend on the connected wallet, server-side fetching can reduce browser JavaScript and avoid duplicating requests across components. This works well for public protocol data, token metadata, and pages where a slightly delayed update is acceptable.
The trade-off is freshness and interactivity. A server-rendered balance will not automatically reflect a transaction completed in the browser. The application needs a reconciliation path after the wallet action.
Use a narrow client cache
A small application may not need a general query library. A domain-level cache can store the specific reads that matter, keyed by chain, contract address, function, and account. After a successful transaction, the application invalidates only the affected records.
This approach gives more control and can keep the dependency graph smaller. It also requires discipline around cache identity. If the active chain or account is omitted from a key, the UI can display valid data for the wrong context, which is a correctness failure rather than a cosmetic bug.
Use an existing application data layer
If the product already uses a data-fetching system, protocol reads can be integrated into that system instead of adopting TanStack React Query through Wagmi. This can make sense when REST, GraphQL, indexed blockchain data, and direct RPC reads already share a caching and loading model.
The cost is that blockchain-specific invalidation still needs to be modeled. A GraphQL query may be refreshed on a timer, while a direct contract read should be invalidated immediately after a receipt. Treating all remote data as identical usually produces either excessive polling or stale transaction results.
The choice depends on the application’s data topology:
| Application profile | More suitable default | Reason |
|---|---|---|
| Single route, one wallet action | Custom Viem hooks | Small state surface and strong bundle control |
| Multi-chain dApp with several connectors | Wagmi | Connector and chain state are central concerns |
| Read-heavy analytics frontend | Viem plus server or domain cache | Avoids loading wallet infrastructure on read routes |
| Existing React data platform | Wagmi selectively or custom Viem | Prevents two competing cache architectures |
| Consumer wallet experience | Wagmi plus a measured UI layer | Consistency and connector coverage outweigh minimal core size |
| Highly performance-sensitive entry route | Deferred Web3 chunk or custom Viem | Keeps optional interaction out of the initial payload |
The wallet connector and UI problem
Wagmi’s core package is only one part of the client-side footprint. Connector packages and wallet UI libraries can become the dominant source of bundle growth.
The move to optional peer dependencies in Wagmi v3 improves this boundary. An application that supports only a small set of wallet paths can avoid installing unrelated third-party SDK connectors. That is a meaningful improvement for production builds, particularly when the project uses strict package ownership and analyzes route-level imports.
But the UI layer still deserves independent scrutiny. A wallet modal that supports dozens of connection methods may be useful for a consumer-facing product. It may be excessive for an enterprise application with a known browser extension or an embedded wallet flow.
The relevant questions are architectural:
- Does the initial route need a wallet modal, or can the connection code load after user intent?
- Are all installed connectors available in the product’s target environments?
- Does the UI library import SDK code eagerly?
- Can the wallet interface be rendered in a client-only boundary?
- Are connector errors translated into actionable product states?
- Is the application measuring First Load JS, not just package sizes in isolation?
A large modal can be justified. It should not be mistaken for a protocol requirement.
Migration strategy: reduce surface area before replacing abstractions
Replacing Wagmi with custom Viem hooks is not always straightforward. The migration must account for wallet connection states, chain switching, query caching, transaction mutations, and every component that currently relies on shared configuration.
A safer migration begins with an inventory rather than a rewrite.
1. Identify the actual bottleneck
Measure route-level First Load JS and inspect the dependency graph. Determine whether Wagmi, TanStack React Query, a wallet connector SDK, or the UI library is responsible for the largest portion of the payload.
If RainbowKit contributes approximately 266 kB or Web3Modal/AppKit approximately 494 kB in the relevant setup, replacing a 65 kB core abstraction may not address the dominant cost.
2. Separate read paths from wallet paths
Move public, account-independent reads into server-rendered or independently loaded modules where the framework supports it. Keep wallet-dependent actions in a client boundary.
This reduces the amount of Web3 code required by routes that never ask the user to sign a transaction.
3. Remove unused connectors
With Wagmi v3’s optional connector dependency model, first test whether the application can achieve the required wallet coverage without installing every SDK. This is a lower-risk optimization than replacing the state layer.
4. Replace isolated hooks first
If one screen uses a specialized Viem action that Wagmi does not expose conveniently, call the Viem action directly or build a narrow custom hook. This preserves the shared account and query model while reducing local abstraction friction.
5. Introduce domain-level transaction boundaries
Before a full migration, consolidate writes and receipt handling behind protocol-specific functions. That work remains valuable whether the final application uses Wagmi or custom hooks.
6. Re-measure behavior, not only bytes
A smaller bundle is not an improvement if it introduces stale balances, duplicated requests, broken reconnect behavior, or transaction states that remain pending indefinitely. Measure interaction latency, route loading, RPC request volume, and error recovery alongside JavaScript size.
The migration should end when the application has a better ownership model, not merely fewer dependencies.
A practical trade-off matrix
The decision can be reduced to a set of engineering priorities, but not to a universal winner.
| Priority | Wagmi | Custom Viem integration |
|---|---|---|
| Fast implementation of standard dApp flows | Strong | Moderate |
| Multi-chain and connector state | Strong | Application-owned |
| Smallest possible protocol client footprint | Moderate | Strong |
| Consistent React query lifecycle | Strong | Requires additional design |
| Fine-grained bundle control | Moderate to strong with careful imports | Strong |
| Long-term maintenance for a large team | Strong if conventions are adopted | Variable; depends on internal abstractions |
| Specialized low-level actions | Can call Viem directly | Strong |
| Initial-route performance | Requires route splitting and UI discipline | Easier to minimize |
| Migration cost from an existing Wagmi app | Low | Potentially high |
| Risk of duplicated state models | Lower when used consistently | Higher if several custom layers emerge |
The practical recommendation is to retain Wagmi when the application’s main difficulty is state coordination: multiple chains, multiple connectors, transaction-heavy flows, and many React surfaces depending on the same account context.
Use custom Viem integration when the application has a narrow protocol surface, a strict initial-route budget, and a team willing to own wallet and cache behavior. In that scenario, the smaller client is not a marketing metric; it is a consequence of removing capabilities the product does not need.
For applications in the middle, use a hybrid architecture. Keep Wagmi’s configuration and connector state, access low-level Viem actions where needed, defer wallet UI code, and split read-heavy routes from transaction routes. This often captures most of the performance benefit without discarding the state machinery that prevents production bugs.
Final recommendation
Wagmi hooks are worth the overhead when they replace a substantial amount of application code and provide a stable model for wallet, chain, query, and transaction state. They are not worth carrying blindly into every route, particularly when the application has a read-heavy public surface or a single optional wallet action.
Custom Viem hooks are not a free performance upgrade. They exchange library weight for engineering ownership. The bundle may shrink, but the system still needs finality tracking, cache invalidation, connector events, chain reconciliation, and failure recovery. Those costs move from dependencies into your codebase.
Start with the bottleneck. If the payload is dominated by a wallet UI library, optimize that boundary. If unused connectors are inflating the build, remove them. If the application’s Web3 logic is genuinely narrow, a custom Viem client can be the cleaner architecture. If the product is a multi-chain dApp with a broad interactive surface, Wagmi’s stateful layer is usually the more defensible trade-off.
The decisive question is not whether Wagmi is bloated. It is whether the state management it provides is cheaper, safer, and more coherent than the custom infrastructure your application would have to maintain without it.
FAQ
Is Wagmi significantly slower than a custom Viem integration?
Does removing Wagmi automatically reduce my application's bundle size?
When is it better to use a custom Viem integration instead of Wagmi?
How can I reduce the bundle size of my Web3 application without removing Wagmi?
What happens if I replace Wagmi with custom Viem hooks?
By Lucas Meade