Web3 Development · 5 min read ·
Learn how to build reliable Web3 frontends using wagmi and viem: wallet UX, reads/writes, events, caching, and production pitfalls.
Building Web3 frontends used to mean stitching together wallet connectors, provider quirks, and brittle Ethers.js abstractions. Today, the most pragmatic stack for React apps is wagmi (React hooks + wallet UX) paired with viem (a fast, typed Ethereum client). This combo is opinionated in the right places: it makes the “happy path” easy while still letting you drop down to low-level RPC calls when needed.
This article walks through how to structure a production-grade frontend with wagmi and viem: configuration, reads/writes, events, performance, and the gotchas teams usually hit.
wagmi focuses on stateful React primitives: connect buttons, account state, network switching, read/write hooks, and excellent integration with TanStack Query. viem is the engine: typed ABI interactions, multicall, transport configuration, and predictable behavior across chains.
Why this matters in practice:
When it’s not ideal:
In a typical Next.js or Vite React project, you’ll install wagmi, viem, and a connector UI layer (many teams use RainbowKit). Even if you don’t want a full “wallet modal,” wagmi connectors still simplify account management.
A key early decision: where your public reads come from.
In wagmi v2, the core configuration is built with createConfig, chain definitions, and transports:
http() transports with fallback() for reliability.Opinionated tip: don’t ship with a single RPC URL. Redundancy saves you from “it’s down for some users” incidents.
A good wallet UX is mostly about eliminating surprise states:
wagmi gives you the building blocks:
useAccount() for address and connection statususeConnect() / useDisconnect() for lifecycleuseChainId() and useSwitchChain() for network correctnessPractical pattern:
Most Web3 UIs are read-heavy. The difference between a snappy app and a sluggish one is often just how you batch and cache reads.
With wagmi, you typically use useReadContract() for single reads and useReadContracts() for batching. Under the hood, viem enables multicall-style behavior where available.
Best practices:
useReadContracts() for dashboards. If you need balanceOf, allowance, and decimals, fetch them together.bigint. Convert at the display layer using formatting helpers (e.g., formatUnits) and keep internal math in bigint to avoid rounding errors.Real example: token approvals
allowance(owner, spender)The most common production failure mode is prompting users to sign transactions that will revert. wagmi + viem supports a safer pattern: simulate first, then write.
Recommended flow:
In wagmi, this maps cleanly to:
useSimulateContract() (or simulateContract via the public client)useWriteContract()useWaitForTransactionReceipt()Benefits:
Opinionated tip: always set UI state based on receipt confirmations, not just “transaction submitted.” Submission is not success.
A responsive UI often needs to react to onchain changes:
You have two broad strategies:
viem supports watchContractEvent and log queries. In practice:
Practical approach that scales:
A few issues show up repeatedly in real deployments:
SSR and hydration issues (Next.js). Wallet state is client-only. Ensure wallet UI components render after mount or use dynamic import to avoid mismatch.
Chain mismatch across connectors. Some wallets report chain state inconsistently on initial load. Always treat chainId as asynchronous: show a “Checking network…” state.
Decimals and formatting bugs. Don’t parse floats for token math. Keep values in bigint and format at the edge.
RPC rate limits. If your UI spams reads on every rerender, you’ll hit 429s. Lean on query caching, batching, and explicit refetch triggers.
Allowance UX. Users hate repeated approvals. Consider “max approve” toggles, but be explicit about risks. For higher-stakes apps, recommend exact approvals by default.
wagmi + viem is the most developer-friendly way to build modern Web3 frontends in React: wagmi handles wallet state and hook ergonomics; viem provides a fast, typed, explicit client for reads, writes, and events. The winning production pattern is consistent: batch reads, simulate before writes, wait for receipts, and invalidate caches deliberately.
If you adopt one mindset shift, make it this: treat the blockchain like a slow, adversarial database. Your frontend’s job is to minimize unnecessary calls, anticipate failure, and make state transitions legible to users. wagmi and viem give you the right primitives—use them with discipline.