Sitemap

Towards an Architecture For Super-Fast Local-First Apps

Why Backend Caching Beats Frontend Complexity

7 min readJul 30, 2025

--

A technical deep-dive into why TrueBlocks Browse chose intelligent backend caching over popular frontend patterns — and why it matters for the future of decentralized applications. Web 2.0 is dead. Long live Web 3.0.

The Problem That Wasn’t

When I started evaluating performance optimizations for TrueBlocks Browse, I made the classic mistake of assuming we needed to follow conventional web wisdom. You know the drill: implement aggressive frontend caching, optimize for perceived performance, minimize backend requests. The usual suspects.

But here’s the thing about building truly decentralized applications — Web 2.0 wisdom is wrong. Straight up, no holds barred, wrong.

What We Built (And Why It’s 10x Better)

TrueBlocks Browse implements “local backend caching with single-page frontend requests.” It’s not sexy. It doesn’t follow the latest React patterns. And it’s brilliant.

Here’s how it works:

The Backend: Binary Cache/Streaming To Memory

Our backend operates a sophisticated store-facet system — organized data stores for Statements, Balances, Transactions, Traces, Receipts, Logs, and more. Each facet maintains its memory-resident cache with lazy loading capabilities.

// Conceptual view of our facet system
type FacetStore struct {
cache map[string][]TransactionData
lastFetch time.Time
isDirty bool
}
func (f *FacetStore) GetPage(request PageRequest) (*PageData, error) {
if f.NeedsUpdate() {
go f.asyncLoad() // Non-blocking background update
}
return f.cachedPage(request), nil
}

The beauty is in the NeedsUpdate() method. It's not just checking timestamps—it's making intelligent decisions about data freshness based on blockchain state, user activity patterns, and resource availability.

The Frontend: Simple, Thin, and Understandable

One of the great benefits of a local-first app is that the frontend and the backend are on the same machine. In a very real sense, there is no distinction between the front end and the back end. This has massive implications.

Instead of complex layers, our React frontend makes straightforward requests directly to the backend:

“Give me page 3 of traces for this address.”

That’s it. No cache invalidation strategies. No stale-while-revalidate patterns. No complex state management for cached data. The frontend is trivially simple.

The backend responds with exactly what was requested, already optimized and ready to display.

Why This Architecture Matters

This isn’t just about performance — it’s about the fundamental promise of decentralized applications that we’ve somehow forgotten.

Press enter or click to view image in full size
The text of this article turned into a DalleDress Image

Speed Without Complexity

Our backend caching delivers sub-100ms response times for complex blockchain queries. Try getting that from a remote API while maintaining 18-decimal precision across thousands of transactions. Go ahead. We’ll wait…

But the real magic happens when you realize the frontend developer never has to think about caching. They request data. They get data. Fast data. 18-decimal place, perfectly accurate data.

Privacy by Design

Every request stays local. No third-party APIs track your wallet addresses. No analytics companies build profiles from your transaction patterns. You never have to tell anyone what your addresses even are. Your data lives on your machine, indexed by your local Khedra instance, served by your local application.

This is what we said we wanted when we started talking about “decentralized” applications, before we got distracted by convenience.

The RPC Problem, Solved

As I’ve been saying for years: “The RPC sucks.” It’s not a database — it’s barely an API. Traditional node software wasn’t designed to serve real applications, which is why every dApp today either crawls along slowly or farms out data access to centralized providers.

Our architecture solves this by treating the blockchain like what it is: a shitty database without indexing. The Unchained Index provides that missing indexing. Our streaming TrueBlocks SDK and the TrueBlocks-Core provide the access.

Together, they turn your local node into a proper application server.

The Patterns Others Miss

Event-Driven Updates

When the backend cache updates asynchronously, it doesn’t leave the frontend hanging. A simple event system notifies components when fresh data arrives:

// Frontend receives updates without polling
useEffect(() => {
const handleDataLoaded = () => {
// Fresh data available, re-render
refreshCurrentPage();
};

window.addEventListener('DATA_LOADED', handleDataLoaded);
return () => window.removeEventListener('DATA_LOADED', handleDataLoaded);
}, []);

No polling. No optimistic updates. No cache invalidation bugs. Just clean, event-driven architecture.

Lazy Loading Done Right

Most applications implement lazy loading in the frontend — load data as the user scrolls, cache everything, and hope for the best. We moved that logic to where it belongs: the backend.

The backend knows when data is stale (see Khedra). It knows when the blockchain has new blocks. It knows when a monitored address has new activity. The frontend requests pages and trusts that the backend will always have the right data at the right time.

Memory Management That Scales

Here’s what’s really clever about our system: the backend only caches data for addresses the user wants to see (again, see Khedra). It’s not trying to index the entire blockchain like a blockchain explorer. It’s not caching every possible query like a general-purpose ETL process. That creates 100s of TB (that’s terrabytes!!) of data.

Our system caches only what you need, when you need it, for as long as you need it. And because it’s on your machine, your data, your privacy — you get to decide what “need” means.

The Mist Connection

This architecture represents what the Mist browser should have been. Mist got the big idea right — local applications running against local nodes — but failed on the implementation details.

We learned from those failures:

  1. Run the node as a service, not on-demand
  2. Index the data minimally but completely, not just by log topics
  3. Cache in binary because you can. Eschew databases. They are not needed for local-first apps.
  4. Keep the frontend simple, not complex

The result is an application that starts instantly, responds immediately, and works entirely offline. Like every other application you use — except this one doesn’t spy on you.

Why Frontend Caching Would Be Wrong

I considered implementing several frontend caching strategies:

  • IndexedDB caching: Duplicate data storage, complex synchronization
  • React Query with persistence: Over-engineered state management
  • Service Worker caching: Adds deployment complexity
  • Memory-based caching: Doesn’t persist across sessions
  • Hybrid approaches: Combines all the downsides

Each would have made the frontend more complex while providing no meaningful benefit. The backend already caches everything optimally. Why duplicate that work in JavaScript? In Web 2.0, the answer to this is because of performance, but with a local-first app, where the frontend and the backend are not only on the same machine, but in the same memory footprint, speed is all but instantaneous. 100s of times faster than a trip to a remote, shared API server.

More importantly, frontend caching would violate the core principle: your data should live in one authoritative place, managed by software designed for that purpose. The backend, with its direct blockchain access and intelligent indexing, is that place.

The Future of Local-First Applications

This architecture points toward something bigger than just better performance. It’s a blueprint for the kind of applications we should be building if we actually want decentralization.

Local-first. Privacy-preserving. Fast. Accurate. Uncensorable.

The backend handles what backends do best: data management, indexing, caching, and serving. The frontend handles what frontends do best: user interaction, presentation, and workflow.

Clean separation of concerns. No clever tricks. No over-engineering. Just good software architecture applied to the unique constraints of blockchain applications.

Implementation Reality

In TrueBlocks Browse (soon to be renamed as Mist II), this architecture lives in the intersection between our Go backend and React frontend, connected by Wails bindings. The backend leverages the TrueBlocks ecosystem — the SDK, Khedra, the Unchained Index — to provide database-like access to blockchain data.

The frontend uses modern React patterns — hooks, context, clean component architecture — without getting tangled up in caching concerns. Developers can focus on building user interfaces, not managing cache invalidation strategies.

It’s the kind of architecture you can understand completely, maintain easily, and extend naturally. Which, it turns out, is exactly what you need when you’re building the tools to “yank” power back from the centralized web.

Conclusion

Sometimes the best optimization is not optimizing. Sometimes the best cache is the one the user never has to think about. Sometimes the best architecture is the one that makes complex problems simple, not the one that makes simple problems complex.

Backend caching with single-page frontend requests isn’t flashy. It doesn’t get you speaking slots at React conferences. But it works. It scales. It preserves privacy. And it lets you build the kind of local-first, decentralized applications that will matter in the future.

Which, when you think about it, is exactly what we said we wanted to build when we started this whole decentralization thing in the first place.

This article explores the architecture of TrueBlocks Browse, a local-first desktop application built with Go, React, and TrueBlocks. Learn more about our tools at trueblocks.io, explore the Khedra indexing daemon at khedra.trueblocks.io, or read about our SDK at pkg.go.dev/github.com/TrueBlocks/trueblocks-sdk/v5.

Where are These Images From?

Press enter or click to view image in full size
The reversed text of this article turned into a DalleDress Image

The images in this article were generated using TrueBlocks Dalle, our Go package for attribute-driven prompt generation and OpenAI integration. The system combines structured attributes (adjectives, nouns, styles) with template-based construction to create prompts for DALL·E 3, then enhances and annotates the resulting images. It’s designed for reproducible generative art and creative prompt engineering.

Your Support is Welcome

TrueBlocks is funded from personal funds and grants from The Ethereum Foundation (2018, 2022, 2024), Optimism Retro PGF (2022, 2023), Moloch DAO (2021), Filecoin/IPFS (2021), Consensys (2019), and our lovely GitCoin donors.

If you like this article and wish to support our work, please donate to our GitCoin grant using ETH or any token. If you’re an Optimism badge holder, vote for our project in future Retro rounds. Or send us a tip directly at trueblocks.eth or 0xf503017d7baf7fbc0fff7492b751025c6a78179b.

--

--

Thomas Jay Rush
Thomas Jay Rush

Written by Thomas Jay Rush

Blockchain Enthusiast, Founder TrueBlocks, LLC and Philadelphia Ethereum Meetup, MS Computer Science UPenn

No responses yet