Why We Switched to Viem
A practical look at migrating our entire Web3 stack from Web3.js to Viem — what worked, what didn't, and whether we recommend it.
Background
We've been building Web3 products since 2021. For most of that time, Web3.js was the default choice for Ethereum interactions. It worked, but it came with friction — large bundle sizes, TypeScript struggles, and an API that felt dated.
When Viem launched, we were skeptical. Another Ethereum library? But after hearing consistently positive feedback from the community and seeing Wagmi (which we were already using) adopt it as its core, we decided to evaluate it seriously.
The Migration Process
Our migration took place over two weeks, affecting three production applications. Here's what we learned.
Bundle Size: The Immediate Win
This was the first thing we noticed. Viem's tree-shakeable architecture means you only ship what you use.
// Before: Web3.js
import Web3 from 'web3'
const web3 = new Web3(rpcUrl)
// Bundle: ~140KB gzipped
// After: Viem
import { createPublicClient, http } from 'viem'
const client = createPublicClient({ transport: http(rpcUrl) })
// Bundle: ~30KB gzippedFor our applications, this translated to ~45% reduction in Web3-related bundle size. Significant, especially on mobile.
TypeScript: Actually Works Now
Web3.js TypeScript support was... optimistic at best. Viem was built with TypeScript from day one, and it shows.
// TypeScript actually catches mistakes now
import { erc20Abi } from 'viem'
const balance = await client.readContract({
address: tokenAddress,
abi: erc20Abi, // Fully typed ABI
functionName: 'balanceOf',
args: [address], // TypeScript knows this needs an address
})
// Try passing a string to args that expects an array?
// TypeScript: "Type 'string' is not assignable to type 'Address'"The API: Cleaner, More Predictable
Viem's API follows a consistent pattern. Actions are grouped logically, and the naming is intuitive.
// Web3.js - inconsistent methods
const balance = await contract.methods.balanceOf(address).call()
const tx = await web3.eth.sendTransaction({...})
const gas = await web3.eth.estimateGas({...})
// Viem - consistent pattern
const balance = await client.readContract({...})
const txHash = await client.sendTransaction({...})
const gas = await client.estimateGas({...})What Was Tricky
No migration is painless. Here were our friction points:
- Custom ABIs — We had complex custom ABIs that needed rewriting. Not hard, just tedious.
- Event parsing — Viem's event decoding works differently. Took some adjustment.
- Provider differences — Viem doesn't include a provider. You need wagmi or a separate provider setup.
- Historical reads — Some patterns for fetching historical data needed rethinking.
Performance Improvements
Beyond bundle size, we saw measurable performance improvements:
- ~20% faster initial RPC calls due to optimized transport layer
- Better request batching with VIEM's public client
- More reliable reconnection handling
- Reduced memory footprint
Would We Recommend It?
Yes. If you're starting a new Web3 project, use Viem. If you're maintaining a Web3.js project, migrate when you have bandwidth.
The bundle size reduction alone justifies the switch. The TypeScript improvements alone justify the switch. Combined with better performance and a more maintainable codebase, it's not even close.
Migration Checklist
If you're planning a migration:
- Set aside dedicated time (estimate 1-2 weeks for full migration)
- Write tests for critical transaction flows before starting
- Migrate one application at a time
- Use wagmi if you're using React — the integration is seamless
- Keep Web3.js around during transition for edge cases
Building with Web3 and considering a stack change? We've migrated dozens of projects and can help you plan the transition.
Let's discuss your architecture →Building a Web3 product? Let's talk →