DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Offline-First: Necessary for Every App, or Over-Engineering?

Is Offline-First Architecture Really Necessary?

In recent years, the "Offline-First" approach has frequently come up in software architecture discussions. I've observed that it's starting to be perceived as a trendy movement among developers; it's often presented as if it's an indispensable feature for every application. However, as someone who has been working with systems and software for over 20 years, I always ask the question, "Do we really need it?" This often stems from an eagerness to create an engineering marvel, overlooking the true cost and complexity involved.

When I was working on a manufacturing ERP, it was critical for operators using tablets on the factory floor to continue their work even during momentary network outages. In such a scenario, Offline-First becomes a necessity, whereas for a simple content site, delving into such deep engineering is often overkill. For me, the important thing is always to define the problem, then find the most pragmatic solution.

The Allure and Behind the Scenes of Offline-First

Offline-First architecture fundamentally aims to ensure that an application continues to function with full capabilities even without a network connection. It promises an uninterrupted user experience, improved performance in low-bandwidth environments, and reduced network dependency. This sounds very appealing, especially for mobile applications and systems used for remote fieldwork. It involves mechanisms such as local data storage, synchronization of changes, and transmitting them to the server when connectivity is restored.

The philosophy behind this approach is to acknowledge that the network is "unreliable" and to enable the application to adapt to this situation. The user continues to use the application without noticing whether there is an internet connection. Sometimes, when the network connection is weak, overall performance can even increase because data can be retrieved faster from local storage. However, it's important to remember that these attractive promises always come with a cost and complexity.

ℹ️ Core Components of Offline-First

Offline-First applications typically include the following core components:

  • Local Data Storage: Storing data on the device using solutions like IndexedDB, localStorage, SQLite (mobile).
  • Synchronization Logic: Algorithms that detect differences between local and remote data and merge them.
  • Conflict Resolution: Strategies for deciding which version of data takes precedence when the same data is modified both offline and online.
  • User Interface: UI designed to provide a seamless experience, independent of network status.

Real-World Needs: Who Truly Needs Offline-First?

Not every application needs to be Offline-First; that's my clear stance. Over the years, I've seen that scenarios truly warranting this architecture are quite specific. For instance, in a manufacturing company's ERP, operators used tablets at various points in the factory. These tablets had to work in areas with weak Wi-Fi coverage or where momentary outages occurred. Operations like recording a product's stage on the production line, entering material, or inputting quality control results needed to be saved instantly, even without internet. Otherwise, the production flow would halt, leading to significant costs.

Similarly, in one of my side products, an Android spam blocking application, it needed to respond instantly to incoming calls even if the phone had no network coverage. Although the database (where I used SQLite) was constantly updated, the blocking decision had to be made entirely on the device and offline. For such applications, Offline-First is not a luxury but a fundamental requirement for functionality. However, for a simple blog site, e-commerce site, or news portal, assuming users rarely stay completely offline, investing in such a complex structure yields very little return.

⚠️ The Cost of Wrong Decisions

An incorrect Offline-First decision can severely exceed a project's budget and timeline. Due to synchronization errors, data loss, and debugging difficulties, it negatively impacts both the development team's morale and end-user satisfaction.

Cost and Complexity: The Price of Over-Engineering

No matter how appealing Offline-First architecture is, the cost and complexity it brings are often overlooked. This isn't just the initial development cost, but also the long-term maintenance, testing, and debugging costs. Firstly, designing a local data storage layer is a task in itself. When using browser APIs directly, such as IndexedDB or Web SQL, you have to deal with browser compatibility, schema management, and performance issues. Libraries like PouchDB or RxDB lighten this load, but there's still a learning curve and integration cost.

Secondly, developing synchronization logic is one of the most challenging parts of an application. The answers to questions like when to synchronize which data, how to manage pending operations when connectivity is lost, and how to merge them flawlessly when connectivity is restored are far from simple. I once set up a simple polling mechanism with sleep 360 in a client's project, but it locked the system because it was OOM-killed. I then had to switch to a polling-wait mechanism. This demonstrates how even a simple synchronization error can lead to serious operational problems.

// A simple synchronization mechanism (anti-pattern)
async function syncData() {
  try {
    const localChanges = await getLocalChanges();
    if (localChanges.length > 0) {
      await sendChangesToServer(localChanges);
      await clearLocalChanges();
      console.log('Data successfully synchronized.');
    }
  } catch (error) {
    console.error('Synchronization error:', error);
  } finally {
    // This part has the potential to be OOM-killed, beware!
    // In real applications, a more sophisticated backoff strategy should be used.
    setTimeout(syncData, 360 * 1000); // Synchronize every 6 minutes (as an example)
  }
}
// syncData(); // Called when the application starts.
Enter fullscreen mode Exit fullscreen mode

A simple setTimeout loop like the one above is a very inefficient and high-risk approach in terms of resource management. I corrected this mistake by switching to a smarter polling mechanism or an event-driven structure.

Data Integrity and Conflict Management: The Biggest Headache

Perhaps the most challenging part of Offline-First architecture is maintaining data integrity and managing conflicts. What happens if a user modifies a record while offline, and simultaneously another user modifies the same record while online? Which change will prevail when connectivity is restored? "Last-write-wins" is the simplest but riskiest approach because important changes made by users can silently disappear. I experienced such a scenario in a manufacturing ERP; when two operators updated the stock quantity of the same product offline at different times, the stock became incorrect after synchronization.

To resolve such situations, more sophisticated algorithms like "Operational Transformation (OT)" or "Conflict-Free Replicated Data Types (CRDTs)" are used. However, implementing and correctly testing these algorithms requires immense engineering effort. You need to define conflict resolution strategies for every field and every data type. This requires not only coding but also a deep understanding of business workflows and anticipating all possible scenarios. For example, if two different users modify the same line in a text document offline, ensuring both changes are preserved is complex. When a numerical value (like stock quantity) changes, it's usually necessary to sum them or prioritize according to a specific rule.

💡 Conflict Resolution Strategies

Common strategies for conflict resolution include:

  • Last-Write-Wins (LWW): Simplest, the last one to update wins. High risk of data loss.
  • Merge: Attempts to combine changes. Like diff/patch for text, summation for numerical values.
  • User Intervention: Asks the user which version to keep in case of a conflict.
  • Version Vectors: Tracks which version each replica (copy) has, enabling smarter resolutions.

In my experience, especially in a side product of mine involving sensitive data like financial calculators, using the LWW approach was impossible. Every transaction had to be recorded correctly, and no data could be lost. Therefore, I built a kind of event-sourcing like structure on the client-side using timestamps and unique IDs, and during synchronization, I ensured the server merged these events in processing order. This, combined with optimistic locking, greatly increased data integrity.

Alternative Approaches and Trade-offs

So, if Offline-First isn't necessary, but we still want to improve the user experience, what should we do? There are many alternative approaches that, while not fully offline, can make the application more resilient when network connectivity is weak or intermittently lost. These can often be implemented with much less complexity and cost.

  • Smart Caching Strategies: Using Service Workers to cache the application's static assets (HTML, CSS, JavaScript, images) significantly reduces initial load time and ensures the interface is largely visible even without connectivity. HTTP caching headers like stale-while-revalidate also offer a good balance when managing content updates. For instance, for a news site, caching recently read articles with a Service Worker can provide users with some content even when offline.
  • Optimistic UI Updates: When a user performs an action (e.g., liking a post or completing a task), we instantly update the UI before the request goes to the server. When the server responds (success or failure), we adjust the UI accordingly. If the request fails, we provide feedback to the user. This significantly improves the user experience by giving the impression that the application responds instantly.
  • Background Sync: Part of the Service Worker API, Background Sync allows failed network requests to be automatically retried when connectivity is restored. This ensures that forms or messages sent by users while offline are seamlessly transmitted to the server when they come back online. In the task management application I built for my own site, I used this method for adding new tasks or completing existing ones. Even if a user added a task while offline, it would automatically synchronize when they came online.

These approaches, while not as ambitious as a full Offline-First architecture, provide sufficient flexibility and user satisfaction in most scenarios. The important thing is to correctly analyze your application's real usage scenarios and your users' network connectivity habits. For example, users working on an internal banking platform can be assumed to almost always have a stable network connection. In this case, instead of building a complex Offline-First architecture, a robust caching and error handling strategy would be much more sensible.

When I Opted for Offline-First: My Experiences

In my career, there have been a few critical moments when I needed or embarked on an Offline-First architecture. Each time, there was a very concrete business need and technical requirement behind it, not a whim or trend following.

When I worked on a manufacturing ERP, the unreliability of the factory floor's wireless network was our biggest problem. The tablets used by operators to collect production data had to work even during momentary network outages. If an operator couldn't record that a part was put into production at 09:15, the entire production schedule would be disrupted. In this situation, each tablet needed to maintain a miniature copy of its PostgreSQL database and continuously synchronize with the FastAPI-based backend. For conflict resolution, we used timestamp and versioning to track which operator updated which data most recently. This way, even if the network connection was down for 5 minutes, operators could continue their work, and data would automatically synchronize when connectivity was restored. Implementing this system took approximately 6 months, and the development cost for just this module was over 50,000 USD.

Another example was the spam blocking application I developed for Android. The phone needed to instantly evaluate and block incoming calls even if there was no network connection (airplane mode or weak signal). In this application, the database (SQLite) was kept entirely on the device, and updates were made in the background without the user noticing. Blocking decisions had to be made in milliseconds, meaning sending a request to the server for every call was unacceptable. This was truly an "Offline-First" application, and all business logic ran on the device.

🔥 Pitfalls of Offline-First

Unnecessarily investing in Offline-First not only consumes project resources but also overwhelms the development team with unnecessary complexity. Debugging, testing, and maintenance processes increase exponentially.

On the other hand, for a platform like my own blog site, I didn't even consider Offline-First. Blog posts are static content, and the vast majority of users have an internet connection when accessing such content. Caching with a Service Worker was sufficient to speed up initial loading and present the interface during short connection drops. If I had gone for a full Offline-First architecture, it would have been pure over-engineering, and the benefits it would bring would be negligible compared to the complexity it would create. I had a similar trade-off during a previous VPS migration process; opting for an unnecessarily complex cluster structure when a simple solution existed led to significant time losses.

Conclusion: A Balanced Approach Based on Application Needs

The answer to the question, "Offline-First: Necessary for Every App, or Over-Engineering?" is quite clear to me: it depends on the application's real needs and usage scenarios. Offline-First is not a "silver bullet" for every software project; most of the time, it means over-engineering and unnecessary costs. In my 20 years of field experience, I've seen that adopting a technology just "because it's cool" or "everyone is talking about it" ultimately leads to regret.

My clear position is this: If your application's critical functions must operate under conditions where your users commonly have unreliable or no network connectivity, then Offline-First is a necessity. Field operations, mobile applications (especially for critical tasks), or systems operating in remote areas fall into this category. However, for most web applications, simpler and more cost-effective approaches like smart caching strategies, optimistic UI updates, and background sync will be more than sufficient. As always in engineering, the best solution is the one that solves the problem with the least complexity and most efficiently.

Top comments (0)