Web3 Development · 5 min read ·
Learn how to build reliable Web3 frontends using wagmi and viem—wallet connections, reads/writes, events, and best practices for production apps.
If you’re building an Ethereum (or EVM) dApp frontend in 2026, wagmi + viem is the most pragmatic stack: it’s type-safe, composable, React-friendly, and significantly less “magic” than older Web3.js-era approaches.
Opinionated take: choose wagmi + viem when you want predictable behavior and strong typing, not a bag of side effects that break under load, network switches, or edge-case wallets.
A typical Next.js or Vite React app will use:
wagmi for hooks + connectorsviem for clients and ABI utilities@tanstack/react-query for caching (wagmi uses it)A clean structure that scales:
src/web3/wagmi.ts (config + clients)src/web3/abi/*.ts (typed ABIs)src/web3/contracts.ts (addresses per chain)src/components/* (UI)Keep your chain IDs, RPCs, and contract addresses centralized. Most production bugs come from “address drift” and chain mismatch.
The backbone is a wagmi config with transports per chain.
Key concepts:
In production, prefer:
Practical guidance:
Wallet connection is where UX goes to die if you over-engineer it.
With wagmi hooks you typically wire:
useAccount() to know if a user is connected and their addressuseConnect() to start a connection flowuseDisconnect() for logoutuseChainId() / useSwitchChain() to handle network mismatchesWhat “good” looks like:
Real example: if your dApp works on Base and Arbitrum, don’t hard-fail on Ethereum mainnet—offer a switch and show which networks are supported.
Reads are the majority of frontend traffic. Optimize for:
wagmi’s useReadContract is the default for single reads; useReadContracts can batch multiple calls.
Common patterns:
balanceOf(address)allowance(owner, spender)getReserves(), totalSupply(), slot0()Best practice: treat reads like you would REST queries.
If you’re displaying onchain prices or pool state (e.g., Uniswap V3 slot0()), you’ll want to refresh on new blocks rather than every second. That’s cheaper and less error-prone.
The single biggest reliability improvement in modern dApps is simulation before transaction submission.
Why?
With viem + wagmi, the flow is typically:
Example: ERC-20 approve + deposit
allowance.approve(spender, amount).deposit(amount).This sounds simple, but production dApps must handle:
Make writes resilient:
Event-driven UX is powerful (and often overused).
Use events when:
Otherwise, prefer simpler strategies:
If you do use events:
Opinionated recommendation: don’t build your app’s core state on client-side event subscriptions. Mobile browsers sleep tabs, WS disconnects, and you’ll end up with inconsistent state. Use events for “nice to have” realtime garnish; use indexed data or deterministic reads for truth.
Multi-chain is now the norm, but it’s a foot-gun.
Do this:
{ [chainId]: { ContractName: address } }useSwitchChain() to guide users to supported networksDon’t do this:
Also, pay attention to decimals and native currency differences when displaying balances and fees.
Raw RPC errors are hostile. Normalize them.
Common cases to map:
viem errors are structured; leverage that. Your goal is a message a non-crypto user can understand:
revert SoldOut())If you control the contract, prefer custom errors (error SoldOut();) and decode them on the frontend for clean UX.
A few battle-tested rules:
bigint.If you’re building a serious DeFi UI, consider pairing wagmi/viem with:
wagmi and viem push Web3 frontend development in the right direction: typed clients, predictable hooks, and production-grade patterns like simulation-before-send.
The winning approach is to treat onchain interactions as a disciplined system:
Get those fundamentals right, and your Web3 frontend stops feeling like a demo—and starts behaving like a product users can trust.