Web3 Development · 5 min read ·
Learn how to build reliable Web3 frontends using wagmi and viem: wallet UX, reads/writes, chain config, contracts, and production best practices.
Building Web3 frontends used to mean wrestling with provider quirks, brittle RPC calls, and wallet UX that felt bolted on. Today, the wagmi + viem stack is the most pragmatic way to ship production-grade Ethereum (and EVM) apps in React: wagmi gives you ergonomic React hooks and state management; viem gives you a modern, type-safe Ethereum client that avoids a lot of the historical footguns.
This article focuses on how to actually build with the stack—connect wallets cleanly, read/write contracts reliably, and avoid common pitfalls that show up once you have real users.
wagmi is a React toolkit for Ethereum. It offers:
viem is the underlying TypeScript client used by wagmi v1+ (replacing ethers as the default). viem’s big wins:
Opinionated take: if you’re starting in 2026 and you’re still reaching for “raw ethers provider + custom hooks,” you’re opting into maintenance work you don’t need.
A solid baseline is: configure chains once, set up transports (RPC endpoints), then wrap your app with WagmiProvider.
Key choices you’ll make early:
A typical setup includes:
@wagmi/core and wagmi for hooksviem for utilities and types@tanstack/react-query for cachingIn practice, you should treat RPC selection as a production concern. Public RPCs are fine for prototyping, but they’ll throttle or degrade under load.
Most Web3 apps fail users before they ever sign anything.
With wagmi, you model connection state via hooks like useAccount, useConnect, and useDisconnect. The UX you want:
Practical tips:
If you’re building for mobile, you almost certainly want WalletConnect. For desktop, injected wallets (MetaMask, Rabby) cover a lot of ground.
Reads are where wagmi shines because it integrates with query caching. Use useReadContract for single reads and useReadContracts for batches.
This matters because in real apps you often need multiple pieces of state:
Batching these reads (or using multicall under the hood) improves UX and reduces RPC load.
Best practices for reads:
const so TypeScript infers types.enabled flags to avoid reads before prerequisites exist (e.g., no address yet).Example scenario: on a token swap screen, you might poll quotes every 10–15 seconds, but you shouldn’t poll static metadata constantly.
Blindly calling writeContract is how you ship broken UX.
The production pattern is:
In wagmi, this usually means pairing useSimulateContract with useWriteContract, then tracking confirmation with useWaitForTransactionReceipt.
Why this matters:
Also: treat the transaction lifecycle as a state machine in your UI—idle → signing → pending → confirmed/failed. Users tolerate slow blocks; they don’t tolerate ambiguity.
ERC-20 approvals are where many apps lose users.
If your dapp needs a token transfer by a contract, you must:
allowance(owner, spender))A clean flow:
Opinionated recommendation: default to exact approvals unless your product has a strong reason for infinite approvals. Unlimited allowances reduce friction but increase blast radius if something goes wrong.
Multi-chain support isn’t just “add more chain configs.” You need to think about:
Use wagmi’s chain primitives and make your contract address selection explicit (e.g., a map keyed by chainId).
Practical approach:
contracts[chainId] = { token, router, vault }Don’t scatter if (chainId === …) checks across components. Centralize it.
Users don’t care about “CALL_EXCEPTION.” They care about what to do next.
With viem, you can often decode errors more reliably, but you still need a strategy:
A production app should also handle flaky RPCs gracefully:
Before shipping, validate your core flows on:
Hardening checklist:
address types carefullybigint for onchain amounts (avoid floating point)In our experience at ChainMagic Studio, most “Web3 frontend bugs” are actually state management and edge-case UX issues—wagmi + viem reduces the surface area, but you still need discipline.
wagmi and viem let you build modern Web3 frontends that feel closer to traditional product engineering: typed APIs, cached reads, explicit transaction lifecycles, and fewer mystery failures. The teams that succeed treat wallet connection and transaction flows as first-class UX, simulate before writing, centralize chain configuration, and invest in error handling.
If you do that, you’ll ship a frontend that users trust—and in Web3, trust is the product.