Web3 Development · 5 min read ·
Build fast, reliable Ethereum dapp frontends using wagmi hooks and viem clients, with modern patterns for reads, writes, and UX.
Most Web3 frontends fail for boring reasons: flaky RPC calls, inconsistent wallet behavior, and state that drifts out of sync with the chain. The wagmi + viem stack is popular because it addresses those problems with a clean mental model: viem is the low-level, type-safe EVM client, and wagmi is the React layer that turns wallet + chain interactions into predictable hooks.
If you’re building a production dapp (not a hackathon demo), the combination is hard to beat: smaller bundle sizes than legacy stacks, better TypeScript ergonomics, and an architecture that pushes you toward correct patterns.
publicClient for reads (no wallet needed) and walletClient for signed actions (needs a connected account). It’s built to be strict about types (addresses, ABIs, event args), which prevents an entire class of runtime bugs.useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt, etc.The key design win: separate read paths from write paths. Reads are cheap, cacheable, and should not depend on a wallet. Writes require explicit user intent and wallet state.
In a typical Next.js or Vite React app, you’ll wire:
A pragmatic baseline looks like:
mainnet, base, arbitrum, sepolia).Conceptually:
createConfig({ chains, transports, connectors })<WagmiProvider config={config}> and <QueryClientProvider>.Opinionated note: don’t couple your app to a single chain even if you launch on one. Users will arrive on the “wrong” network, and your frontend should handle it gracefully.
The two hooks you’ll touch constantly:
useAccount() → { address, isConnected, chain }useConnect() / useDisconnect() → connect flowsA good UX pattern:
!isConnected.Two practical tips that prevent support tickets:
useSwitchChain() and show a clear error when the user is on the wrong chain.For reads, you typically want:
wagmi’s useReadContract gives you exactly that.
Example use cases that map cleanly to reads:
balanceOf(address) for ERC-20allowance(owner, spender)Best practices:
enabled. If address is undefined, don’t call balanceOf.If you’re building a portfolio-style UI, multicall is the difference between “snappy” and “stutters on every rerender.”
Writes are where frontends get users rekt: wrong calldata, wrong chain, wrong gas assumptions.
A robust pattern is:
In wagmi, writes are typically handled with useWriteContract() and then useWaitForTransactionReceipt().
Why simulation matters:
A common ERC-20 flow:
allowance(owner, spender)approve(spender, amount)deposit(amount))This sequencing is exactly where wagmi’s hook composition shines.
Your users do not care that the error came from “CALL_EXCEPTION.” They care what to do next.
Practical error handling guidance:
viem is good at decoding revert data when ABIs include custom errors. If your contracts use Solidity custom errors (they should), your frontend can surface precise messages.
Opinionated note: if you ship contracts without meaningful custom errors, you’re choosing worse UX.
A wallet returning a transaction hash is not success. Users need a confirmed state.
Use useWaitForTransactionReceipt({ hash }) to drive:
For real-time updates (mint counters, order fills), you can subscribe to events using viem’s log utilities (or wagmi equivalents depending on your version). The pattern:
This is how you avoid naive polling loops.
Frontends are often the only layer users see, so reliability is your job—even if the issue is an RPC provider.
What we recommend in production:
Also: don’t ship a frontend that breaks when the user has multiple wallets installed. Test with MetaMask + Coinbase Wallet + a WalletConnect wallet.
At minimum, test:
address undefinedUse a testnet like Sepolia for manual QA, and consider Playwright for smoke tests. The goal is not perfect coverage; it’s preventing regressions in the flows that make or lose money.
wagmi and viem push you toward the right architecture: wallet state is explicit, reads are cacheable and independent, writes are structured and confirmable. If you care about UX, reliability, and TypeScript correctness, this stack is a strong default for EVM dapps.
The big takeaway: treat your Web3 frontend like a serious distributed system client, not a simple UI. Simulate transactions, handle networks deliberately, confirm on-chain outcomes, and invest in RPC resilience. Do that, and wagmi + viem will feel less like “Web3 tooling” and more like a real application framework.