Web3 Development · 5 min read ·
Learn how to build modern Ethereum app frontends using wagmi hooks and viem clients for fast, type-safe wallet connections, reads, and writes.
Most Web3 frontends fail for boring reasons: flaky wallet connections, inconsistent chain config, slow RPC reads, and contract calls that are hard to type and harder to debug. The modern fix is a clear separation of concerns:
Together they replace the older “ethers everywhere” approach with something more modular, more type-safe, and easier to maintain at scale.
A good Web3 frontend needs to do four things reliably:
wagmi handles (1) and much of (4) through opinionated React hooks. viem excels at (2) and (3) with a small surface area and strong TypeScript types.
The opinionated take: if you’re starting a new React/Web3 app in 2026, wagmi + viem should be your default unless you have a hard constraint (non-React frontend, custom wallet stack, or deep protocol-specific client requirements).
A typical stack:
Key design choice: configure chains and transports once, then consume via hooks everywhere.
In wagmi v2, you create a config that wires:
chains (e.g., mainnet, base, arbitrum)transports (HTTP/WebSocket per chain)Then wrap your app with WagmiProvider (and typically QueryClientProvider). This centralizes chain + RPC policy so you don’t scatter RPC URLs across components.
RPC reliability is the hidden cost center of Web3 frontends. Two practical rules:
viem transports make this straightforward. You can route by chain and swap providers without rewriting app code.
Example policy that scales:
The most common frontend bug is mismatching ABI types and arguments. viem’s typing helps prevent that.
In wagmi, reads typically use useReadContract and useReadContracts (batch). Under the hood, wagmi relies on viem clients.
Practical patterns:
useReadContracts.staleTime rather than manual state.A concrete example: an ERC-20 token row often needs balanceOf(user), decimals(), and symbol(). Batch those three calls to avoid waterfall latency.
A polished dapp doesn’t just “send a transaction.” It:
With wagmi + viem, the simulation-first flow is a best practice, not a nice-to-have.
Typical approach:
useSimulateContract to validate args and preflight the calluseWriteContract to submituseWaitForTransactionReceipt to confirmWhy simulation matters: it surfaces errors like “ERC20: insufficient allowance” before the user pays gas, and it produces a request object that can be handed to the writer.
Real users are on the wrong chain constantly. Your UI should treat this as a first-class path.
Minimum viable UX:
useChainId() or useAccount() stateswitchChainAvoid dark patterns:
Watching events is seductive—and expensive. WebSocket subscriptions can be flaky on mobile and certain corporate networks.
Use event watching for:
Avoid it for:
If you do use it, keep the subscription scope tight and handle reconnect logic gracefully.
The fastest way to accumulate frontend debt is copy-pasting ABIs and hand-typing addresses.
Do this instead:
A practical pattern for multi-chain deployments:
contracts.ts exports { chainId: { Token: { address, abi }}}chainId (or derive it) and pick the correct addressThis prevents the classic mistake: calling the right ABI on the wrong address on the wrong chain.
bigint by design. Keep values as bigint until final formatting.formatUnits(value, decimals) and parse with parseUnits.enabled flags.wagmi and viem are not just trendy libraries—they encode hard-won lessons about wallet state, RPC reality, and type-safe contract interaction. Use wagmi to standardize connection and UX flows, use viem to make reads/writes fast and predictable, and invest early in chain config + typed ABIs.
If you do those three things, the “Web3 part” of your frontend becomes boring—in the best possible way—and your team can focus on product: onboarding, conversion, and features users actually care about.