Hotwire and Turbo Streams have become the default answer for real-time applications in the Rails ecosystem. They are great for HTML-over-the-wire setups, but what if you prefer building your frontend with component frameworks like Svelte, paired with Inertia.js for server-driven routing? When you step outside the Turbo ecosystem, real-time sync often gets cumbersome. Do you re-fetch Inertia page props on every WebSocket event? Do you write custom ActionCable subscribers and manually mutate complex client-side stores? There’s a much cleaner way to handle real-time sync without Turbo—by placing a local database in the middle. Meet DexieCable. The Problem: Real-Time in Inertia Apps Inertia.js gives us monolith productivity with the rich UI component model of Svelte, Vue, or React. However, handling real-time updates over WebSockets in an Inertia app typically leads to one of two awkward patterns: Inertia Reloads (router.reload({ only: ['todos'] })): Every WebSocket event triggers a network request back to Rails to fetch updated props. It works, but it causes unnecessary server load and adds latency to UI updates. Manual Component State Sync: You listen to ActionCable events and manually splice arrays or update objects inside frontend state stores. This scales poorly and quickly leads to brittle client-side logic. The Solution: Local-First Synchronization with DexieCable DexieCable bridges the gap between Rails (via ActionCable) and client-side IndexedDB (via Dexie.js). Instead of pushing WebSocket updates directly into your UI components, DexieCable allows Rails to execute Dexie.js write operations straight into IndexedDB over ActionCable. Your UI components then subscribe to Dexie using reactive liveQueries. [ Rails / ActionCable ] | [ DexieCable ] | [ Dexie (IndexedDB) ] | [ LiveQuery ] Enter fullscreen mode Exit fullscreen mode This architecture gives you some significant benefits: Zero Sync Boilerplate: Components don't care how data arrived in IndexedDB; they simply observe local tables. Instant UI Updates: UI rendering runs off browser memory/IndexedDB—eliminating network render delays. Decoupled Architecture: ActionCable updates IndexedDB in the background, regardless of which page or component is currently mounted. Declarative Rails Macros: You can automate model broadcasting on the backend using simple ActiveRecord macros. How It Works in Practice Let’s look at a complete example using Rails, ActionCable, DexieCable, and Svelte. 1. Setting Up the Client (Dexie + DexieCable) Configure your Dexie database and point DexieCable to your database instance. // db.js import Dexie from "dexie"; import DexieCable from "dexiecable"; export const db = new Dexie("MyAppDB"); db.version(1).stores({ todos: "id, title, completed, updated_at" }); // Pass your Dexie database instance to DexieCable and subscribe to your channel DexieCable.db = db; DexieCable.subscribe("UserChannel"); Enter fullscreen mode Exit fullscreen mode 2. Setting Up Rails (Channel & Model) First, include DexieCable in your ActionCable channel: # app/channels/user_channel.rb class UserChannel import { db } from './db'; import { liveQuery } from 'dexie'; // Observe the local IndexedDB table reactively let todos = liveQuery(() => db.todos.toArray()); Real-Time Todos {#if $todos} {#each $todos as todo (todo.id)} {todo.title} {/each} {/if} Enter fullscreen mode Exit fullscreen mode Advanced Query Chaining from Rails syncs_to_dexie covers standard CRUD synchronization, but DexieCable also lets you chain arbitrary Dexie operations directly from Rails controllers or background jobs: # Single item insert[cite: 1] UserChannel[current_user].table("todos").add(id: 1, title: "Buy milk") #[cite: 1] # Modify matching records[cite: 1] UserChannel[current_user] .table("todos") .where(:completed).equals(false) .modify(completed: true) #[cite: 1] # Delete specific scopes[cite: 1] UserChannel[current_user] .table("todos") .where(:project_id).equals(project.id) .delete() #[cite: 1] Enter fullscreen mode Exit fullscreen mode DexieCable serializes the method chain into JSON, sends it across ActionCable, and replays the exact operation chain against the local IndexedDB database in the browser. Why Choose This Over Turbo Streams? Turbo Streams couple your backend directly to HTML fragment generation or DOM manipulation. By choosing the DexieCable + Inertia + Svelte approach: You keep complete control over your frontend state inside Svelte components. Your backend serves pure data rather than rendering HTML partials over WebSockets. Your UI feels immediate because reads occur locally against IndexedDB. Wrapping Up If you prefer the Rails + Inertia stack but want reactive real-time updates without Turbo, DexieCable provides a lightweight pattern that bridges the gap. Rails manages data and business logic, ActionCable handles transport, Dexie manages local browser storage, and Svelte delivers the UI. Check out the repository on GitHub: 👉 github.com/buhrmi/dexiecable
Real-Time Rails Without Turbo: Modern Reactive UIs with Inertia and DexieCable
Full Article
Original Source
Read the full article at Dev →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.