Web3 frontend development has matured: users expect the same responsiveness, safety, and polish as any fintech app. The old pattern—sprinkling ethers.js calls through React components and hoping wallet state behaves—doesn’t hold up at scale.
Today, the most productive (and defensible) stack for EVM app frontends is wagmi for React hooks + wallet orchestration, and viem for fast, type-friendly Ethereum JSON-RPC interactions. Together they give you a clean architecture: viem handles the protocol details; wagmi handles React state, caching, connectors, and UX ergonomics.
Why wagmi + viem is the default choice now
A good Web3 frontend needs to solve more than “send a transaction.” You need:
- Reliable wallet connections (multiple providers, mobile vs desktop, reconnect behavior)
- Safe contract reads/writes (ABI correctness, parameter types, decoding)
- Caching and refetching (block-based or event-based updates)
- Transaction lifecycle UX (pending, confirmed, reverted, replaced)
- Multi-chain support (chain switching, mismatched chain prompts)
wagmi is effectively the “React Query for Ethereum” plus connector management. viem is a lower-level toolkit that is strict about types and encoding/decoding, and it’s notably fast. wagmi uses viem internally; the combination is consistent and future-proof.
Opinionated take: if your frontend is React (Next.js, Vite, Remix), wagmi + viem is the path of least regret for EVM.
Project setup: the minimal but production-minded baseline
Assume a Next.js (App Router) or Vite React app. You’ll typically install:
wagmiviem- a wallet UI kit (commonly RainbowKit) or build your own connect UI
In wagmi v2, you create a config with chains, transports, and connectors. A good baseline includes:
- HTTP transport for reads (fast and stable)
- WebSocket transport only if you truly need subscriptions
- Multiple RPCs (at least one paid provider) to avoid rate-limit outages
Practical advice: treat RPC selection like infrastructure, not a dev convenience. Free public RPCs are fine for prototypes, not for revenue.
Core architecture: separate reads, writes, and UI state
The biggest unlock is to avoid mixing “wallet state” with “protocol reads.”
- Public reads: use a public client (no wallet needed) for reads like price, balances (sometimes), protocol state.
- Wallet-required actions: use wallet client via wagmi hooks for signing and sending transactions.
This separation lets your app render meaningful data even before a user connects a wallet, which improves conversion.
Reading from contracts with type-safety
Contract reads are where most frontends quietly accumulate bugs: wrong ABI fragments, incorrect argument order, BigInt handling, and decoding surprises.
With wagmi, the common pattern is useReadContract (and useReadContracts for batching). You provide:
addressabifunctionNameargschainId(optional but recommended when multi-chain)
Key details that make this production-grade:
- Use ABI as a constant (e.g., generated via
abitype/wagmi clior your build tooling). Avoid hand-editing. - Use
BigIntend-to-end. viem returnsbigintfor uints. Don’t coerce tonumberexcept for UI formatting. - Batch reads for dashboards.
useReadContractsreduces RPC roundtrips dramatically.
Real example: a lending UI might batch totalSupply, borrowRate, userBorrowBalance, and userCollateralValue in one request per block.
Handling events and “live” UI updates
Many teams overuse event subscriptions early. Subscriptions are fragile across providers and browsers. A more reliable approach:
- Refetch reads on an interval or on new blocks (
watch: truepatterns) - After a write, explicitly invalidate/refetch the relevant reads
If you truly need events (e.g., chatty order books, realtime fills), use WebSocket transport and be deliberate about fallback behavior.
Opinionated take: optimistic UI beats event subscriptions for most DeFi flows. Show the pending state immediately, then reconcile on confirmation.
Writing transactions: simulate first, then send
A professional-grade Web3 frontend does not “just call write.” It:
- Simulates the call to catch reverts and estimate gas
- Builds the request with the correct calldata
- Sends and captures the transaction hash
- Waits for confirmation with a configurable number of confirmations
- Updates UI and refetches state
wagmi supports this flow cleanly. The usual pattern is:
useSimulateContractto validate inputs and generate a requestuseWriteContractto submituseWaitForTransactionReceiptto track confirmation
Why simulate? Because it moves failure earlier—before the user signs. That improves trust and reduces support tickets (“I paid gas and nothing happened”).
Concrete example: in an ERC-20 approve + deposit flow:
- Simulate
approve(spender, amount) - Send approve
- Wait for receipt
- Simulate
deposit(amount) - Send deposit
Yes, it’s more steps. It’s also how you avoid edge-case reverts due to allowances, paused markets, or stale parameters.
Common UX pitfalls (and how wagmi helps)
1) Chain mismatch
Your app should detect the wrong network and offer a one-click switch. wagmi’s chain configuration makes this straightforward.
2) Multiple wallets, reconnects, and stale sessions
Wallet state is messy: users change accounts, lock wallets, or their mobile session drops. wagmi handles connector state and exposes consistent hooks so your UI doesn’t devolve into conditional spaghetti.
3) Pending transaction clarity
Users need to know:
- what they signed
- whether it’s pending
- how to view it on a block explorer
- when it confirms (or fails)
Make the transaction hash a first-class citizen in your state model, and always provide an explorer link per chain.
Advanced patterns: multicall, account abstraction, and SSR
- Multicall: Use batched reads (
useReadContracts) aggressively for performance. For complex dashboards, consider a backend indexer, but multicall gets you far. - Account abstraction (ERC-4337): wagmi can coexist with smart accounts, but plan your architecture. You may need custom connectors or a different signing flow. Keep protocol interactions in a dedicated layer so you can swap execution models.
- SSR and hydration: For Next.js, be careful with client-only wallet APIs. Render read-only views on the server, and gate wallet-dependent components behind client boundaries.
Practical rule: don’t block initial render on wallet connection. It’s a conversion killer.
Testing and debugging tips
- Use a dedicated test wallet and a local fork (Anvil/Foundry) for deterministic reproduction.
- Log simulation errors verbosely; viem errors are usually informative if you surface them.
- Add “developer mode” toggles: show chainId, account, transport, and last RPC latency.
Conclusion
wagmi + viem is the modern baseline for EVM Web3 frontends because it forces a clean separation of concerns: viem for correct, type-safe protocol calls and wagmi for React-native state, caching, and wallet UX. If you build around simulation-first writes, batched reads, and explicit transaction lifecycle UI, you’ll end up with an app that feels like real product software—not a hackathon demo.
If you’re scaling a DeFi or onchain app, invest early in these patterns. Frontend reliability is not a “nice-to-have” in Web3; it’s part of your security posture and your growth strategy.