If you’re like me—a frontend dev who cut their teeth on design and found their freedom in crypto—then you know that building for Web3 is a constant tug-of-war between complexity and elegance.
In this post, I’m diving into three of the most important frontend techniques that have helped me scale real-world dApps without losing sleep (or my users).
1. Using Zustand/Jotai in Production Crypto dApps
Global state in Web3 isn’t just UI fluff—it can represent real token balances, on-chain auth, wallet connections, and smart contract interactions.
Why Not Redux?
Redux is a beast. It’s overkill for the lean, reactive needs of most Web3 dApps. Zustand and Jotai, on the other hand, give you atomic state management with way less boilerplate.
Zustand in Practice:
import { create } from 'zustand';
const useWalletStore = create((set) => ({
address: null,
setAddress: (addr) => set({ address: addr }),
chainId: null,
setChainId: (id) => set({ chainId: id }),
}));
No reducers. No context spaghetti. Just composable, lightweight state.
Bonus: Persistence and Middleware
Zustand supports middleware for persisting to localStorage, syncing across tabs, or debugging without extra setup.
Jotai is even more atomic—ideal for when you want to isolate individual pieces of state (like specific token values or component-scoped loading flags).
2. SSR & Caching in Next.js for Blockchain-Based Frontends
Let’s be real: most Web3 devs skip SSR. They slap up a bunch of useEffects and call it a day. But if you want SEO, faster Time to First Byte, or robust UX for unauthenticated users, server-side rendering matters.
Common Pitfalls:
- Token balances fetched client-side only? Goodbye to instant feedback.
- Wallet-dependent UI flickering on page load? UX killer.
Solution: Hybrid Rendering + Caching
Here’s what works for me:
- getServerSideProps to fetch general data (e.g. token stats, protocol metrics)
- SWR on the client to hydrate and revalidate wallet-specific data
- Edge caching using Vercel or custom headers
export async function getServerSideProps() {
const prices = await fetchTokenPrices();
return {
props: { prices },
};
}
Remember: blockchain data doesn’t change every second. Cache it wisely, rehydrate it fast.
3. Leveraging GraphQL in Web3 Apps with Live Token Data
REST is a relic. GraphQL is how you tame on-chain chaos and offer your UI exactly what it needs.
Why GraphQL?
- Fine-grained queries = less overfetching
- Combine multiple contracts/subgraphs into one UI layer
- Cleaner typings when using codegen (e.g. graphql-codegen)
Tools I Use:
- The Graph Protocol (Subgraph APIs)
- Apollo Client for caching and reactive updates
- Satsuma/Subgrounds for querying across multiple chains
Example:
query GetTokenHolders($id: String!) {
token(id: $id) {
id
symbol
holders(first: 10) {
id
balance
}
}
}
Pair this with useQuery in Apollo and you have real-time token data piped into a responsive, cache-aware UI.
Final Thoughts: Build Like It’s the New Internet
Web3 isn’t a trend—it’s a rewrite of how value, identity, and code interact. But if the frontend sucks, no one cares what your protocol does.
Zustand gives you composability. Next.js gives you speed. GraphQL gives you control. Combine these and you can build experiences that feel like magic—even when your users have no idea they just signed a transaction or bridged assets across chains.
Design like an artist. Code like an engineer. Think like a rebel. That’s the frontend crypto stack.