Introducing Recached: Cache Locally, Sync Everywhere
Imagine a dashboard showing inventory levels. Your backend has the latest value, but the browser has an older copy. Another tab needs the same update. A mobile app goes offline and reconnects later. A seemingly simple c
Imagine a dashboard showing inventory levels. Your backend has the latest value, but the browser has an older copy. Another tab needs the same update. A mobile app goes offline and reconnects later.
A seemingly simple cache now involves several moving parts: server-side caching, API requests, client-side state, invalidation, WebSocket events, and reconnection logic.
What if the cache could live inside the applicationβand synchronize with the server?
That's the idea behind Recached, an open-source, local-first cache and real-time sync engine powered by Rust.
Cache locally. Sync everywhere.
Recached is designed to keep shared cache data close to where it's used: inside a browser, a native mobile app, or a backend service. Applications can read cached values from local memory while changes flow between the server and connected clients.
The problem: caching doesn't end at the backend
A traditional cache such as Redis is great for keeping frequently accessed data close to your server. But if that data also needs to appear in an interface, you often end up building another system around it:
- Your backend writes a value to the cache.
- Your frontend polls an endpoint or receives a custom WebSocket event.
- Client-side code updates another cache or state store.
- When a device reconnects, you decide what it missed and how to recover.
None of these steps is inherently wrong. But they're recurring infrastructure tasks, especially in real-time dashboards, shared application state, and offline-capable applications.
Recached combines the cache and its synchronization path. The backend can continue using a familiar Redis client, while supported clients maintain a local view of the keys they watch.
How Recached works
At its core, Recached uses a shared Rust cache engine across its server and client runtimes.
Your backend / API
|
Redis client (RESP)
|
v
+----------------+
| Recached |
| server |
+----------------+
|
WebSocket sync
|
+-----------+-----------+
| | |
v v v
Browser Mobile Rust service
(WASM) (native) (embedded)
| | |
Local cache Local cache Local cache
| | |
Local reads Local reads Local reads
The two important paths are different:
- Backend writes: Existing Redis clients communicate with the Recached server using its supported RESP command subset.
- Client reads: Browsers, mobile apps, and embedded Rust services read from their own local cache. Watched server data arrives through WebSocket synchronization.
A client can subscribe to key patterns to obtain an initial snapshot and later changes. Reconnecting clients can reconcile missed updates. An initial read may still miss until the first snapshot has arrived, so applications should handle loading and synchronization state.
Let's build a tiny live inventory example
Here's a local development setup with a Node.js backend and a browser client.
1. Start the Recached server
Run it with Docker:
docker run -p 6379:6379 -p 6380:6380 ghcr.io/recached-sh/recached:latest
By default, 6379 is the Redis-compatible RESP port and 6380 is the WebSocket sync port.
You can verify the server responds:
redis-cli ping
# PONG
Security note: This is a localhost development example. Do not expose a default server publicly: authentication, TLS, browser-origin restrictions, and key-level sync scopes need to be configured for your deployment.
2. Write a value using a Redis client
Install ioredis in your Node.js project:
npm install ioredis
Then use the familiar Redis API:
import Redis from 'ioredis';
const cache = new Redis('redis://127.0.0.1:6379');
await cache.set('inventory:item:99', '42');
The backend doesn't need a custom Recached driver. It uses the supported commands through a standard Redis client.
3. Read it locally in the browser
Install the browser SDK:
npm install recached-edge@^0.4.0
Recached runs its cache engine in the browser through WebAssembly:
import { createCache } from 'recached-edge';
const cache = await createCache({
connect: { url: 'ws://127.0.0.1:6380' },
});
// React whenever this local key changes.
const unsubscribe = cache.onKeyChange('inventory:item:99', () => {
console.log('Inventory:', cache.get('inventory:item:99'));
});
// Request an initial snapshot and subsequent updates.
cache.liveQuery('inventory:*');
// This is a local-memory read; it can be null before hydration.
console.log(cache.get('inventory:item:99'));
// Call unsubscribe() when the listener is no longer needed.
Now change the value from your backend:
await cache.set('inventory:item:99', '41');
The subscribed browser receives the change, updates its local cache, and invokes the change listener. The UI doesn't have to poll an API endpoint to discover the new value.
The interesting part isn't merely that Recached uses WebSockets. It's that the value you read is already held inside the application.
It also works without a server
The browser package can be used as a standalone local cache. Simply omit the connect option:
import { createCache } from 'recached-edge';
const cache = await createCache({
persistence: true,
broadcastChannel: 'my-app',
});
cache.setJSON('settings', { theme: 'dark' }, 3600);
const settings = cache.getJSON('settings');
In this mode, you can use local cache operations, TTLs, JSON values, optional IndexedDB persistence, and cross-tab mutation sharing through BroadcastChannelβwithout running a Recached server.
Browser persistence has limits: it records local writes, not every server-pushed value. It's important to understand what will be available after a page refresh, especially in applications that connect to a server. The persistence documentation explains the contract.
One engine, multiple runtimes
Recached's shared Rust foundation extends beyond the browser:
| Runtime | Integration | What it offers |
|---|---|---|
| Browser |
recached-edge (WASM) |
Local cache, WebSocket sync, optional IndexedDB persistence |
| React / Vue |
@recached/react, @recached/vue
|
Reactive adapters for the browser cache |
| Android | Kotlin SDK | Native Rust core, SQLite persistence, Kotlin Flow observers |
| iOS / macOS | Swift SDK | Native Rust core, SQLite persistence, Swift async observation |
| Rust services | recached-embed |
In-process cache for watched server data |
The APIs and durability guarantees aren't identical across platforms. The Android and Apple SDKs are 0.1.0 previews, the browser sync layer is beta, and the embedded Rust client is currently an unpublished Git dependency.
For example, a Rust service can keep frequently read feature flags or configuration values in its own process memory instead of fetching them over the network on every request. A native mobile app can restore cached data from SQLite after restarting offline and reconcile when it reconnects.
Where it fitsβand where it doesn't
Recached is useful when you need:
- Live dashboards: Counters, status changes, and operational data that should update without polling.
- Shared client-visible state: A cache that multiple tabs or devices can observe.
- Offline-capable applications: Previously saved reference data and queued cache writes that can reconcile after reconnecting, subject to documented limits.
- Local configuration reads: Feature flags and tenant settings used frequently by application services.
But it's important to be specific about what Recached is not.
It's not a replacement for PostgreSQL or MySQL. Your database should remain the source of truth. Publish committed state into Recached as a derived cache; a cache write is not a database transaction.
It's not a drop-in replacement for every Redis workload. Recached supports a subset of Redis commands, but does not implement features such as Lua scripting or Redis Streams. Software that depends on those primitives may not work, even though its Redis client can connect.
It's not a general-purpose CRDT engine. Conflicting writes do not get automatic, application-aware conflict resolution, and queued writes are not a durable business transaction system.
This distinction matters. Recached is designed around local cache reads plus real-time synchronization, not maximum Redis compatibility or database replication.
A critical production consideration: sync scopes
Real-time synchronization also creates an authorization problem. In the default unscoped configuration, server mutations can be pushed to every connected WebSocket client. That's not appropriate for a multi-user application.
Recached provides sync scopes so your backend can mint signed tokens granting read or read/write access to specific key patterns. A user might receive read access to catalog:* and read/write access only to their own cart keys.
Watching a pattern is not the same thing as having permission to see it. Before building a multi-user app, read the sync scopes guide and security documentation.
What stage is the project at?
Recached is open source under the Apache 2.0 license, and it's still evolving.
The cache server is described as a release candidate for cache workloads, while the synchronization layer is beta and the native SDKs are early previews. The project has automated tests and documented performance measurements, but has not completed broad production validation or an independent security audit.
The goal isn't to claim that every application should replace its current cache. It's to explore a different developer experience: write cached data once, keep useful copies near applications, and synchronize changes without rebuilding the same plumbing in every project.
Try Recached
If you're building a real-time web app, an offline-capable mobile experience, or a service that repeatedly reads shared configuration, Recached may be worth experimenting with.
- GitHub: github.com/recached-sh/recached
- Documentation: recached.dev
- Quick start: recached.dev/guide/quick-start
- Client support and limitations: recached.dev/guide/client-support
I'd love feedback from developers working on caching, local-first software, WebAssembly, or real-time synchronization. What would you want from a cache that runs inside both your server and your applications?
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.