The Sync Dilemma in React Native

Adding offline synchronization to a React Native application typically brings to mind a few established names: PowerSync, ElectricSQL, WatermelonDB, and RxDB. These libraries aim to solve the complex problem of keeping local data consistent with a remote source, often with sophisticated underlying mechanisms like CRDTs or specialized replication protocols. However, for developer Simon Skulpt, these solutions presented an over-engineered approach for his specific needs.

Skulpt, an open-source workout tracker for iOS and Android, was designed from the ground up to function primarily offline. Its data resides in a local SQLite database, and the server-side component is an optional enhancement rather than a core dependency. Skulpt's developer opted for a custom sync layer built in TypeScript, deliberately avoiding third-party sync libraries, CRDTs, and complex backend setups like Postgres replication slots. This decision stemmed from a thorough evaluation of existing tools, each of which seemed to address a problem larger than Skulpt's immediate requirements.

React Native app architecture diagram showing custom sync layer over SQLite

Why Not Use Existing Sync Libraries?

The tradeoffs associated with popular sync solutions proved prohibitive for Skulpt's use case. PowerSync, for instance, necessitates a server-side service, whether managed or self-hosted, adding operational overhead. ElectricSQL makes strong assumptions about a Postgres-backed sync infrastructure, which wasn't part of Skulpt's simpler backend design. WatermelonDB introduces its own database abstraction layer and requires the backend to adhere to its specific sync protocol.

RxDB was a closer fit but came with a significant bundle size cost that felt excessive for the functionality required. The core issue was that these libraries are designed for a broad spectrum of complex synchronization needs, often involving real-time collaboration, complex conflict resolution, and extensive data modeling. Skulpt, by contrast, has a more constrained data model and a simpler sync requirement: ensure that user-entered workout data is eventually consistent across devices without requiring constant server connectivity.

Building a Custom Sync Layer

The custom sync layer for Skulpt operates on a straightforward principle: track changes locally and push them to a server when connectivity is available. The developer implemented this by leveraging SQLite's capabilities and a TypeScript-based logic layer. This approach allowed for fine-grained control over the synchronization process, ensuring that only the necessary data and logic were included, thus minimizing the application's footprint and complexity.

The process involves:

  • Local Change Tracking: When data is modified in the SQLite database on the device, the application logs these changes. This can be achieved through triggers, application-level logic that records mutations, or by maintaining a separate log table.
  • Batching Changes: Changes are batched together to optimize network usage. This prevents sending individual updates for every small modification, which is particularly important for mobile applications where network conditions can be variable.
  • Server Synchronization: When the device has an internet connection, the batched changes are sent to a backend API. The backend processes these changes, updates its own data store, and acknowledges receipt of the changes.
  • Receiving Remote Changes: The backend also manages changes originating from other devices. Periodically, or upon request, the device fetches any new changes from the server that it hasn't yet applied locally.
  • Applying Remote Changes: The device then applies these incoming changes to its local SQLite database. Conflict resolution, in this simpler model, might be based on timestamps or last-write-wins, depending on the specific requirements and data types.

This method bypasses the need for complex CRDT implementations or specialized database replication. The developer can define the exact sync behavior, conflict resolution strategies, and data transformation logic required for Skulpt, without being constrained by the abstractions of a third-party library. The result is a leaner, more tailored solution.

The SQLite Advantage

SQLite is a ubiquitous and powerful embedded database engine. Its strength lies in its simplicity, robustness, and wide adoption. For mobile applications, it provides a reliable local data store that performs well even with large datasets. By building sync on top of SQLite, Skulpt leverages these inherent advantages.

The decision to use SQLite as the sole data store on the device simplifies the architecture considerably. There's no need to manage multiple data layers or synchronize between an ORM and a separate database. All application data lives in one place, and the custom sync logic interacts directly with this single source of truth. This direct interaction allows for highly optimized data handling.

What's surprising is not that a custom solution was built, but the developer's explicit decision to avoid widely adopted, feature-rich libraries. This suggests a growing pragmatism in the developer community: when a simpler, bespoke solution meets the exact requirements with less overhead, it can be the more effective choice. The key here is understanding the precise scope of the problem. If your sync needs are complex, involving multi-user real-time editing or intricate conflict resolution, then libraries like PowerSync or ElectricSQL are likely still the best path. But for applications with a more defined, offline-first sync model, a custom approach built on SQLite can be surprisingly viable.

Implications for Developers

This approach offers a compelling alternative for developers who find existing sync solutions to be overkill for their projects. By understanding the fundamental principles of data synchronization and leveraging the capabilities of SQLite, it's possible to build a robust, custom sync layer. This requires a solid grasp of local data management, network communication, and state tracking. The benefits include reduced dependencies, smaller application bundles, and complete control over the sync behavior.

For founders, this strategy can lead to faster development cycles and lower operational costs, especially for applications that prioritize offline functionality. It removes the need to integrate, configure, and potentially debug complex third-party sync systems. Instead, development effort is focused on building core application features and a tailored sync mechanism that precisely fits the business logic.

The success of Skulpt's custom sync layer demonstrates that the 'build vs. buy' decision for synchronization is highly context-dependent. While powerful libraries exist, they are not always the optimal solution. For many applications, a well-architected custom sync solution built directly on a local database like SQLite can be more efficient, more controllable, and ultimately, more effective.